User Contact Methods Generator
Add fields to the Contact Info table on the user profile screen, with sanitisation, validation, REST exposure and an output helper.
Your fields
4 fields
Mastodonmastodon
Required
In REST
LinkedInlinkedin
Required
In REST
GitHubgithub
Required
In REST
Press emailpress_email
Required
In REST
What ships alongside
Validate on save — hooks user_profile_update_errors so bad URLs, bad emails and empty required fields are rejected before core writes them.
Core methods
3 removed
<?php // Generated with WP CodeKit — powered by GrowQuest (https://growquest.io). /** * Plugin Name: acme contact methods * Description: Adds 4 contact fields to the user profile screen. * Version: 1.0.0 * Requires PHP: 7.4 * Text Domain: acme */ defined( 'ABSPATH' ) || exit; /** * Add the fields this site needs to the Contact Info table. * * @param array $methods Method key => label. * @param WP_User|null $user The user being edited, null on the list screen. * @return array */ function acme_user_contact_methods( $methods, $user ) { unset( $methods['aim'] ); unset( $methods['yim'] ); unset( $methods['jabber'] ); $methods = array_merge( $methods, array( 'acme_mastodon' => __( 'Mastodon', 'acme' ), 'acme_linkedin' => __( 'LinkedIn', 'acme' ), 'acme_github' => __( 'GitHub', 'acme' ), 'acme_press_email' => __( 'Press email', 'acme' ), ) ); return $methods; } add_filter( 'user_contact_methods', 'acme_user_contact_methods', 10, 2 ); /** * The fields worth checking before the profile saves. * * @return array */ function acme_contact_field_rules() { return array( 'acme_mastodon' => array( 'label' => __( 'Mastodon', 'acme' ), 'type' => 'url', 'required' => false, ), 'acme_linkedin' => array( 'label' => __( 'LinkedIn', 'acme' ), 'type' => 'url', 'required' => false, ), 'acme_press_email' => array( 'label' => __( 'Press email', 'acme' ), 'type' => 'email', 'required' => false, ), ); } /** * Reject bad values before core writes them to usermeta. * * Core sanitises contact methods with sanitize_text_field() and nothing more, * so this is the only chance to say no. * * @param WP_Error $errors Error collector, passed by reference. * @param bool $update Whether this is an existing user. * @param stdClass $user The user object about to be written. */ function acme_validate_contact_methods( $errors, $update, $user ) { foreach ( acme_contact_field_rules() as $key => $rule ) { $value = isset( $_POST[ $key ] ) ? trim( sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) ) : ''; if ( '' === $value ) { if ( ! empty( $rule['required'] ) ) { $errors->add( $key . '_empty', sprintf( /* translators: %s: field label */ __( '<strong>Error</strong>: %s cannot be empty.', 'acme' ), $rule['label'] ) ); } continue; } if ( 'url' === $rule['type'] && ! wp_http_validate_url( $value ) ) { $errors->add( $key . '_invalid', sprintf( /* translators: %s: field label */ __( '<strong>Error</strong>: %s must be a full URL, starting with https://.', 'acme' ), $rule['label'] ) ); } if ( 'email' === $rule['type'] && ! is_email( $value ) ) { $errors->add( $key . '_invalid', sprintf( /* translators: %s: field label */ __( '<strong>Error</strong>: %s is not a valid email address.', 'acme' ), $rule['label'] ) ); } } } add_action( 'user_profile_update_errors', 'acme_validate_contact_methods', 10, 3 ); /** * Expose the fields on the user REST resource. * * Without this the values exist in usermeta but never appear in /wp/v2/users. */ function acme_register_contact_meta() { register_meta( 'user', 'acme_mastodon', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'description' => __( 'Mastodon', 'acme' ), 'sanitize_callback' => 'esc_url_raw', 'auth_callback' => function () { return current_user_can( 'edit_users' ); }, ) ); register_meta( 'user', 'acme_linkedin', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'description' => __( 'LinkedIn', 'acme' ), 'sanitize_callback' => 'esc_url_raw', 'auth_callback' => function () { return current_user_can( 'edit_users' ); }, ) ); register_meta( 'user', 'acme_github', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'description' => __( 'GitHub', 'acme' ), 'sanitize_callback' => 'sanitize_text_field', 'auth_callback' => function () { return current_user_can( 'edit_users' ); }, ) ); } add_action( 'init', 'acme_register_contact_meta' ); /** * The author contact links, ready to print. * * @param int $user_id User id. Defaults to the current post author. * @return string Escaped markup, or an empty string when nothing is filled in. */ function acme_contact_links( $user_id = 0 ) { $user_id = $user_id ? (int) $user_id : get_the_author_meta( 'ID' ); $fields = array( 'acme_mastodon' => array( 'label' => __( 'Mastodon', 'acme' ), 'escape' => 'esc_url', 'link' => true, ), 'acme_linkedin' => array( 'label' => __( 'LinkedIn', 'acme' ), 'escape' => 'esc_url', 'link' => true, ), 'acme_github' => array( 'label' => __( 'GitHub', 'acme' ), 'escape' => 'esc_html', 'link' => false, ), 'acme_press_email' => array( 'label' => __( 'Press email', 'acme' ), 'escape' => 'esc_html', 'link' => true, ), ); $items = array(); foreach ( $fields as $key => $field ) { $value = get_user_meta( $user_id, $key, true ); if ( ! $value ) { continue; } $label = esc_html( $field['label'] ); if ( $field['link'] ) { $href = ( false === strpos( $value, '@' ) || false !== strpos( $value, '://' ) ) ? esc_url( $value ) : 'mailto:' . esc_attr( sanitize_email( $value ) ); $items[] = sprintf( '<li><a href="%1$s" rel="me nofollow">%2$s</a></li>', $href, $label ); continue; } $items[] = sprintf( '<li>%1$s: %2$s</li>', $label, call_user_func( $field['escape'], $value ) ); } if ( ! $items ) { return ''; } return '<ul class="acme-contact">' . implode( '', $items ) . '</ul>'; }