Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
03/27/2014 07:34:02 PM (10 years ago)
Author:
djpaul
Message:

xProfile: re-architecture profile field types and de-duplicate the templating and validation logic to make it easier for core and plugins to add new profile field types.

Until now, it's been pretty hard to add new types of profile field to BuddyPress. There are a couple of plugins that do a good job, but BuddyPress makes it much harder than it should be because, historically, we've hardcoded values and checks in templates throughout the project. For example, profile field templating was duplicated in the registration template, the member/profile/edit template, in parts of the wp-admin xProfile screens, and in the new wp-admin extended profile editor.

This change implements a new approach that creates a class for each profile field type; selectbox, textbox, textarea, and so on. They all share an abstract base class BP_XProfile_Field_Type which consolidates a lot of special behaviour that had been added to BuddyPress over the years (e.g. some fields accept null values, some accept multiple default values), and adds true field value validation. Unit tests are included.

We've also implemented a new "Numbers" field type with these changes. It behaves much the same as a regular textbox field does, but it only accepts numbers.

Fixes #5220 and #4694

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-xprofile/bp-xprofile-template.php

    r8142 r8178  
    222222            $css_classes[] = 'alt';
    223223
     224        $css_classes[] = 'field_type_' . sanitize_title( $profile_template->field->type );
    224225        $css_classes = apply_filters_ref_array( 'bp_field_css_classes', array( &$css_classes ) );
    225226
     
    387388        global $field;
    388389
    389         $array_box = false;
    390         if ( 'multiselectbox' == $field->type )
    391             $array_box = '[]';
    392 
    393         return apply_filters( 'bp_get_the_profile_field_input_name', 'field_' . $field->id . $array_box );
     390        return apply_filters( 'bp_get_the_profile_field_input_name', 'field_' . $field->id );
    394391    }
    395392
     
    424421 * @param array $args Specify type for datebox. Allowed 'day', 'month', 'year'.
    425422 */
    426 function bp_the_profile_field_options( $args = '' ) {
     423function bp_the_profile_field_options( $args = array() ) {
    427424    echo bp_get_the_profile_field_options( $args );
    428425}
     
    430427     * bp_get_the_profile_field_options()
    431428     *
    432      * Retrieves field options HTML for field types of 'selectbox', 'multiselectbox',
    433      * 'radio', 'checkbox', and 'datebox'.
     429     * Retrieves field options HTML for field types of 'selectbox', 'multiselectbox', 'radio', 'checkbox', and 'datebox'.
    434430     *
    435431     * @package BuddyPress Xprofile
     
    447443     * }
    448444     */
    449     function bp_get_the_profile_field_options( $args = '' ) {
    450         global $field;
    451 
    452         $defaults = array(
     445    function bp_get_the_profile_field_options( $args = array() ) {
     446        global $field;
     447
     448        $args = bp_parse_args( $args, array(
    453449            'type'    => false,
    454450            'user_id' => bp_displayed_user_id(),
    455         );
    456 
    457         $r = wp_parse_args( $args, $defaults );
    458         extract( $r, EXTR_SKIP );
    459 
    460         // In some cases, the $field global is not an instantiation of the BP_XProfile_Field
    461         // class. However, we have to make sure that all data originally in $field gets
    462         // merged back in, after reinstantiation.
    463         if ( !method_exists( $field, 'get_children' ) ) {
     451        ), 'get_the_profile_field_options' );
     452
     453        /**
     454         * In some cases, the $field global is not an instantiation of the BP_XProfile_Field class.
     455         * However, we have to make sure that all data originally in $field gets merged back in, after reinstantiation.
     456         */
     457        if ( ! method_exists( $field, 'get_children' ) ) {
    464458            $field_obj = new BP_XProfile_Field( $field->id );
    465459
    466             foreach( $field as $field_prop => $field_prop_value ) {
    467                 if ( !isset( $field_obj->{$field_prop} ) ) {
     460            foreach ( $field as $field_prop => $field_prop_value ) {
     461                if ( ! isset( $field_obj->{$field_prop} ) )
    468462                    $field_obj->{$field_prop} = $field_prop_value;
    469                 }
    470463            }
    471464
     
    473466        }
    474467
    475         $options = $field->get_children();
    476 
    477         // Setup some defaults
    478         $html     = '';
    479         $selected = '';
    480 
    481         switch ( $field->type ) {
    482             case 'selectbox':
    483 
    484                 $html .= '<option value="">' . /* translators: no option picked in select box */ __( '----', 'buddypress' ) . '</option>';
    485 
    486                 $original_option_values = '';
    487                 $original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id ) );
    488 
    489                 if ( empty( $original_option_values ) && !empty( $_POST['field_' . $field->id] ) ) {
    490                     $original_option_values = $_POST['field_' . $field->id];
    491                 }
    492 
    493                 $option_values = (array) $original_option_values;
    494 
    495                 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
    496 
    497                     // Check for updated posted values, but errors preventing them from being saved first time
    498                     foreach( $option_values as $i => $option_value ) {
    499                         if ( isset( $_POST['field_' . $field->id] ) && $_POST['field_' . $field->id] != $option_value ) {
    500                             if ( !empty( $_POST['field_' . $field->id] ) ) {
    501                                 $option_values[$i] = $_POST['field_' . $field->id];
    502                             }
    503                         }
    504                     }
    505 
    506                     $selected = '';
    507 
    508                     // Run the allowed option name through the before_save filter, so we'll be sure to get a match
    509                     $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
    510 
    511                     // First, check to see whether the user-entered value matches
    512                     if ( in_array( $allowed_options, (array) $option_values ) ) {
    513                         $selected = ' selected="selected"';
    514                     }
    515 
    516                     // Then, if the user has not provided a value, check for defaults
    517                     if ( !is_array( $original_option_values ) && empty( $option_values ) && $options[$k]->is_default_option ) {
    518                         $selected = ' selected="selected"';
    519                     }
    520 
    521                     $html .= apply_filters( 'bp_get_the_profile_field_options_select', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_attr( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $field->id, $selected, $k );
    522                 }
    523                 break;
    524 
    525             case 'multiselectbox':
    526                 $original_option_values = '';
    527                 $original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id ) );
    528 
    529                 if ( empty( $original_option_values ) && !empty( $_POST['field_' . $field->id] ) ) {
    530                     $original_option_values = $_POST['field_' . $field->id];
    531                 }
    532 
    533                 $option_values = (array) $original_option_values;
    534 
    535                 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
    536 
    537                     // Check for updated posted values, but errors preventing them from being saved first time
    538                     foreach( $option_values as $i => $option_value ) {
    539                         if ( isset( $_POST['field_' . $field->id] ) && $_POST['field_' . $field->id][$i] != $option_value ) {
    540                             if ( !empty( $_POST['field_' . $field->id][$i] ) ) {
    541                                 $option_values[] = $_POST['field_' . $field->id][$i];
    542                             }
    543                         }
    544                     }
    545                     $selected = '';
    546 
    547                     // Run the allowed option name through the before_save filter, so we'll be sure to get a match
    548                     $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
    549 
    550                     // First, check to see whether the user-entered value matches
    551                     if ( in_array( $allowed_options, (array) $option_values ) ) {
    552                         $selected = ' selected="selected"';
    553                     }
    554 
    555                     // Then, if the user has not provided a value, check for defaults
    556                     if ( !is_array( $original_option_values ) && empty( $option_values ) && !empty( $options[$k]->is_default_option ) ) {
    557                         $selected = ' selected="selected"';
    558                     }
    559 
    560                     $html .= apply_filters( 'bp_get_the_profile_field_options_multiselect', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_attr( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $field->id, $selected, $k );
    561                 }
    562                 break;
    563 
    564             case 'radio':
    565                 $html .= '<div id="field_' . $field->id . '">';
    566                 $option_value = BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id );
    567 
    568                 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
    569 
    570                     // Check for updated posted values, but errors preventing them from being saved first time
    571                     if ( isset( $_POST['field_' . $field->id] ) && $option_value != $_POST['field_' . $field->id] ) {
    572                         if ( !empty( $_POST['field_' . $field->id] ) ) {
    573                             $option_value = $_POST['field_' . $field->id];
    574                         }
    575                     }
    576 
    577                     // Run the allowed option name through the before_save
    578                     // filter, so we'll be sure to get a match
    579                     $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
    580                     $selected        = '';
    581 
    582                     if ( $option_value == $allowed_options || ( empty( $option_value ) && !empty( $options[$k]->is_default_option ) ) )
    583                         $selected = ' checked="checked"';
    584 
    585                     $html .= apply_filters( 'bp_get_the_profile_field_options_radio', '<label><input' . $selected . ' type="radio" name="field_' . $field->id . '" id="option_' . $options[$k]->id . '" value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '"> ' . esc_attr( stripslashes( $options[$k]->name ) ) . '</label>', $options[$k], $field->id, $selected, $k );
    586                 }
    587 
    588                 $html .= '</div>';
    589                 break;
    590 
    591             case 'checkbox':
    592                 $option_values = BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id );
    593                 $option_values = (array) maybe_unserialize( $option_values );
    594 
    595                 // Check for updated posted values, but errors preventing them from being saved first time
    596                 if ( isset( $_POST['field_' . $field->id] ) && $option_values != maybe_serialize( $_POST['field_' . $field->id] ) ) {
    597                     if ( !empty( $_POST['field_' . $field->id] ) )
    598                         $option_values = $_POST['field_' . $field->id];
    599                 }
    600 
    601                 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
    602                     $selected = '';
    603 
    604                     // First, check to see whether the user's saved values
    605                     // match the option
    606                     for ( $j = 0, $count_values = count( $option_values ); $j < $count_values; ++$j ) {
    607 
    608                         // Run the allowed option name through the
    609                         // before_save filter, so we'll be sure to get a match
    610                         $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
    611 
    612                         if ( $option_values[$j] == $allowed_options || @in_array( $allowed_options, $option_values ) ) {
    613                             $selected = ' checked="checked"';
    614                             break;
    615                         }
    616                     }
    617 
    618                     // If the user has not yet supplied a value for this field,
    619                     // check to see whether there is a default value available
    620                     if ( !is_array( $option_values ) && empty( $option_values ) && empty( $selected ) && !empty( $options[$k]->is_default_option ) ) {
    621                         $selected = ' checked="checked"';
    622                     }
    623 
    624                     $html .= apply_filters( 'bp_get_the_profile_field_options_checkbox', '<label><input' . $selected . ' type="checkbox" name="field_' . $field->id . '[]" id="field_' . $options[$k]->id . '_' . $k . '" value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '"> ' . esc_attr( stripslashes( $options[$k]->name ) ) . '</label>', $options[$k], $field->id, $selected, $k );
    625                 }
    626                 break;
    627 
    628             case 'datebox':
    629                 $date = BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id );
    630 
    631                 // Set day, month, year defaults
    632                 $day   = '';
    633                 $month = '';
    634                 $year  = '';
    635 
    636                 if ( !empty( $date ) ) {
    637 
    638                     // If Unix timestamp
    639                     if ( is_numeric( $date ) ) {
    640                         $day   = date( 'j', $date );
    641                         $month = date( 'F', $date );
    642                         $year  = date( 'Y', $date );
    643 
    644                     // If MySQL timestamp
    645                     } else {
    646                         $day   = mysql2date( 'j', $date );
    647                         $month = mysql2date( 'F', $date, false ); // Not localized, so that selected() works below
    648                         $year  = mysql2date( 'Y', $date );
    649                     }
    650                 }
    651 
    652                 // Check for updated posted values, and errors preventing
    653                 // them from being saved first time.
    654                 if ( !empty( $_POST['field_' . $field->id . '_day'] ) ) {
    655                     if ( $day != $_POST['field_' . $field->id . '_day'] ) {
    656                         $day = $_POST['field_' . $field->id . '_day'];
    657                     }
    658                 }
    659 
    660                 if ( !empty( $_POST['field_' . $field->id . '_month'] ) ) {
    661                     if ( $month != $_POST['field_' . $field->id . '_month'] ) {
    662                         $month = $_POST['field_' . $field->id . '_month'];
    663                     }
    664                 }
    665 
    666                 if ( !empty( $_POST['field_' . $field->id . '_year'] ) ) {
    667                     if ( $year != date( "j", $_POST['field_' . $field->id . '_year'] ) ) {
    668                         $year = $_POST['field_' . $field->id . '_year'];
    669                     }
    670                 }
    671 
    672                 // $type will be passed by calling function when needed
    673                 switch ( $type ) {
    674                     case 'day':
    675                         $html .= '<option value=""' . selected( $day, '', false ) . '>--</option>';
    676 
    677                         for ( $i = 1; $i < 32; ++$i ) {
    678                             $html .= '<option value="' . $i .'"' . selected( $day, $i, false ) . '>' . $i . '</option>';
    679                         }
    680                         break;
    681 
    682                     case 'month':
    683                         $eng_months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' );
    684 
    685                         $months = array(
    686                             __( 'January', 'buddypress' ),
    687                             __( 'February', 'buddypress' ),
    688                             __( 'March', 'buddypress' ),
    689                             __( 'April', 'buddypress' ),
    690                             __( 'May', 'buddypress' ),
    691                             __( 'June', 'buddypress' ),
    692                             __( 'July', 'buddypress' ),
    693                             __( 'August', 'buddypress' ),
    694                             __( 'September', 'buddypress' ),
    695                             __( 'October', 'buddypress' ),
    696                             __( 'November', 'buddypress' ),
    697                             __( 'December', 'buddypress' )
    698                         );
    699 
    700                         $html .= '<option value=""' . selected( $month, '', false ) . '>------</option>';
    701 
    702                         for ( $i = 0; $i < 12; ++$i ) {
    703                             $html .= '<option value="' . $eng_months[$i] . '"' . selected( $month, $eng_months[$i], false ) . '>' . $months[$i] . '</option>';
    704                         }
    705                         break;
    706 
    707                     case 'year':
    708                         $html .= '<option value=""' . selected( $year, '', false ) . '>----</option>';
    709 
    710                         for ( $i = 2037; $i > 1901; $i-- ) {
    711                             $html .= '<option value="' . $i .'"' . selected( $year, $i, false ) . '>' . $i . '</option>';
    712                         }
    713                         break;
    714                 }
    715 
    716                 $html = apply_filters( 'bp_get_the_profile_field_datebox', $html, $type, $day, $month, $year, $field->id, $date );
    717 
    718                 break;
    719         }
     468        ob_start();
     469        $field->type_obj->edit_field_options_html( $args );
     470        $html = ob_get_contents();
     471        ob_end_clean();
    720472
    721473        return $html;
Note: See TracChangeset for help on using the changeset viewer.