Instantly share code, notes, and snippets.
Created
July 30, 2026 14:13
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save justintadlock/b9d84f97d8e961a5ac555234ab7e124c to your computer and use it in GitHub Desktop.
Term Author for WordPress
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: Term Author | |
| * Plugin URI: https://example.com/term-author | |
| * Description: Records the user who created a taxonomy term in the `term_author` term meta key, and lets editors view or reassign it. | |
| * Version: 1.0.0 | |
| * Requires at least: 5.5 | |
| * Requires PHP: 7.2 | |
| * Author: You | |
| * License: GPL-2.0-or-later | |
| * License URI: https://www.gnu.org/licenses/gpl-2.0.html | |
| * Text Domain: term-author | |
| * | |
| * @package TermAuthor | |
| */ | |
| defined( 'ABSPATH' ) || exit; | |
| /** | |
| * The meta key used to store the author's user ID. | |
| */ | |
| define( 'TERM_AUTHOR_META_KEY', 'term_author' ); | |
| /* ------------------------------------------------------------------------- | |
| * Meta registration | |
| * ---------------------------------------------------------------------- */ | |
| /** | |
| * Register the meta key for every taxonomy so it is typed, sanitized and | |
| * available over the REST API. | |
| * | |
| * Passing an empty taxonomy registers the key for all taxonomies. | |
| * | |
| * @return void | |
| */ | |
| function term_author_register_meta() { | |
| register_term_meta( | |
| '', | |
| TERM_AUTHOR_META_KEY, | |
| array( | |
| 'type' => 'integer', | |
| 'description' => __( 'ID of the user who created the term.', 'term-author' ), | |
| 'single' => true, | |
| 'default' => 0, | |
| 'show_in_rest' => true, | |
| 'sanitize_callback' => 'absint', | |
| 'auth_callback' => 'term_author_meta_auth_callback', | |
| ) | |
| ); | |
| } | |
| add_action( 'init', 'term_author_register_meta' ); | |
| /** | |
| * Only users who can edit the term may write the meta value. | |
| * | |
| * @param bool $allowed Whether the user can add/edit the meta. | |
| * @param string $meta_key The meta key. | |
| * @param int $term_id The term ID. | |
| * @param int $user_id The user ID being checked. | |
| * @return bool | |
| */ | |
| function term_author_meta_auth_callback( $allowed, $meta_key, $term_id, $user_id ) { | |
| return user_can( $user_id, 'edit_term', $term_id ); | |
| } | |
| /* ------------------------------------------------------------------------- | |
| * Capture the author on creation | |
| * ---------------------------------------------------------------------- */ | |
| /** | |
| * Store the current user as the author when a term is created. | |
| * | |
| * Nothing is stored for terms created outside a user session (WP-CLI, cron, | |
| * unauthenticated imports) so the absence of the key stays meaningful. | |
| * | |
| * @param int $term_id Term ID. | |
| * @param int $tt_id Term taxonomy ID. | |
| * @param string $taxonomy Taxonomy slug. | |
| * @return void | |
| */ | |
| function term_author_set_on_create( $term_id, $tt_id, $taxonomy ) { | |
| /** | |
| * Filters the user ID recorded as the term author. | |
| * | |
| * Return 0 to skip storing an author entirely — useful during imports | |
| * where you want to assign authorship yourself. | |
| * | |
| * @param int $user_id Defaults to the current user. | |
| * @param int $term_id Term ID. | |
| * @param string $taxonomy Taxonomy slug. | |
| */ | |
| $user_id = (int) apply_filters( 'term_author_user_id', get_current_user_id(), $term_id, $taxonomy ); | |
| if ( $user_id > 0 ) { | |
| add_term_meta( $term_id, TERM_AUTHOR_META_KEY, $user_id, true ); | |
| } | |
| } | |
| add_action( 'created_term', 'term_author_set_on_create', 10, 3 ); | |
| /* ------------------------------------------------------------------------- | |
| * Public helpers | |
| * ---------------------------------------------------------------------- */ | |
| /** | |
| * Get the author ID for a term. | |
| * | |
| * @param int|WP_Term $term Term ID or object. | |
| * @param string $taxonomy Optional taxonomy, when passing an ID. | |
| * @return int User ID, or 0 if none is recorded. | |
| */ | |
| function term_author_get_id( $term, $taxonomy = '' ) { | |
| $term = get_term( $term, $taxonomy ); | |
| if ( ! $term instanceof WP_Term ) { | |
| return 0; | |
| } | |
| return (int) get_term_meta( $term->term_id, TERM_AUTHOR_META_KEY, true ); | |
| } | |
| /** | |
| * Get the author as a WP_User object. | |
| * | |
| * @param int|WP_Term $term Term ID or object. | |
| * @param string $taxonomy Optional taxonomy, when passing an ID. | |
| * @return WP_User|null | |
| */ | |
| function term_author_get_user( $term, $taxonomy = '' ) { | |
| $user_id = term_author_get_id( $term, $taxonomy ); | |
| $user = $user_id ? get_userdata( $user_id ) : false; | |
| return $user ? $user : null; | |
| } | |
| /** | |
| * Set (or clear) the author of a term. | |
| * | |
| * @param int $term_id Term ID. | |
| * @param int $user_id User ID. Pass 0 to remove the stored author. | |
| * @return bool True on success. | |
| */ | |
| function term_author_set_id( $term_id, $user_id ) { | |
| $term_id = absint( $term_id ); | |
| $user_id = absint( $user_id ); | |
| if ( ! $term_id ) { | |
| return false; | |
| } | |
| if ( ! $user_id ) { | |
| return (bool) delete_term_meta( $term_id, TERM_AUTHOR_META_KEY ); | |
| } | |
| return false !== update_term_meta( $term_id, TERM_AUTHOR_META_KEY, $user_id ); | |
| } | |
| /* ------------------------------------------------------------------------- | |
| * Admin UI | |
| * ---------------------------------------------------------------------- */ | |
| /** | |
| * Wire up the column and edit field for every taxonomy with a UI. | |
| * | |
| * @return void | |
| */ | |
| function term_author_admin_hooks() { | |
| $taxonomies = get_taxonomies( array( 'show_ui' => true ), 'names' ); | |
| foreach ( $taxonomies as $taxonomy ) { | |
| add_filter( "manage_edit-{$taxonomy}_columns", 'term_author_add_column' ); | |
| add_filter( "manage_{$taxonomy}_custom_column", 'term_author_render_column', 10, 3 ); | |
| add_action( "{$taxonomy}_edit_form_fields", 'term_author_render_edit_field', 10, 2 ); | |
| } | |
| } | |
| add_action( 'admin_init', 'term_author_admin_hooks' ); | |
| /** | |
| * Add an "Author" column to the term list table. | |
| * | |
| * @param array $columns Existing columns. | |
| * @return array | |
| */ | |
| function term_author_add_column( $columns ) { | |
| $columns[ TERM_AUTHOR_META_KEY ] = __( 'Author', 'term-author' ); | |
| return $columns; | |
| } | |
| /** | |
| * Render the author column. | |
| * | |
| * @param string $content Column content. | |
| * @param string $column_name Column being rendered. | |
| * @param int $term_id Term ID. | |
| * @return string | |
| */ | |
| function term_author_render_column( $content, $column_name, $term_id ) { | |
| if ( TERM_AUTHOR_META_KEY !== $column_name ) { | |
| return $content; | |
| } | |
| return esc_html( term_author_get_display_name( $term_id ) ); | |
| } | |
| /** | |
| * Human-readable author name, with sensible fallbacks. | |
| * | |
| * @param int $term_id Term ID. | |
| * @return string | |
| */ | |
| function term_author_get_display_name( $term_id ) { | |
| $user_id = term_author_get_id( $term_id ); | |
| if ( ! $user_id ) { | |
| return __( '—', 'term-author' ); | |
| } | |
| $user = get_userdata( $user_id ); | |
| if ( ! $user ) { | |
| /* translators: %d: user ID that no longer exists. */ | |
| return sprintf( __( 'Deleted user (#%d)', 'term-author' ), $user_id ); | |
| } | |
| return $user->display_name; | |
| } | |
| /** | |
| * Render the author field on the term edit screen. | |
| * | |
| * @param WP_Term $term Term being edited. | |
| * @param string $taxonomy Taxonomy slug. | |
| * @return void | |
| */ | |
| function term_author_render_edit_field( $term, $taxonomy ) { | |
| $user_id = term_author_get_id( $term ); | |
| ?> | |
| <tr class="form-field"> | |
| <th scope="row"> | |
| <label for="term_author"><?php esc_html_e( 'Author', 'term-author' ); ?></label> | |
| </th> | |
| <td> | |
| <?php if ( current_user_can( 'list_users' ) && current_user_can( 'edit_term', $term->term_id ) ) : ?> | |
| <?php | |
| wp_nonce_field( 'term_author_' . $term->term_id, 'term_author_nonce' ); | |
| wp_dropdown_users( | |
| array( | |
| 'name' => 'term_author', | |
| 'id' => 'term_author', | |
| 'selected' => $user_id, | |
| 'include_selected' => true, | |
| 'show_option_none' => __( '— No author —', 'term-author' ), | |
| 'option_none_value' => 0, | |
| ) | |
| ); | |
| ?> | |
| <p class="description"> | |
| <?php esc_html_e( 'The user credited with creating this term.', 'term-author' ); ?> | |
| </p> | |
| <?php else : ?> | |
| <p><?php echo esc_html( term_author_get_display_name( $term->term_id ) ); ?></p> | |
| <?php endif; ?> | |
| </td> | |
| </tr> | |
| <?php | |
| } | |
| /** | |
| * Persist the author field from the term edit screen. | |
| * | |
| * @param int $term_id Term ID. | |
| * @param int $tt_id Term taxonomy ID. | |
| * @param string $taxonomy Taxonomy slug. | |
| * @return void | |
| */ | |
| function term_author_save_edit_field( $term_id, $tt_id, $taxonomy ) { | |
| if ( ! isset( $_POST['term_author_nonce'], $_POST['term_author'] ) ) { | |
| return; | |
| } | |
| $nonce = sanitize_key( wp_unslash( $_POST['term_author_nonce'] ) ); | |
| if ( ! wp_verify_nonce( $nonce, 'term_author_' . $term_id ) ) { | |
| return; | |
| } | |
| if ( ! current_user_can( 'edit_term', $term_id ) ) { | |
| return; | |
| } | |
| term_author_set_id( $term_id, absint( wp_unslash( $_POST['term_author'] ) ) ); | |
| } | |
| add_action( 'edited_term', 'term_author_save_edit_field', 10, 3 ); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How it works:
created_termfires for every taxonomy, so the current user ID gets written once viaadd_term_meta( ..., true )— later edits won't overwrite it.register_term_metawith an empty taxonomy registers the key everywhere, gives it an integer type withabsintsanitization, and exposes it atmeta.term_authorin the REST API. Writes are gated on theedit_termcapability.term_author_get_id(),term_author_get_user(),term_author_set_id().Two things worth knowing:
Terms created without a logged-in user (WP-CLI, cron, unauthenticated imports) get no meta at all rather than
0, so a missing key genuinely means "unknown." If you want to force an author in those contexts, filter it:Existing terms won't be backfilled — there's no record of who made them. If you want everything to have a value, a one-off WP-CLI loop assigning a default admin ID is the usual approach.