add category id to option name when adding an option on edit_category

There are 4 problems a small one, a security problem, a misunderstanding, and a big problem.

The Small Problem

Because you used [] notation in the name you’ve indicated that there are multiple category_meta_ values:

category_meta_[<?php echo $cat->term_id ?>]

Which means $_POST['category_meta_'] will always be an array.

It would be much easier to append the term ID, or remove it entirely than it would be to wrap it in [] characters.

The Security Problem

This is not good:

esc_attr_e( $category_meta[ $cat->term_id ] )

I can give you points for escaping, but this code is the same as:

echo esc_attr( __( $category_meta[ $cat->term_id ] ) );

Which commits a major mistake of passing dynamic data into a static string localisation API. Never pass dynamic strings into __( type functions, be it directly or indirectly. echo esc_attr should have been used.

The Misunderstanding

This:

if ( ... && !update_option(...) )
    add_option(...);

Works the same as this:

if ( ! ... ) {
    return;
}
update_option(...)

update_ type functions will add the option/meta/etc if it doesn’t exist

The Biggest Problem

You’ve reinvented term meta. There’s already an API for this!!!!!

  • get_term_meta
  • add_term_meta
  • update_term_meta
  • delete_term_meta

This code is much more reliable and avoids several problems you didn’t realise:

add_action( 'category_edit_form_fields', 'display_category_title_field' );
add_action( 'edit_category', 'save_category_title_field' );

function display_category_title_field( \WP_Term $cat ) : void {
    $title = get_term_meta( $cat->term_id, 'title', true );
    ?>
    <tr class="form-field">
        <th scope="row" valign="top">
            <label for="category-title"><?php esc_html_e( 'Title' ); ?></label>
        </th>
        <td>
            <input id="category-title" name="category-title"
            value="<?php echo esc_attr( $title ); ?>" />
        </td>
    </tr>
    <?php
}

function save_category_title_field( int $term_id ) : void {
    if ( ! isset( $_POST['category-title'] ) ) {
        return;
    }
    $title = sanitize_text_field( $_POST['category-title'] );
    update_term_meta( $term_id, 'title', $title );
}
  • when the category is deleted you aren’t left with phantom options, term meta gets cleaned up
  • We’re now sanitising our inputs
  • no more arrays and serialised data, it stores a plain string
  • when WP fetches a term it grabs all the term meta at the same time saving on queries
  • if you’d used this system with enough terms you would have made every page load very slow from auto-loading all those options
  • grabbing the value is much easier now get_term_meta( $cat_id, 'title', true );
  • The data is now accessible via WP CLI wp term meta commands
  • If you register the meta it can even show up in REST API requests for use with AJAX/JS

A final note, terms already have a title/name, and you’ve chosen super generic name for your fields/functions/etc. Prefix your values so they’re unique to you, e.g. gurky_title instead of title. And maybe name them term titles rather than category titles? That way if you decide to use them for tags etc you don’t have to rewrite half your code