• Resolved Debugger

    (@retrobeatcom)


    Hello there,
    I would like to bring your attention to few details when it comes to username generation.

    First of all, I have a website with +8000 registered users through Nextend Facebook Connect.
    When I checked for usernames that consist of facebook + ID I got +400 results (~5% of the users). For me, this is a problem, and here’s why:
    – I’ve set a prefix for new registrations with facebook connect, but these usernames do not follow the prefix convention.
    – It’s really not good looking username.

    So, let’s assume few things before we start:
    – The prefix is set to “fb-”
    – The user’s First, Last and Full name are all in non-latin language (for example Chinese, Cyrillic etc.)
    First name: “Иван”
    Last Name: “Иванов”

    Your code:

    
        $username = strtolower($user_profile['first_name'] . $user_profile['last_name']);
        // We will end up with $username = "ИванИванов"
        // As strtolower() is not working with multibyte character encoding it will do nothing in this case
        
        $sanitized_user_login = sanitize_user($new_fb_settings['fb_user_prefix'] . $username);
        // Now we have $sanitized_user_login = "fb-ИванИванов" - so far so goog
        
        if (!validate_username($sanitized_user_login)) {
            // Validity check will fail because strict flag was not passed as a second argument to sanitize_user in the previous line, 
            // but validate_username check against sanitize_user($name, true)
            
            $sanitized_user_login = sanitize_user('facebook' . $user_profile['id']);
            // So at this point we already have a not so good looking username $sanitized_user_login = "faceookID"
        }
            
        // more code
    

    I can offer a fairly simple solution, following these recommendations:

    – No matter what always include the prefix. – That’s a must.
    – Add additional settings for username_fallback – then check if its set, and if unset, sure go ahead and use the “facebook”. ID approach.
    – Remove the spaces, it’s really not a good idea to have spaces in login names.
    – Maybe separate the first and last name with an underscore – just an idea, it will be more readable and user-friendly.

    And here’s the refactored code:

    
        // Demo setup:
        $user_profile['first_name'] = "Иван";
        $user_profile['last_name'] = "Иванов";
    
        $new_fb_settings['fb_user_prefix'] = "fb-";
        $new_fb_settings['fb_user_fallback'] = "member";
    
        if (!isset($new_fb_settings['fb_user_prefix'])) {
            $new_fb_settings['fb_user_prefix'] = 'facebook-';
        }
    	
    	// Code
        $username = strtolower($user_profile['first_name'] . $user_profile['last_name']);
    
        $sanitized_user_login = sanitize_user($new_fb_settings['fb_user_prefix'] . $username, true); // do not forget to pass the strict flag (true)
    
        // Let's replace the spaces and double underscores with underscore. Its much readable this way.
        $sanitized_user_login = strtolower(preg_replace("/[\s_]+/", "_", $sanitized_user_login));
    
        // If username consist of 100% non-latin characters, at this point we will end up with the prefix only.
        if (trim($sanitized_user_login) === $new_fb_settings['fb_user_prefix']) {
            // Lets use the fallback name
            // It doesn't really matter if the fallback name is blank/set or not.
            $sanitized_user_login = $new_fb_settings['fb_user_prefix'] . $new_fb_settings['fb_user_fallback'];
        }
    
        $defaul_user_name = $sanitized_user_login;
        $i = 1;
        while (username_exists($sanitized_user_login)) {
            $sanitized_user_login = $defaul_user_name . $i;
            $i++;
        }
    
        // At this point, we will have fb-member as username for non-latin usernames
        // If fb-member is existing: fb-member1 etc.
        // But most importantly, we will cover edge cases like this: "Michael O'Reilly"
        // Instead ending up with something like facebook12345678 we will end up with fb-michael_oreilly, fb-michael_oreilly1, fb-michael_oreilly2 etc...
    

    Have a great day!

    PS. Here’s a gist with code highlighting:
    https://gist.github.com/vdonchev/8c98da9bfcce7c13308f6021b48ed02f

    • This topic was modified 6 years, 9 months ago by Debugger.
    • This topic was modified 6 years, 9 months ago by Debugger.
    • This topic was modified 6 years, 9 months ago by Debugger.
    • This topic was modified 6 years, 9 months ago by Debugger.
Viewing 6 replies - 1 through 6 (of 6 total)
  • Plugin Author Nextendweb

    (@nextendweb)

    Hi @retrobeatcom,
    thank you for your suggestion. Give me a few hours to check your code and then we can continue the discussion.

    Plugin Author Nextendweb

    (@nextendweb)

    @retrobeatcom,
    your code based on Version 2, but as we moving forward to version 3 I will add code that is related to the new version. (The process is similar)

    To summarize your suggestion:
    You would like to introduce a new field called user_fallback. In your case it contains ‘member’. When WordPress validate_username rejects the username based on the first and last name, you would like to use $user_prefix . $user_fallback as the username. Right?

    By default we use the following fallback: $user_prefix . $userIDatFacebook then we check the existence of this username and we adding incremented number to it while we found a free user name:

    while (username_exists($sanitized_user_login)) {
        $sanitized_user_login = $default_user_name . $i;
        $i++;
    }

    In your example the problem comes when you have 500 user who have Cyrillic character in their name. Then for the 501th user WordPress needs to check in the databse 500 possible good usernames which are not free:

    1. fb-member-1 – taken
    2. fb-member-2 – taken
    3. fb-member-3 – taken
    4. fb-member-… – taken
    5. fb-member-499 – taken
    6. fb-member-500 – taken
    7. fb-member-501 – free

    Possible future solution in our plugin
    What we could force users to enter username when we are not able to generate one based on the first and last name.

    What you could do
    If you are running a site where most people user cyrillic alphabet, you could enable the usage of cyrillic characters in usernames:

    function acu_sanitize_user($username, $raw_username, $strict) {
        $username = wp_strip_all_tags( $raw_username );
        $username = remove_accents( $username );
        // Kill octets
        $username = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $username );
        $username = preg_replace( '/&.+?;/', '', $username ); // Kill entities
    
        // If strict, reduce to ASCII and Cyrillic characters for max portability.
        if ( $strict )
            $username = preg_replace( '|[^a-zD°-??а-я0-9 _.\-@]|iu', '', $username );
    
        $username = trim( $username );
        // Consolidate contiguous whitespace
        $username = preg_replace( '|\s+|', ' ', $username );
        return $username;
    }
    add_filter('sanitize_user', 'acu_sanitize_user', 10, 3);

    Please let me know what your thoughts!

    Thread Starter Debugger

    (@retrobeatcom)

    Hello,
    thanks for the quick reply!

    On your first question, yes that’s what I propose. The possibility of setting custom default username in the admin area. And also, always to include the prefix. In my case, I depend on that prefix to execute some custom actions for these users. But anyway, it will be much more consistent if all users have it, right?

    The problem really occurs when there are too many users with the default name in the DB. $username . $index in this case $index can always be replaced with the provider ID or simply with build in rand(). It will do the trick in both cases.

    If a feature is introduced, in my opinion, it should prepend the prefix before inserting the username to the db.

    Thanks for the suggestion about the Cyrillic usernames, but I prefer to keep all usernames in Latin. It simplifies searching and adding custom scripts that manage these users.

    Thread Starter Debugger

    (@retrobeatcom)

    PS. I Just checked the code located in \includes\providers.php

    
            $username = strtolower($first_name . $last_name);
            if (empty($username)) {
                $username = strtolower($name);
            }
            if (empty($username)) {
                $username = str_replace(array(
                    '@',
                    '.'
                ), '', strtolower($email));
            }
    

    The part where you parse the username from the email is not a good idea. The email is something private and using it to build a login name will make it publicly available to all others, that can see your username.

    Plugin Author Nextendweb

    (@nextendweb)

    @retrobeatcom

    Here is a possible solution based on our discussion: https://i.imgur.com/U6jGaa1.png
    (If you have better name or description for this field, please share with us.)

    Site owner can enter a prefix which always used and can enter a fallback which will be concatenated to $prefix.$fallback

    • Username prefix on register -> value: fb
    • Username fallback on register -> value: member

    And we make fbmember in PHP when needed

    Another approach field label idea
    Site owner can enter different prefixes. Maybe it would be easier to understand and gives more freedom.

    • Username prefix on register -> value: fb
    • Fallback username prefix on register -> value: fbmember

    Which one do you prefer?

    Also I do agree with you with the email suggestion, I will remove that in the next version.

    The provider prefix is always added to the user on registration expect in the 'nsl_' . $this->getId() . '_register_user_data' filter which can modify the username. Currently it used in the Pro Addon: when the always ask username enabled, we set the entered username here. I do not think that it would be good to add prefix to the user’s choosen username as that makes it harder to remember. But I think in your case it does not matter as you do not plan to use it.

    Proposed code:

    $username = strtolower($first_name . $last_name);
    if (empty($username)) {
        $username = strtolower($name);
    }
    
    $prefix = $this->settings->get('user_prefix');
    
    $sanitized_user_login = sanitize_user($prefix . $username);
    if (empty($username) || !validate_username($sanitized_user_login)) {
        $sanitized_user_login = sanitize_user($prefix . $this->settings->get('user_fallback') . $providerID);
    }
    $default_user_name = $sanitized_user_login;
    
    $i = 1;
    while (username_exists($sanitized_user_login)) {
        $sanitized_user_login = $default_user_name . $i;
        $i++;
    }
    
    $userData = array(
        'email'    => $email,
        'username' => $sanitized_user_login
    );
    
    $userData = apply_filters('nsl_' . $this->getId() . '_register_user_data', $userData);
    • This reply was modified 6 years, 9 months ago by Nextendweb.
    Plugin Author Nextendweb

    (@nextendweb)

    This feature and improvements will be released in the next version.

Viewing 6 replies - 1 through 6 (of 6 total)
  • The topic ‘Suggestion for improving the plugin’ is closed to new replies.