Skip to content

Instantly share code, notes, and snippets.

@GaryJones
Created July 9, 2018 20:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save GaryJones/d1f138638b1832c0d4e708bcbd27a021 to your computer and use it in GitHub Desktop.
Save GaryJones/d1f138638b1832c0d4e708bcbd27a021 to your computer and use it in GitHub Desktop.
WordPress plugin to store custom and built-in user fields on a per-site basis for WP multisite. Good for multilingual setups. Customise to your needs.
<?php
/**
* Per-Site User Fields
*
* @package GaryJones\PerSiteUserfields
* @author Gary Jones
* @copyright 2018 Gary Jones, Gamajo
* @license GPL-2.0-or-later
*
* @wordpress-plugin
* Plugin Name: Per-Site User Fields
* Description: Handle user fields to work on a per-site basis.
* Version: 1.0.0
* Author: Gary Jones, Giuseppe Mazzapica, and Sami Keijonen
* Author URI: https://garyjones.io
* Text Domain: per-site-user-fields
* License: GPL-2.0-or-later
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Requires PHP: 7.1
* Requires WP: 4.7
*/
declare( strict_types = 1 );
namespace GaryJones\PerSiteUserFields;
// Exit if accessed directly
if ( ! \defined( 'ABSPATH' ) ) {
exit;
}
// 0. Define which fields we're targetting. Anything not already being sanitized elsewhere can have a callback here.
function per_site_user_settings() {
return [
'job_title' => [ // Custom added by this plugin.
'sanitization' => function( $value ) {
return sanitize_text_field( $value );
},
],
'description' => [], // Built-in field (bio).
'intro_text' => [], // Added by Genesis.
];
}
// 1. First, handle the custom field[s]. These are saved and retrieved as global user settings (user_meta).
\add_action( 'show_user_profile', __NAMESPACE__ . '\\add_user_field' );
\add_action( 'edit_user_profile', __NAMESPACE__ . '\\add_user_field' );
/**
* Add field to user profile.
*
* For this example, its just one field - job title, which we'll use on an author byline via code in our theme.
*
* @since 1.0.0
*/
function add_user_field( $user ) {
$job_title_field = 'job_title';
$job_title = \get_user_meta( $user->ID, $job_title_field, true );
?>
<h3><?php \esc_html_e( 'Bylines', 'per-site-user-fields' ); ?></h3>
<table class="form-table">
<tbody>
<tr>
<th scope="row"><label for="<?php echo esc_attr( $job_title_field ); ?>"><?php \esc_html_e( 'Job Title', 'per-site-user-fields' ); ?></label></th>
<td>
<input type="text" name="<?php echo esc_attr( $job_title_field ); ?>" id="<?php echo \esc_attr( $job_title_field ); ?>" value="<?php if ( ! empty( $job_title ) ) esc_attr_e( $job_title ); ?>" class="medium-text" />
</td>
</tr>
<tbody>
</table>
<?php
}
\add_action( 'personal_options_update', __NAMESPACE__ . '\\save_user_field' );
\add_action( 'edit_user_profile_update', __NAMESPACE__ . '\\save_user_field' );
/**
* Save field for user profile.
*
* Even though we're going to change _how_ we save the data, we still need to kickstart the saving process,
* so WP is aware of the field.
*
* @since 1.0.0
*/
function save_user_field( $user_id ) {
// Bail if we don't have permission to edit user.
if ( ! \current_user_can( 'edit_user', $user_id ) ) {
return false;
}
foreach ( per_site_user_settings() as $setting => $config ) {
if ( array_key_exists( 'sanitization', $config ) ) { // Is a custom field.
$value = isset( $_POST[ $setting ] ) ? call_user_func( $config['sanitization'], $_POST[ $setting ] ) : null;
} else {
// Default to text field sanitization.
$value = isset( $_POST[ $setting ] ) ? \sanitize_text_field( $_POST[ $setting ] ) : null;
}
// Save value as user meta - this makes it a standard global setting,
// but we overwrite this later to be per-site.
\update_user_meta( $user_id, $setting, $value );
}
}
// 2. Now adjust the targetted built-in and custom fields so they are handled on a per-site basis.
\add_filter(
'update_user_metadata', // update_{$meta_type}_metadata hook.
function ( $check, $user_id, $meta_key, $meta_value ) {
if ( should_intercept_user_meta( $meta_key ) ) {
// Remove site-specific value if it's empty, so that it falls
// back to network value.
if ( null === $meta_value || '' === $meta_value ) {
\delete_user_option( $user_id, $meta_key );
return true;
}
\update_user_option( $user_id, $meta_key, $meta_value );
// Return a non-null value, so that the rest of update_user_meta()
// does NOT run, and the unprefixed meta key doesn't get updated as well.
return true;
}
// Effectively return null, so that non-primary sites, or other fields
// we're not interested in, will fully run update_user_meta().
return $check;
},
10,
4
);
\add_filter( 'get_user_metadata', __NAMESPACE__ . '\\get_user_metadata', 10, 3 ); // get_{$meta_type}_metadata hook.
function get_user_metadata( $return, $user, $meta_key ) {
if ( should_intercept_user_meta( $meta_key ) ) {
// get_user_option() can call get_user_meta(), which would call this filter callback recursively,
// so temporarily disable this filter, then add it back afterwards.
\remove_filter( 'get_user_metadata', __NAMESPACE__ . '\\get_user_metadata', 10 );
$return = \get_user_option( $meta_key, $user ) ?: '';
\add_filter( 'get_user_metadata', __NAMESPACE__ . '\\get_user_metadata', 10, 3 );
}
return $return;
}
function should_intercept_user_meta( $meta_key ) {
return \in_array( $meta_key, per_site_user_settings(), true )
&& ! \is_network_admin()
&& \get_current_blog_id() !== \get_main_site_id();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment