Using Heroku, need to have non-gravatar avatars, but not stored locally

The function that does all the heavy lifting for avatars is get_avatar. It’s a pluggable function, meaning you can replace it with a plugin. Get avatar also has a handy filter you can use to override things programatically. So, a few options: (1) completely replace get_avatar which might have unintended side effects and break things like avatars in comments, (2) Role your own function just for author avatars, (3) Filter get_avatar for authors and leave it alone for everyone else.

Either way, it’s starts with adding a field to user profiles so you can specify an avatar to do that.

Let’s start by wrapping everything in a class.

<?php
WPSE67312_Avatars::init();

class WPSE67312_Avatars
{
    // we'll need a nonce key
    const NONCE = 'wpse67312_nonce';

    // The meta key where the avatar URL is stored.
    const META_KEY = 'wpse67312_avatar';

    /***** Singleton pattern to add hooks and such *****/

    private static $ins = null;

    public static function init()
    {
        add_action('plugins_loaded', array(__CLASS__, 'instance'));
    }

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }
}

You’ll need to hook into show_user_profile and edit_user_profile. The first shows fields on your own profile (and authors will be able to see the field as well) and edit_user_profile will show the field when you’re editing other users.

This also adds a little helper function to fetch the user avatar url. Pretty self explanatory: stick in a nonce, add a header, and echo out the field.

<?php
class WPSE67312_Avatars
{
    // snip snip

    /**
     * Helper to fetch avatar urls.
     *
     * @param   int $user_id The user ID for which to fetch the avatar
     * @uses    get_user_meta To fetch the avatar URL.
     * @return  string The avatar url
     */
    public static function get_avatar($user_id)
    {
        return get_user_meta($user_id, self::META_KEY, true);
    }

    /**
     * Constructor.  Where all the real actions get added.
     *
     * @uses    add_action
     * @uses    add_filter
     * @return  void
     */
    protected function __construct()
    {
        add_action('edit_user_profile', array($this, 'field'));
        add_action('show_user_profile', array($this, 'field'));
    }

    public function field($user)
    {
        // Might want to hide this from authors?
        // if(!current_user_can('manage_options'))
        //      return;

        wp_nonce_field(self::NONCE . $user->ID, self::NONCE, false);
        echo '<h4>', esc_html__('Avatar URL', 'wpse'), '</h4>';
        printf(
            '<input type="text" class="regular-text" id="https://wordpress.stackexchange.com/questions/67312/%1$s" name="https://wordpress.stackexchange.com/questions/67312/%1$s" value="%2$s" />',
            esc_attr(self::META_KEY),
            esc_attr(self::get_avatar($user->ID))
        );
    }
}

Adding the field doesn’t actually take care of saving it, however. You need to hook into edit_user_profile_update (fires when saving profiles other than your own) and person_options_update (fires when you save your own profile). In the hooked function we check our nonce, check to make sure the current user can edit, and save the data with update_user_meta or delete it with delete_user_meta.

<?php
class WPSE67312_Avatars
{
    // snip snip

    /**
     * Constructor.  Where all the real actions get added.
     *
     * @uses    add_action
     * @uses    add_filter
     * @return  void
     */
    protected function __construct()
    {
        add_action('edit_user_profile', array($this, 'field'));
        add_action('show_user_profile', array($this, 'field'));
        add_action('edit_user_profile_update', array($this, 'save'));
        add_action('personal_options_update', array($this, 'save'));
    }

    // snip snip

    public function save($user_id)
    {
        if(
            !isset($_POST[self::NONCE]) ||
            !wp_verify_nonce($_POST[self::NONCE], self::NONCE . $user_id)
        ) return; // nonce is no good, bail

        if(!current_user_can('edit_user', $user_id))
            return; // current user can't edit this user, bail

        if(!empty($_POST[self::META_KEY]))
        {
            // we have data! save it!
            update_user_meta(
                $user_id, 
                self::META_KEY,
                esc_url_raw($_POST[self::META_KEY])
            );
        }
        else
        {
            // empty field, delete the old value
            delete_user_meta($user_id, self::META_KEY);
        }
    }
}

You could use the above in your template directly (somewhere in the loop):

<?php
if($avt = WPSEWPSE67312_Avatars::get_avatar(get_the_author_meta('ID')))
{
    printf(
        '<img src="https://wordpress.stackexchange.com/questions/67312/%1s$" alt="%2$s" title="%4$s" />', 
        esc_url($avt), 
        esc_attr(get_the_author_meta('display_name'))
    );
}

Which is kind of messy. Or we can try filtering the avatar with get_avatar:

<?php
class WPSE67312_Avatars
{
    // snip snip

    /**
     * Constructor.  Where all the real actions get added.
     *
     * @uses    add_action
     * @uses    add_filter
     * @return  void
     */
    protected function __construct()
    {
        add_action('edit_user_profile', array($this, 'field'));
        add_action('show_user_profile', array($this, 'field'));
        add_action('edit_user_profile_update', array($this, 'save'));
        add_action('personal_options_update', array($this, 'save'));
        add_filter('get_avatar', array($this, 'filter_avatar'), 10, 5);
    }

    // snip snip

    public function filter_avatar($avatar, $id_or_email, $size, $default, $alt)
    {
        // do the dance to get a user
        $id = false;
        if(is_numeric($id_or_email))
        {
            $id = $id_or_email;
        }
        elseif(is_object($id_or_email))
        {
            if(!empty($id_or_email->user_id))
                $id = $id_or_email->user_id; // comment
        }
        elseif($user = get_user_by('email', $id_or_email))
        {
            $id = $user->ID;
        }

        if($id && ($avt = self::get_avatar($id)))
        {
            $avatar = sprintf(
                '<img src="https://wordpress.stackexchange.com/questions/67312/%1$s" alt="%2$s" title="%2$s" width="%3$s" />',
                esc_url($avt),
                esc_attr($alt),
                esc_attr($size)
            );
        }

        return $avatar;
    }
}

Theoretically the above should leave normal avatars alone, and use your custom avatars when they’re available. It probably needs to be tested more than I was able to.

Here is all that, wrapped in a plugin.

Leave a Comment