What are ‘authors’ in WordPress, and is there an author ‘metabox class’ available anywhere?

Authors are rather simple content objects in WordPress. They have roles and capabilities, some basic meta data and their own edit screens. That’s it pretty much. If you want to add a custom taxonomy to authors you have to mirror each user with a (hidden) custom post type because the author object doesn’t support taxonomies …

Anyway … for meta boxes I have written a simple class. I hope it helps to understand how things work and where to hook in.

class T5_User_Profile_Addon

<?php
/**
 * Adds a form field to the edit profile page.
 *
 * @author Thomas Scholz http://toscho.de
 * @version 1.2
 * @license GPL
 *
 */
class T5_User_Profile_Addon
{

    public $settings = array (
    /* The name attribute. */
        'name'        => ''
    ,   'label'       => ''
    ,   'description' => ''

    /* You may use the following placeholder:
     * %name%        - name attribute
     * %label%       - label text
     * %description% - additional text
     * To use more placeholders, extend markup_filter().
     */
    ,   'markup'      => ''
    /* If both are not FALSE, they will replace the 'markup', and a
     * table will be created. Uses the same placeholders as 'markup'.
     */
    ,   'th'          => FALSE
    ,   'td'          => FALSE
    /* Capabilities to show and edit the field.
     * Useful, if want to add a field that only administrators or editors
     * may edit or view.
     */
    ,   'cap_show'    => 'read'
    ,   'cap_save'    => 'edit_user'
    );

    /**
     * Constructor
     *
     * @param array $args See settings. 'name' and 'markup' required.
     */
    public function __construct( $args )
    {
        $this->settings = array_merge( $this->settings, $args );

        // The id attribute should be different to name, otherwise it doesn’t
        // work in Opera.
        empty ( $this->settings['id'] )
            and $this->settings['id'] = $this->settings['name'] . '_id';

        FALSE !== $this->settings['th'] and FALSE !== $this->settings['td']
            and $this->settings['markup'] = '<table class="form-table"><tr><th>'
                . $this->settings['th'] . '</th><td>' . $this->settings['td']
                . '</td></tr></table>';

        add_action( 'show_user_profile',        array ( $this, 'show' ) );
        add_action( 'edit_user_profile',        array ( $this, 'show' ) );
        add_action( 'personal_options_update',  array ( $this, 'save' ) );
        add_action( 'edit_user_profile_update', array ( $this, 'save' ) );
    }

    /**
     * Prints the form.
     *
     * @param  object $user
     * @return void
     */
    public function show( $user )
    {
        if ( ! current_user_can( $this->settings['cap_show'], $user->ID ) )
        {
            return;
        }

        $label   = "<label for="{$this->settings["id']}'>{$this->settings['label']}</label>";
        $markup  = strtr( $this->settings['markup'],
            array (
                '%name%'        => $this->settings['name']
            ,   '%id%'          => $this->settings['id']
            ,   '%label%'       => $label
            ,   '%description%' => $this->settings['description']
            )
        );
        $old_val = trim( get_the_author_meta( $this->settings['name'], $user->ID ) );
        $markup  = $this->markup_filter( $markup, $old_val );

        print $markup;
    }

    /**
     * Saves the data.
     *
     * @param  int $user_id
     * @return void
     */
    public function save( $user_id )
    {
        if ( ! current_user_can( $this->settings['cap_save'], $user_id ) )
        {
            return;
        }

        $input = empty ( $_POST[ $this->settings['name'] ] )
            ? '' : $_POST[ $this->settings['name'] ];
        $input = $this->prepare_input( $input );

        update_user_meta( $user_id, $this->settings['name'], $input );
    }

    /**
     * Prepares the user input. For extensions.
     *
     * @param  string $input
     * @return string
     */
    public function prepare_input( $input )
    {
        return $input;
    }

    /**
     * Prepares the form markup.
     *
     * @param  string $markup
     * @param  string $old_val
     * @return string
     */
    public function markup_filter( $markup, $old_val )
    {
        $old_val = htmlspecialchars( $old_val, ENT_QUOTES, 'utf-8', FALSE );
        return str_replace( '%content%', $old_val, $markup );
    }
}

Usage

<?php
add_action( 'init', 'init_profile_addons' );
function init_profile_addons()
{
    $GLOBALS['extended_author_text'] = new T5_User_Profile_Addon(
        array (
            'name' => 'extended_author_text'
        ,   'label' => 'Second text field'
        ,   'description' => '<p>This text will be shown at the top of your author archive. You may use HTML.</p>'
        ,   'markup' => '<table class="form-table"><tr><th>%label%<br /><br />%description%</th>
            <td><textarea name="%name%" id="%id%" rows="10" cols="30">%content%</textarea></td></tr></table>'
        )
    );
}

Example: a checkbox

In some cases you may want to extend the class to change some behavior. Here I built a checkbox:

/**
 * Template for a simple checkbox.
 */
class T5_User_Profile_Checkbox extends T5_User_Profile_Addon
{
    public function prepare_input( $input )
    {   // Convert the checkbox value to integer
        return '' == trim( $input ) ? 0 : 1;
    }

    public function markup_filter( $markup, $old_value )
    {   // Preselect the checkbox if necessary.
        $checked = 1 == $old_value ? ' checked="checked"' : '';
        return str_replace( '%checked%', $checked, $markup );
    }
}

My primitive template system may not be the best approach. There is no user friendly error handling. I just needed something that works and is easy to understand. 🙂