Custom Meta for new Taxonomies in WordPress 3.0
Filed Under: Tags: Custom Taxonomy, Custom Taxonomy Meta, get_metadata, register_taxonomy, WordPress, WordPress 3.0
I’ve been playing around with the new options in WordPress 3.0 like the new Menu system and Custom Post types. These are fantastic additions to the WordPress engine. The guys who work on WordPress core really deserve a big hand.
One other item which gets less attention are Taxonomies. When you initiate a new Taxonomy you are basically cloning the existing Post Category or Post Tags logic. With Post Categories you are provided an additional field for parent because post categories are hierarchal. But what if you want to add additional fields to the interface? This is what I intent to disclose in this article.
Before we get into the code example let me provide some background. I’m working on a custom shopping cart for a client. I intent to support the shopping cart system using a Custom Post Type and 3 Custom Taxonomies. The Custom Post Type will be called ‘Products’. Within Product we have at least 3 new Taxonomies. These are ‘Product Categories’, ‘Product Tags’ and ‘Product Packages’. The ‘Product Categories’ and ‘Product Tags’ function similar to the Post Categories and Post Tags but are used for the Products. The third Taxonomy, ‘Product Packages’, is a little more special. A ‘Package’ in terms of the shopping cart functionality will contain information related to the product configurations. For the client a Product is a photograph and is sold in different formats as in 8 x 10 Print, 8 x 10 Matted and Framed Print, Xmas Cards, etc. You may be thinking these are are just categories and you would be correct. The WordPress Taxonomies support 4 basic fields of information Name, Slug, Parent and Description. For the ‘Product Packages’ Taxonomy I also need to store extra fields such as Active status, Unit Sale Price, Unit Ship Price and Unit Quantity.
Here is a screenshot of the actual Product Package detail.
Here is the simple code to define the new Taxonomy ‘Product Packages’. I’ve wrapped this in my init function to make it easier to use.
1 2 3 4 5 6 7 8 9 10 | add_action( 'init', 'product_init' ); function product_init() { register_taxonomy( 'product_packages', 'products', array( 'hierarchical' => true, 'label' => __('Product Packages'), 'query_var' => false ) ); } |
Now we want to hook into some actions created by WordPress when the new Taxonomy is registered. The first action is used to insert extra fields to the Taxonomy item edit form. Note that all Taxonomies offer the quick edit and the full edit form. This is for the full edit form only.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | add_action( 'product_packages_edit_form_fields', 'edit_product_packages', 10, 2); function edit_product_package($tag, $taxonomy) { $product_package_active = get_metadata($tag->taxonomy, $tag->term_id, 'product_package_active', true); // Check/Set the default value if (!$product_package_active) $product_package_active = "Yes"; $product_package_unit_price = get_metadata($tag->taxonomy, $tag->term_id, 'product_package_unit_price', true); $product_package_ship_price = get_metadata($tag->taxonomy, $tag->term_id, 'product_package_ship_price', true); // Check/Set the default value $product_package_ship_unit = get_metadata($tag->taxonomy, $tag->term_id, 'product_package_ship_unit', true); if (!$product_package_ship_unit) $product_package_ship_unit = 1; ?> <tr class="form-field"> <th scope="row" valign="top"><label for="product_package_active">Package Active?</label></th> <td> <select name="product_package_active" id="product_package_active"> <option value="Yes" selected="selected">Yes</option> <option value="No" <?php if ($product_package_active == "No") echo ' selected="selected";'; ?>>No</option> </select> <p class="description">Marking a Package to 'No' will hide all items in that package.</p> </td> </tr> <tr class="form-field"> <th scope="row" valign="top"><label for="product_package_unit_price">Package Unit Price?</label></th> <td> <input type="text" name="product_package_unit_price" id="product_package_unit_price" value="<?php echo $product_package_unit_price; ?>"/><br /> <p class="description">This is the unit price for the items in this 'package'.</p> </td> </tr> <tr class="form-field"> <th scope="row" valign="top"><label for="product_package_ship_price">Package Shipping Price?</label></th> <td> <input type="text" name="product_package_ship_price" id="product_package_ship_price" value="<?php echo $product_package_ship_price; ?>"/><br /> <p class="description">This is the cost per unit to ship the products in this package.</p> </td> </tr> <tr class="form-field"> <th scope="row" valign="top"><label for="product_package_ship_price">Package Shipping Unit</label></th> <td> <input type="text" name="product_package_ship_unit" id="product_package_ship_unit" value="<?php echo $product_package_ship_unit; ?>"/><br /> <p class="description">This is the quantity of product items. Normally this will be 1. For cards this number may be 6 or 8.</p> </td> </tr> <?php } |
Let’s break down the code some. The ‘add_action’ is a built-in WordPress function. The first parameter ‘product_packages_edit_form_fields’ is a dynamic action key which is created based on the Taxonomy key + ‘_edit_form_fields’. In our case the Taxonomy key is ‘product_packages’. This action is executed from the WordPress file wp-admin/edit-tag-form.php line 68. There is also another action on line 67 which is more generic ‘edit_tag_form_fields’. But we are only concerned with the edits for our Taxonomy. From edit-tag-form.php line 68 you can see the action will pass your function two argument the Tag being edited and the Taxonomy. So our function ‘edit_product_package’ also supports two arguments passed into it. Though we really don’t need or do anything with the second argument since we know this is our specific Taxonomy ‘product_packages’.
Inside the function we call the new WordPress function ‘get_metadata’ to load in the meta items we will be adding to the bottom of the item edit form. This function replaces the get_post_meta function. Actually if you review the get_post_meta function in wp-includes/post.php line 1196 you will see if calls the get_metadata function. The new get_metadata function takes the same basic arguments. The big difference is the first parameter which needs to be a valid Taxonomy. In our case we pass in the Taxonomy ‘product_packages’. Once we have loaded all the item meta information we then display the table row which contain our new form fields. PRetty simply.
Next we add another action hook. This one will activate when the user saves the Taxonomy item. Here is the action code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | add_action( 'edited_product_packages', 'save_product_packages', 10, 2); function save_product_packages($term_id, $tt_id) { if (!$term_id) return; if (isset($_POST['product_package_active'])) update_metadata($_POST['taxonomy'], $term_id, 'product_package_active', $_POST['product_package_active']); if (isset($_POST['product_package_unit_price'])) update_metadata($_POST['taxonomy'], $term_id, 'product_package_unit_price', sprintf("%01.2f", $_POST['product_package_unit_price'])); if (isset($_POST['product_package_ship_price'])) update_metadata($_POST['taxonomy'], $term_id, 'product_package_ship_price', sprintf("%01.2f", $_POST['product_package_ship_price'])); if (!isset($_POST['product_package_ship_unit'])) $_POST['product_package_ship_unit'] = 1; update_metadata($_POST['taxonomy'], $term_id, 'product_package_ship_unit', $_POST['product_package_ship_unit']); } |
This action is similar to the previous action we discussed. But one gets initiated from wp-includes/taxonomy.php line 1970 and is called after the Taxonomy item has been updated in the database. The action key ‘edited_product_packages’ is again made up of ‘edited_’ + our Taxonomy key ‘product_packages’. Also like the previous action we tell the action we want to accept the maximum two arguments though we only really care about the first on. Inside the function ‘save_product_packages’ we check to make sure the ‘term_id’ was not empty. The rest of the function code simple checked if the $_POST variable was passed in from the form. And if it was the WordPress function ‘update_metadata’ is called with the Taxonomy key ‘product_packages’ and the updated value. Again the function ‘update_metadata’ replaced the function and works similar to ‘update_post_meta’.
That is pretty much it for the WordPress coding part. (keep reading)
Not quite. There is one other important topic to discuss. We need to create a new database table to contain out ‘product_packages’ term meta information. Let me explain why. You might have a guess that our ‘update_metadata’ function would store the fields into the wp_postmeta table. Well you would be a close guess but wrong. The function ‘get_metadata’ and ‘update’metadata’ use the Taxonomy key provided via the first function argument to interact with a specific database table. The name of the database is again determined based on the name of the Taxonomy from the first argument. In our case the table name will be something like ‘wp_product_packagesmeta’. Note this depends on your table prefix as defined in your site’s wp_config.php. See the similarity in the tables ‘wp_postmeta’ and our table named ‘wp_product_packagesmeta’? It is simply ‘wp_’ + Taxonomy key + ‘meta’. Again the ‘wp_’ table prefix will be different depending on your settings in your wp_config.php file for the variable ‘$table_prefix’.
Next we need to define the table structure. Again there is a similar pattern for the column names. If you examine the wp_postmeta table columns you will see the 4 columns meta_id, post_id, meta_key and meta_value. For our table the only change is the name of the second column. Instead of ‘post_id’ our column will be named ‘product_packages_id’. See the pattern? Taxonomy key + ‘_id’.
Instead of providing the exact SQL CREATE TABLE command I am only going to suggest you copy the wp_postmeta table of your current site.
My reasoning for not providing the SQL CREATE TABLE command are I don’t want some people to take this literal SQL command and breaking their system then blaming me.
Copy the table structure only and name the copy ‘wp_product_packagesmeta’. Then edit the table structure and rename the second column from ‘post_id’ to ‘product_packages_id’. Once you have the table created and ready you need to tell WordPress where to use it. In the init function ‘product_init’ we need to make two changes. At the top inside the function add the line ‘global $wpdb;’. Then at the very bottom of the function add the line ‘$wpdb->product_packagesmeta = $wpdb->prefix.”product_packagesmeta”;’. your final ‘product_init’ function should look like this
1 2 3 4 5 6 7 8 9 10 11 12 13 | add_action( 'init', 'product_init' ); function product_init() { global $wpdb; register_taxonomy( 'product_packages', 'products', array( 'hierarchical' => true, 'label' => __('Product Packages'), 'query_var' => false ) ); $wpdb->product_packagesmeta = $wpdb->prefix."product_packagesmeta"; } |
Hope you enjoy this little tip. I’m already working on the next article which will build upon this one. The next article will show you how to customize the table list column when you list out the Product Packages items (see image below). Look for it soon.
You can download all the code used in the article below:
codehooligans_wordpress_taxonomy_meta.zip






August 9th, 2010 at 10:20 am
This is a great tutorial, thanks a million! One suggestion, instead creating a meta table for each taxonomy, would it make more sense to just create one table … “wp_termmeta”. To keep the clutter down. Also there’s a plugin that will create this table for you “Simple Term Meta”.
[link:http://wordpress.org/extend/plugins/simple-term-meta/]
Not a huge deal but could make life a little easier.
August 9th, 2010 at 8:17 pm
@mike van winkle: Thanks for stopping by and leaving a nice detailed comment. My example was that of illustration for developers not really focused on how to override the new default WP function get/add/delete_metadata. True one could use the simple term meta plugin. But given this term meta table has the same structure as the already existing postmeta table why not just use it and then no need for a new table.
I would rather keep things separated in the case of my custom taxonomies. That way if I decide to go another way I can just delete the one table not need to worry about filtering the table. Which on that note I think the simple term meta is a great plugin but it will cause issues down the road for users. Here is why. Look at the plugin function for add_termmeta().
2
3
return add_metadata('term', $term_id, $meta_key, $meta_value, $unique);
}
The plugin function is calling the WP function add_metadata with a hard-coded
meta_type value of ‘term’. So months down the road if the user is using more than a few custom taxonomies with extended meta data they will all be all be stored into this one table. All with the type of ‘term’. How would you split this term information up if you were removing one of your custom taxonomies? Just a thought. Thanks again for the comment. I’m show many will appreciate the note about Simple Term Meta.
August 10th, 2010 at 10:12 am
[...] my search, I stumbled upon this terrific tutorial from CodeHooligans and this simple plugin Jacob M Goldman. Both of which I rely on [...]
August 27th, 2010 at 1:06 pm
Very useful tutorial that serves perfectly for a project I’m currently working on, but I really need the second part of the article… When will the article be available?
August 27th, 2010 at 5:40 pm
@Pedro Farinha: Thanks for the comment. And thanks for reminding me that I promised another article on this thread. Look for this coming over the weekend. Probably sometime Sunday afternoon. The third article will deal with adding custom user roles to the system to manage access. Quite interesting.
August 29th, 2010 at 2:21 pm
[...] Custom Meta for new Taxonomies in WordPress 3.0 [...]