Skip to:
Content

BuddyPress.org

Changeset 7570


Ignore:
Timestamp:
11/14/2013 03:43:43 PM (6 years ago)
Author:
boonebgorges
Message:

Remove BuddyPress's restriction spaces in user_login

BuddyPress has historically enforced a no-spaces rule on user_login during
user registration. Originally this was rooted in WPMU's own peculiar character
restrictions, and when the MU requirement was dropped, the same restrictions
were carried over to WordPress Single.

However, these restrictions have caused various problems. BP enforced the "no
spaces" rule during registration by simply swapping out spaces with hyphens and
not telling users. This caused immense confusion. Moreover, the restriction
caused problems when bypassing BP's native user registration, as when
integrating with an external authentication service; these external usernames
*can* sometimes have spaces, and certain areas of BuddyPress were not equipped
to deal with them.

This changeset removes the no-spaces restriction from BuddyPress, and hands
off user_login validation to WordPress Multisite when possible (meaning that on
MS, spaces will still not be allowed during native registration). It also
makes the necessary adjustments throughout BuddyPress to ensure that spaces
in user_login will not break related functionality. On a normal setup, BP (and
WP) only use user_login for authentication, but several changes were necessary
to account for "username compatibility mode", where the user_login is displayed
publicly:

  • Refactor the way that activity @-mentions work in username compatibility mode. We now have functions for converting user IDs to "mentionname" (and vice versa) which will produce @-mention-safe versions of user_nicename or user_login, as appropriate.
  • Use proper URL encoding when building and parsing URLs that contain usernames when compatibility mode is enabled.
  • Fix private messaging autocomplete to work with spaces.

See #4622

Fixes #5185

Location:
trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-activity/bp-activity-functions.php

    r7469 r7570  
    7373    // We've found some mentions! Check to see if users exist
    7474    foreach( (array) $usernames as $key => $username ) {
    75         if ( bp_is_username_compatibility_mode() ) {
    76             $user_id = username_exists( $username );
    77         } else {
    78             $user_id = bp_core_get_userid_from_nicename( $username );
    79         }
     75        $user_id = bp_activity_get_userid_from_mentionname( $username );
    8076
    8177        // user ID exists, so let's add it to our array
     
    246242
    247243    return $return;
     244}
     245
     246/**
     247 * Determine a user's "mentionname", the name used for that user in @-mentions.
     248 *
     249 * @since BuddyPress (1.9.0)
     250 *
     251 * @return string User name appropriate for @-mentions.
     252 */
     253function bp_activity_get_user_mentionname( $user_id ) {
     254    $mentionname = '';
     255
     256    $userdata = bp_core_get_core_userdata( $user_id );
     257
     258    if ( $userdata ) {
     259        if ( bp_is_username_compatibility_mode() ) {
     260            $mentionname = str_replace( ' ', '-', $userdata->user_login );
     261        } else {
     262            $mentionname = $userdata->user_nicename;
     263        }
     264    }
     265
     266    return $mentionname;
     267}
     268
     269/**
     270 * Get a user ID from a "mentionname", the name used for a user in @-mentions.
     271 *
     272 * @since BuddyPress (1.9.0)
     273 *
     274 * @return int|bool ID of the user, if one is found. Otherwise false.
     275 */
     276function bp_activity_get_userid_from_mentionname( $mentionname ) {
     277    $user_id = false;
     278
     279    // In username compatibility mode, hyphens are ambiguous between
     280    // actual hyphens and converted spaces.
     281    //
     282    // @todo There is the potential for username clashes between 'foo bar'
     283    // and 'foo-bar' in compatibility mode. Come up with a system for
     284    // unique mentionnames.
     285    if ( bp_is_username_compatibility_mode() ) {
     286        // First, try the raw username
     287        $userdata = get_user_by( 'login', $mentionname );
     288
     289        // Doing a direct query to use proper regex. Necessary to
     290        // account for hyphens + spaces in the same user_login.
     291        if ( empty( $userdata ) || ! is_a( $userdata, 'WP_User' ) ) {
     292            global $wpdb;
     293            $regex   = esc_sql( str_replace( '-', '[ \-]', $mentionname ) );
     294            $user_id = $wpdb->get_var( "SELECT ID FROM {$wpdb->users} WHERE user_login REGEXP '{$regex}'" );
     295        } else {
     296            $user_id = $userdata->ID;
     297        }
     298
     299    // When username compatibility mode is disabled, the mentionname is
     300    // the same as the nicename
     301    } else {
     302        $user_id = bp_core_get_userid_from_nicename( $mentionname );
     303    }
     304
     305
     306    return $user_id;
    248307}
    249308
  • trunk/bp-activity/bp-activity-template.php

    r7520 r7570  
    584584
    585585                    // Start search at @ symbol and stop search at closing tag delimiter.
    586                     $search_terms     = '@' . bp_core_get_username( $user_id ) . '<';
     586                    $search_terms     = '@' . bp_activity_get_user_mentionname( $user_id ) . '<';
    587587                    $display_comments = 'stream';
    588588                    $user_id = 0;
     
    27192719            return false;
    27202720
    2721         return apply_filters( 'bp_get_send_public_message_link', wp_nonce_url( bp_get_activity_directory_permalink() . '?r=' . bp_get_displayed_user_username() ) );
    2722     }
     2721        return apply_filters( 'bp_get_send_public_message_link', wp_nonce_url( bp_get_activity_directory_permalink() . '?r=' . bp_get_displayed_user_mentionname() ) );
     2722    }
     2723
    27232724
    27242725/**
     
    29202921    }
    29212922
     2923/**
     2924 * Output the mentionname for the displayed user.
     2925 *
     2926 * @since BuddyPress (1.9.0)
     2927 */
     2928function bp_displayed_user_mentionname() {
     2929    echo bp_get_displayed_user_mentionname();
     2930}
     2931    /**
     2932     * Get the mentionname for the displayed user.
     2933     *
     2934     * @since BuddyPress (1.9.0)
     2935     *
     2936     * @return string Mentionname for the displayed user, if available.
     2937     */
     2938    function bp_get_displayed_user_mentionname() {
     2939        return apply_filters( 'bp_get_displayed_user_mentionname', bp_activity_get_user_mentionname( bp_displayed_user_id() ) );
     2940    }
    29222941
    29232942/**
  • trunk/bp-members/bp-members-functions.php

    r7562 r7570  
    270270    }
    271271
    272     // Check $username for empty spaces and default to nicename if found
    273     if ( strstr( $username, ' ' ) ) {
    274         $username = bp_members_get_user_nicename( $user_id );
    275     }
    276 
    277272    // Add this to cache
    278273    if ( ( true === $update_cache ) && !empty( $username ) ) {
     
    945940    }
    946941}
    947 
    948 /**
    949  * Strips spaces from usernames that are created using add_user() and wp_insert_user()
    950  *
    951  * @package BuddyPress Core
    952  */
    953 function bp_core_strip_username_spaces( $username ) {
    954     // Don't alter the user_login of existing users, as it causes user_nicename problems.
    955     // See http://trac.buddypress.org/ticket/2642
    956     if ( username_exists( $username ) && ( !bp_is_username_compatibility_mode() ) )
    957         return $username;
    958 
    959     return str_replace( ' ', '-', $username );
    960 }
    961 add_action( 'pre_user_login', 'bp_core_strip_username_spaces' );
    962942
    963943/**
     
    11731153function bp_core_validate_user_signup( $user_name, $user_email ) {
    11741154
    1175     $errors = new WP_Error();
    1176 
    1177     // Apply any user_login filters added by BP or other plugins before validating
    1178     $user_name = apply_filters( 'pre_user_login', $user_name );
    1179 
    1180     if ( empty( $user_name ) )
    1181         $errors->add( 'user_name', __( 'Please enter a username', 'buddypress' ) );
    1182 
    11831155    // Make sure illegal names include BuddyPress slugs and values
    11841156    bp_core_flush_illegal_names();
    11851157
    1186     $illegal_names = get_site_option( 'illegal_names' );
    1187 
    1188     if ( in_array( $user_name, (array) $illegal_names ) )
    1189         $errors->add( 'user_name', __( 'That username is not allowed', 'buddypress' ) );
    1190 
    1191     if ( ! validate_username( $user_name ) ) {
    1192         // Check for capital letters when on multisite.
    1193         //
    1194         // If so, throw a different error message.
    1195         // @see #5175
    1196         if ( is_multisite() ) {
    1197             $match = array();
    1198             preg_match( '/[A-Z]/', $user_name, $match );
    1199 
    1200             if ( ! empty( $match ) ) {
    1201                 $errors->add( 'user_name', __( 'Username must be in lowercase characters', 'buddypress' ) );
    1202             }
    1203 
    1204         } else {
     1158    // WordPress Multisite has its own validation. Use it, so that we
     1159    // properly mirror restrictions on username, etc.
     1160    if ( function_exists( 'wpmu_validate_user_signup' ) ) {
     1161        $result = wpmu_validate_user_signup( $user_name, $user_email );
     1162
     1163    // When not running Multisite, we perform our own validation. What
     1164    // follows reproduces much of the logic of wpmu_validate_user_signup(),
     1165    // minus the multisite-specific restrictions on user_login
     1166    } else {
     1167        $errors = new WP_Error();
     1168
     1169        // Apply any user_login filters added by BP or other plugins before validating
     1170        $user_name = apply_filters( 'pre_user_login', $user_name );
     1171
     1172        // User name can't be empty
     1173        if ( empty( $user_name ) ) {
     1174            $errors->add( 'user_name', __( 'Please enter a username', 'buddypress' ) );
     1175        }
     1176
     1177        // user name can't be on the blacklist
     1178        $illegal_names = get_site_option( 'illegal_names' );
     1179        if ( in_array( $user_name, (array) $illegal_names ) ) {
     1180            $errors->add( 'user_name', __( 'That username is not allowed', 'buddypress' ) );
     1181        }
     1182
     1183        // User name must pass WP's validity check
     1184        if ( ! validate_username( $user_name ) ) {
    12051185            $errors->add( 'user_name', __( 'Usernames can contain only letters, numbers, ., -, and @', 'buddypress' ) );
    12061186        }
    1207     }
    1208 
    1209     if( strlen( $user_name ) < 4 )
    1210         $errors->add( 'user_name',  __( 'Username must be at least 4 characters', 'buddypress' ) );
    1211 
    1212     if ( strpos( ' ' . $user_name, '_' ) != false )
    1213         $errors->add( 'user_name', __( 'Sorry, usernames may not contain the character "_"!', 'buddypress' ) );
    1214 
    1215     // Is the user_name all numeric?
    1216     $match = array();
    1217     preg_match( '/[0-9]*/', $user_name, $match );
    1218 
    1219     if ( $match[0] == $user_name )
    1220         $errors->add( 'user_name', __( 'Sorry, usernames must have letters too!', 'buddypress' ) );
    1221 
    1222     // Check if the username has been used already.
    1223     if ( username_exists( $user_name ) )
    1224         $errors->add( 'user_name', __( 'Sorry, that username already exists!', 'buddypress' ) );
    1225 
    1226     // Validate the email address and process the validation results into
    1227     // error messages
    1228     $validate_email = bp_core_validate_email_address( $user_email );
    1229     bp_core_add_validation_error_messages( $errors, $validate_email );
    1230 
    1231     // Assemble the return array
    1232     $result = array( 'user_name' => $user_name, 'user_email' => $user_email, 'errors' => $errors );
    1233 
    1234     // Apply WPMU legacy filter
    1235     $result = apply_filters( 'wpmu_validate_user_signup', $result );
     1187
     1188        // Minimum of 4 characters
     1189        if ( strlen( $user_name ) < 4 ) {
     1190            $errors->add( 'user_name',  __( 'Username must be at least 4 characters', 'buddypress' ) );
     1191        }
     1192
     1193        // No underscores. @todo Why not?
     1194        if ( false !== strpos( ' ' . $user_name, '_' ) ) {
     1195            $errors->add( 'user_name', __( 'Sorry, usernames may not contain the character "_"!', 'buddypress' ) );
     1196        }
     1197
     1198        // No usernames that are all numeric. @todo Why?
     1199        $match = array();
     1200        preg_match( '/[0-9]*/', $user_name, $match );
     1201        if ( $match[0] == $user_name ) {
     1202            $errors->add( 'user_name', __( 'Sorry, usernames must have letters too!', 'buddypress' ) );
     1203        }
     1204
     1205        // Check if the username has been used already.
     1206        if ( username_exists( $user_name ) ) {
     1207            $errors->add( 'user_name', __( 'Sorry, that username already exists!', 'buddypress' ) );
     1208        }
     1209
     1210        // Validate the email address and process the validation results into
     1211        // error messages
     1212        $validate_email = bp_core_validate_email_address( $user_email );
     1213        bp_core_add_validation_error_messages( $errors, $validate_email );
     1214
     1215        // Assemble the return array
     1216        $result = array(
     1217            'user_name'  => $user_name,
     1218            'user_email' => $user_email,
     1219            'errors'     => $errors,
     1220        );
     1221
     1222        // Apply WPMU legacy filter
     1223        $result = apply_filters( 'wpmu_validate_user_signup', $result );
     1224    }
    12361225
    12371226    return apply_filters( 'bp_core_validate_user_signup', $result );
  • trunk/bp-messages/bp-messages-functions.php

    r7527 r7570  
    7878            // @see http://buddypress.trac.wordpress.org/ticket/5151
    7979            if ( bp_is_username_compatibility_mode() ) {
    80                 $recipient_id = bp_core_get_userid( $recipient );
     80                $recipient_id = bp_core_get_userid( urldecode( $recipient ) );
    8181            } else {
    8282                $recipient_id = bp_core_get_userid_from_nicename( $recipient );
  • trunk/bp-templates/bp-legacy/buddypress-functions.php

    r7459 r7570  
    13361336
    13371337            if ( bp_is_username_compatibility_mode() ) {
    1338                 $username = $ud->user_login;
     1338                // Sanitize for spaces. Use urlencode() rather
     1339                // than rawurlencode() because %20 breaks JS
     1340                $username = urlencode( $ud->user_login );
    13391341            } else {
    13401342                $username = $ud->user_nicename;
  • trunk/bp-templates/bp-legacy/buddypress/members/single/member-header.php

    r7232 r7570  
    2323
    2424    <?php if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() ) : ?>
    25         <h2 class="user-nicename">@<?php bp_displayed_user_username(); ?></h2>
     25        <h2 class="user-nicename">@<?php bp_displayed_user_mentionname(); ?></h2>
    2626    <?php endif; ?>
    2727
  • trunk/bp-themes/bp-default/_inc/ajax.php

    r7442 r7570  
    983983
    984984            if ( bp_is_username_compatibility_mode() ) {
    985                 $username = $ud->user_login;
     985                // Sanitize for spaces
     986                $username = urlencode( $ud->user_login );
    986987            } else {
    987988                $username = $ud->user_nicename;
  • trunk/bp-themes/bp-default/members/single/member-header.php

    r7232 r7570  
    2727
    2828    <?php if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() ) : ?>
    29         <span class="user-nicename">@<?php bp_displayed_user_username(); ?></span>
     29        <span class="user-nicename">@<?php bp_displayed_user_mentionname(); ?></span>
    3030    <?php endif; ?>
    3131
  • trunk/tests/testcases/activity/functions.php

    r7469 r7570  
    101101        $this->assertEquals( $meta_value, bp_activity_get_meta( $a, 'linebreak_test' ) );
    102102    }
     103
     104    /**
     105     * @group bp_activity_get_user_mentionname
     106     */
     107    public function test_bp_activity_get_user_mentionname_compatibilitymode_off() {
     108        add_filter( 'bp_is_username_compatibility_mode', '__return_false' );
     109
     110        $u = $this->create_user( array(
     111            'user_login' => 'foo bar baz',
     112            'user_nicename' => 'foo-bar-baz',
     113        ) );
     114
     115        $this->assertEquals( 'foo-bar-baz', bp_activity_get_user_mentionname( $u ) );
     116
     117        remove_filter( 'bp_is_username_compatibility_mode', '__return_false' );
     118    }
     119
     120    /**
     121     * @group bp_activity_get_user_mentionname
     122     */
     123    public function test_bp_activity_get_user_mentionname_compatibilitymode_on() {
     124        add_filter( 'bp_is_username_compatibility_mode', '__return_true' );
     125
     126        $u1 = $this->create_user( array(
     127            'user_login' => 'foo bar baz',
     128            'user_nicename' => 'foo-bar-baz',
     129        ) );
     130
     131        $u2 = $this->create_user( array(
     132            'user_login' => 'foo.bar.baz',
     133            'user_nicename' => 'foo-bar-baz',
     134        ) );
     135
     136        $this->assertEquals( 'foo-bar-baz', bp_activity_get_user_mentionname( $u1 ) );
     137        $this->assertEquals( 'foo.bar.baz', bp_activity_get_user_mentionname( $u2 ) );
     138
     139        remove_filter( 'bp_is_username_compatibility_mode', '__return_true' );
     140    }
     141
     142    /**
     143     * @group bp_activity_get_userid_from_mentionname
     144     */
     145    public function test_bp_activity_get_userid_from_mentionname_compatibilitymode_off() {
     146        add_filter( 'bp_is_username_compatibility_mode', '__return_false' );
     147
     148        $u = $this->create_user( array(
     149            'user_login' => 'foo bar baz',
     150            'user_nicename' => 'foo-bar-baz',
     151        ) );
     152
     153        $this->assertEquals( $u, bp_activity_get_userid_from_mentionname( 'foo-bar-baz' ) );
     154
     155        remove_filter( 'bp_is_username_compatibility_mode', '__return_false' );
     156    }
     157
     158    /**
     159     * @group bp_activity_get_userid_from_mentionname
     160     */
     161    public function test_bp_activity_get_userid_from_mentionname_compatibilitymode_on() {
     162        add_filter( 'bp_is_username_compatibility_mode', '__return_true' );
     163
     164        // all spaces are hyphens
     165        $u1 = $this->create_user( array(
     166            'user_login' => 'foo bar baz',
     167            'user_nicename' => 'foobarbaz',
     168        ) );
     169
     170        // no spaces are hyphens
     171        $u2 = $this->create_user( array(
     172            'user_login' => 'foo-bar-baz-1',
     173            'user_nicename' => 'foobarbaz-1',
     174        ) );
     175
     176        // some spaces are hyphens
     177        $u3 = $this->create_user( array(
     178            'user_login' => 'foo bar-baz 2',
     179            'user_nicename' => 'foobarbaz-2',
     180        ) );
     181
     182        $u4 = $this->create_user( array(
     183            'user_login' => 'foo.bar.baz',
     184            'user_nicename' => 'foo-bar-baz',
     185        ) );
     186
     187        $this->assertEquals( $u1, bp_activity_get_userid_from_mentionname( 'foo-bar-baz' ) );
     188        $this->assertEquals( $u2, bp_activity_get_userid_from_mentionname( 'foo-bar-baz-1' ) );
     189        $this->assertEquals( $u3, bp_activity_get_userid_from_mentionname( 'foo-bar-baz-2' ) );
     190        $this->assertEquals( $u4, bp_activity_get_userid_from_mentionname( 'foo.bar.baz' ) );
     191
     192        remove_filter( 'bp_is_username_compatibility_mode', '__return_true' );
     193    }
     194
    103195}
Note: See TracChangeset for help on using the changeset viewer.