Proposal: Status API for taxonomy terms

Below is a proposal for a Term Status API. Feedback and comments are welcome below.

Summary

Of WordPress’s four main content types – posts, comments, users, and terms – only terms does not have the concept of “status”. The wp_posts, wp_comments, and wp_users tables all have status columns. The semantics and implementation details differ across posts, comments, and users. But in each case, the idea of “status” has allowed for new and improved user experience: autosave for posts, pending user registrations, spammed comments, and so on. It’s time for terms to have their own ‘status’ too.

Use cases

The immediate use case for term status comes from the customizer. Recent developments have made it possible to draft nav menus and other content in the customizer before publishing to your site. The post_status API makes this possible in the case of most nav menu items: items are set to ‘auto-draft’ until the changes have been saved, which ensures that they’re never visible in the Dashboard. This corresponds nicely to the driving philosophy of the Customizer project, which is that it should be possible to preview changes to your site, with the confidence that the changes will be discarded if you choose not to save them.

Taxonomy terms, on the other hand, cannot yet have corresponding nav items created in the customizer; see #37915. Without resorting to odd techniques (such as a “shadow taxonomy” that is hidden from normal view), it’s not possible to create a taxonomy term that is not immediately available in all relevant interfaces: metaboxes, term queries, etc. Allowing the creation of auto-draft terms will create parity between term-related nav items and other types of nav items, creating a more consistent experience for users setting up their sites in the customizer.

Once the fundamentals of the term status API have been built, it’s possible to imagine a number of other following user-facing improvements:

  • “Trash” status for terms, including the ability to restore items from the trash
  • Private terms, which can only be seen and assigned by users with the proper capabilities
  • Autosave when editing terms in the Dashboard
  • “Pending” terms, submitted by Contributors but not generally available until approved by an Editor
  • AND SO MUCH MORE!!!!1!

Technical outline and proposed implementation plan

I’ve reviewed the developer-facing parts of the Taxonomy API to catalog those areas that would need adjustment with the introduction of term status. I’ve also reviewed the post_status API to get a better sense of what a relatively well-rounded implementation of “status” has to recommend (and recommend against!). Here’s how I see the implementation process, broken down into phases that may span releases.

  1. Database schema upgrade
    While it’d be possible to implement term status using termmeta, it’d almost certainly result in significant performance problems. A dedicated ‘status’ column in wp_term_taxonomy is fastest, and best parallels the other content types.
  2. Upgrade routine
    The upgrade routine would include the schema update, as well as the filling-in of the new database column for all existing terms.
  3. Status registration and fetching API functions
    The minimal functions needed for ‘auto-draft’ support are probably as follows:
    • register_term_status() (‘publish’ and ‘auto-draft’ would likely be the first two statuses implemented)
    • get_term_status(), _status_object() and _stati(), to be used when whitelisting, etc
  4. ‘Status’ support when creating or updating terms
    Presumably, this will be a ‘status’ argument in wp_update_term() and wp_insert_term(), with checks against a whitelist of registered statuses.
  5. ‘Status’ support when querying terms
    For backward compatibility, get_terms() and wp_get_object_terms() should default to returning only those terms with the ‘publish’ status. A ‘status’ parameter will allow more fine-grained filtering. This parameter will work similarly to other item queries, with support for arrays of statuses as well as magic terms like ‘any’. More specific functions like get_term_by() and term_exists() will either ignore status or presume ‘public’; more discussion is needed. We’ll also need to make decisions about how non-public terms are handled in hierarchical queries – get_pages() and friends may be helpful benchmarks.
  6. Status “transition” logic
    Similar to wp_transition_post_status(), we want hooks that fire on term status transitions.

I take items 1-6 to be a minimal API for term statuses. Next are the details related to the first use case: ‘auto-draft’.

  1. ‘Auto-draft’ and slugs
    Posts with status ‘auto-draft’ do not get slugs, so that they don’t interfere with the creation of other posts. See wp_unique_post_slug(). ‘Auto-draft’ should probably act similarly.
  2. ‘Auto-draft’ terms should be excluded from XHR exports
    Just like posts.
  3. ‘Auto-draft’ deletion on a schedule
    Posts with ‘auto-draft’ are deleted when they’re older than 7 days. This is not likely to be a huge problem for terms, but should probably be addressed anyway.
  4. Protect auto-draft (and other terms from non-public statuses) in canonical, permalinks, and rewrites
    Things that it (probably) shouldn’t be possible to do:
    • Get a permalink of the archive for a non-public term
    • Load the archive of a non-public term on the front-end
  5. Convenience functionsSomething like wp_publish_term() would be convenient. Maybe others.

I believe that 7-11 are pretty close to what we need to support the Customizer use case. There are a few more obvious things that can happen in future releases, independent of any specific feature:

  1. Status interface when editing/creating a term
    Auto-draft won’t be a ‘public’ status, but once another ‘public’ status is available, a dropdown should appear.
  2. Status filters for the Terms list table
    Like we have for posts.
  3. Integration with capabilities
    Work is being done on fine-grained capabilities for terms #35614. Certain statuses will probably integrate with this.

These last items aren’t necessary for the initial use case, but are the kinds of things that developers will expect as they start using term statuses in plugins.

Potential problems

A couple of potential gotchas:

  • Performance. Term queries are currently pretty fast. Adding a status column, and including an additional WHERE clause with every query, is not going to make things faster. We should think about the proper use of indexes, etc.
  • Direct database queries. Plugins making direct queries against the terms tables will not properly exclude non-public terms. Not much we can do about this from a technical point of view, but we should write some good documentation to help avoid problems.
  • How to handle single term-fetching functions. get_term_by(), get_term(), and term_exists() could all be used in ways that expect the returned value to be a public term (since all terms are now, in fact, public). It would be bad if someone got back an ‘auto-draft’ term for get_term_by( ‘name’, ‘foo’ ). We can explicitly blacklist ‘auto-draft’ terms. Or we can always exclude non-public terms. Either way, we probably want to offer flexibility for developers who want to return non-public terms. I’m not sure of the strategy here: we want something that balances developer expectations with backward compatibility.

Next steps

#38227 will be a parent ticket for work on this project.

There will be a meeting for all interested parties at Tuesday, October 11 18:00 in #core on Slack.