Transform Meta Boxes
Change the way tags are shown in a post’s Edit screen.
Intro
For more than one client, I’ve wanted to show the taxonomy on a post’s Edit page without the ability to enter new ones - I’ve seen too many occasions where the same term has been spelt slightly differently, which pretty much defeats the point of tagging something within a site.
So instead, they could first create those terms beforehand, and then I could display them as a bunch of buttons in the individual Edit page. This would let them pick quickly and easily from their predefined set of tags, and stop them accidentally making a new tag with a typo.
In the plugin constructor, along with the usual code for settings pages etc, the init hook is called late (hopefully after everything else) so that we can change how each taxonomy is registered to be displayed.
// only need this plugin in the backend
if (! is_admin()) return;
// get which taxonomies need altering or set the default to be empty
if (! ($this->options = get_option('TransformMetaBoxesPluginOptions'))) {
add_option('TransformMetaBoxesPluginOptions', array('taxonomies_to_change' => array()), null, false);
}
// if we have anything to change, only then load in the file that does the transformations
if (sizeof($this->options['taxonomies_to_change'])) {
require_once 'meta-box-transformations.php';
add_action('init', array($this, 'alter_taxonomies'), 99);
}
alter_taxonomies()
This is how the look of a taxonomy is changed; by setting the meta_box_cb
hook to a different class that gets called when the taxonomy is shown in the Edit screens of the backend, I override the standard freetext tag entry with a form of my own.
Unfortunately, it’s also why it doesn’t work with the Gutenberg editor, as that ignores the meta_box_cb hook because it isn’t in the REST API, which is a backwards compatibility bug that doesn’t look like getting fixed anytime soon.
public function alter_taxonomies() {
foreach ($this->options['taxonomies_to_change'] as $taxonomy => $settings) {
$tax_obj = get_taxonomy($taxonomy);
// check the taxonomy is still a taxonomy (ie. if it gets deleted while selected here)
if (is_object($tax_obj)) {
// when the taxonomy gets displayed in the backend, the meta_box_cb will be called
$tax_obj->meta_box_cb = array(
// a separate class to hold the different HTML bits
new MetaBoxTransformations(array(
'taxonomy' => $taxonomy,
'name' => $settings['name'],
'field' => $settings['field'],
'multiple' => $settings['multiple'],
)),
// and what method to call in it
$settings['transformation']
);
register_taxonomy($settings['taxonomy'], $tax_obj->object_type, (array) $tax_obj);
}
}
}
MetaBoxTransformations
In case I came up with any other ways of showing tags, having split the meta_box_cb
callback into a separate class should make it a bit neater.
class MetaBoxTransformations {
public function __construct($params = array()) {
foreach ($params as $key => $value) $this->$key = $value;
if (! isset($this->taxonomy)) $this->taxonomy = 'category';
if (! isset($this->name)) $this->name = 'post_category';
if (! isset($this->field)) $this->field = 'term_id';
if (! isset($this->multiple)) $this->multiple = false;
if (! isset($this->select_size)) $this->select_size = '10';
$this->terms = get_terms(array(
'taxonomy' => $this->taxonomy,
'orderby' => 'name',
'hide_empty' => false,
'exclude' => '',
));
// means it's hierarchical and we can't rely on wp's dropdown category function to sort it for us
if (! is_wp_error($this->terms) AND 'term_id' == $this->field) {
$sorted = array();
$this->sort_terms_hierarchically($this->terms, $sorted);
$this->terms = $sorted;
}
}
...
// one of the meta_box_cb calling methods
public function toggles($post) {
$this->selected = $this->get_selected($post->ID);
$output = '<div id="taxonomy-'.$this->taxonomy.'" class="categorydiv checkbox-toggles">'
. $this->display_buttons_recursively($this->terms)
. '</div>';
echo $output;
}
private function display_buttons_recursively($terms = array(), $level = 0) {
$output = '';
foreach ($terms as $i => $term) {
$checked = (in_array($term->term_id, $this->selected)) ? ' checked="checked"' : '';
$output.= '<input type="checkbox" name="'.$this->name.'[]" id="'.$this->name.'['.$i.']" value="'.$term->{$this->field}.'"'.$checked.'>'
. '<label class="level-'.$level.'" for="'.$this->name.'['.$i.']">'.$term->name.' ('.$term->count.')</label>';
if (isset($term->children) AND sizeof($term->children)) {
$output.= $this->display_buttons_recursively($term->children, $level+1);
}
}
return $output;
}
}
The function “toggles” is the method called by the meta_box_cb
hook with $settings['transformation']
in the alter_taxonomies()
function. I also used a very similar pair of functions to display a dropdown instead of toggle switches.