I love WooCommerce and use it for most of my clients e-commerce sites and needs from building the simplest to the most advanced and complex online shops.
Its manly for the flexibility it provides and the ease of customization. There are too many extensions to count and they cover almost every feature or functionality you need. And the best part is if you can’t find an extension that is what you need you can always write it yourself.
The Plan
WooCommerce product tabs are a great way to tell your customers everything and anything there is to know about your product and there are many extensions that allow you to add tabs but none are implemented in the way I was looking for so in this tutorial we are going to create a simple plugin to add custom product tabs to any product we want and as many as we need.
To avoid messing with filters, template files, or modifying your theme we are going to create a simple plugin that will ease the way we create new tabs and add them to our product pages. We are going to use a few action and filter hooks provided by WooCommerce, add our own custom post type for tabs and we are also going to setup an options panel to allow custom tabs to be set as global tabs so they will show up on all product pages automatically.
This tutorial is a bit more advanced then the previous ones but nothing too complex.
Coding the plugin
The whole plugin is available for download at GitHub.
To start coding our plugin we create a new file named Simple_Custom_Product_Tabs.php
and we load it up with Standard WordPress Plugin Information header.
<?php /* Plugin Name: GWP Custom Product Tabs Plugin URI: Description: A plugin to add Custom product tabs for WooCommerce Version: 1.0 Author: Ohad Raz Author URI: https://generatewp.com */Then we create a main class for the plugin with the methods we want to implement:
/** * GWP_Custom_Product_Tabs */ class GWP_Custom_Product_Tabs{ /** * $post_type * holds custo post type name * @var string */ public $post_type = 'c_p_tab'; /** * $id * holds settings tab id * @var string */ public $id = 'gwp_custom_tabs'; /** * __construct * class constructor will set the needed filter and action hooks */ function __construct(){} /** * woocommerce_settings_tabs_array * Used to add a WooCommerce settings tab * @param array $settings_tabs * @return array */ function woocommerce_settings_tabs_array( $settings_tabs ) {} /** * show_settings_tab * Used to display the WooCommerce settings tab content * @return void */ function show_settings_tab(){} /** * update_settings_tab * Used to save the WooCommerce settings tab values * @return void */ function update_settings_tab(){} /** * get_settings * Used to define the WooCommerce settings tab fields * @return void */ function get_settings(){} /** * show_c_p_tab_field * Used to print the settings field of the custom type c_p_tab * @param array $field * @return void */ function show_c_p_tab_field($field){} /** * save_c_p_tab_field * Used to save the settings field of the custom type c_p_tab * @param array $field * @return void */ function save_c_p_tab_field($field){} /** * ajax_footer_js * Used to add needed javascript to product edit screen and custom settings tab * @return void */ function ajax_footer_js(){} /** * woocommerce_product_write_panel_tabs * Used to add a product custom tab to product edit screen * @return void */ function woocommerce_product_write_panel_tabs(){} /** * woocommerce_product_write_panels * Used to display a product custom tab content (fields) to product edit screen * @return void */ function woocommerce_product_write_panels() {} /** * woocommerce_process_product_meta * used to save product custom tabs meta * @param int $post_id * @return void */ function woocommerce_process_product_meta( $post_id ) {} /** * woocommerce_json_custom_tabs * An AJAX handler to list tabs for tabs field * prints out json of {tab_id: tab_name} * @return void */ function woocommerce_json_custom_tabs(){} /** * woocommerce_product_tabs * Used to add tabs to product view page * @param array $tabs * @return array */ function woocommerce_product_tabs($tabs){} /** * render_tab * Used to render tabs on product view page * @param string $key * @param array $tab * @return void */ function render_tab($key,$tab){} /** * custom_product_tabs_post_type * Register custom tabs Post Type * @return void */ function custom_product_tabs_post_type() {} }//end GWP_Custom_Product_Tabs class. new GWP_Custom_Product_Tabs();Upgrade to GenerateWP Premium Enjoy better WordPress generators Upgrade Now
Next we are going to implement the class methods and we actually start from the end and the easy part which is creating our “custom tabs” custom post type, to do that we use use the handy Custom Post Type Generator and we get this:
// Register Custom Post Type function custom_product_tabs_post_type() { $labels = array( 'name' => _x( 'Product Tabs', 'Post Type General Name', 'GWP' ), 'singular_name' => _x( 'Product Tab', 'Post Type Singular Name', 'GWP' ), 'menu_name' => __( 'product Tabs', 'GWP' ), 'parent_item_colon' => __( '', 'GWP' ), 'all_items' => __( 'Product Tabs', 'GWP' ), 'view_item' => __( '', 'GWP' ), 'add_new_item' => __( 'Add Product Tab', 'GWP' ), 'add_new' => __( 'Add New', 'GWP' ), 'edit_item' => __( 'Edit Product Tab', 'GWP' ), 'update_item' => __( 'Update Product Tab', 'GWP' ), 'search_items' => __( 'Search Product Tab', 'GWP' ), 'not_found' => __( 'Not found', 'GWP' ), 'not_found_in_trash' => __( 'Not found in Trash', 'GWP' ), ); $args = array( 'label' => __( 'Product Tabs', 'GWP' ), 'description' => __( 'Custom Product Tabs', 'GWP' ), 'labels' => $labels, 'supports' => array( 'title', 'editor', ), 'hierarchical' => false, 'public' => true, 'show_ui' => true, 'show_in_menu' => 'edit.php?post_type=product', 'show_in_nav_menus' => false, 'show_in_admin_bar' => true, 'menu_position' => 5, 'menu_icon' => 'dashicons-feedback', 'can_export' => true, 'has_archive' => false, 'exclude_from_search' => true, 'publicly_queryable' => false, 'capability_type' => 'post', ); register_post_type( 'c_p_tab', $args ); } // Hook into the 'init' action add_action( 'init', 'custom_product_tabs_post_type', 0 );View snippet Clone snippet Download snippet
We only modify the
add_action
to hook the method from within the class and drop that line of code in the class constructor so it should look like this:/** * __construct * class constructor will set the needed filter and action hooks */ function __construct(){ //register_post_type add_action( 'init', array($this,'custom_product_tabs_post_type'), 0 ); }And Once we do that we can activate our plugin and we should see a new menu item under products named “product tabs”.
So now we can start adding our custom product tabs using the familiar WordPress UI, Title will be used for custom tab link/title and the content editor will be used to the custom tab content.
Like I said we start we the easy part, moving on we need a way to link our custom tabs with a specific single product and a way to make this custom tabs global so they will show up on all products without having to link the custom tabs to each product one at a time. So first we are going to add a settings tab to WooCommerce settings panel which will allow as to select which custom product tabs we want link to all products or as we are going to call it: “Make the custom product tab a global tab”. We start by implementing the
woocommerce_settings_tabs_array
method of our class which we hook towoocommerce_settings_tabs_array
filter hook provided by WooCommerce and we hook the method in our class constructor./** * woocommerce_settings_tabs_array * Used to add a WooCommerce settings tab * @param array $settings_tabs * @return array */ function woocommerce_settings_tabs_array( $settings_tabs ) { $settings_tabs[$this->id] = __('GWP Custom Tabs','GWP'); return $settings_tabs; }As you see its a simple method to add our WooCommerce settings tab to the tabs array.
Side note:
You may notice that I name (as much as I can) the methods of the class in the name of the filter or action hook that will be used to execute that method. This helps identify the method’s purpose and since we are coding this plugin using OOP (Object-oriented programming) approach we don’t have to worry about having a function or a method with that same name.Next we Implement the
show_settings_tab
andupdate_settings_tab
methods which are used to display the WooCommerce settings tab and save it when we hit the save button:/** * show_settings_tab * Used to display the WooCommerce settings tab content * @return void */ function show_settings_tab(){ woocommerce_admin_fields($this->get_settings()); } /** * update_settings_tab * Used to save the WooCommerce settings tab values * @return void */ function update_settings_tab(){ woocommerce_update_options($this->get_settings()); }Two simple methods which both use WooCommerce provided functions
woocommerce_admin_fields
which accepts an array of settings fields to display andwoocommerce_update_options
which accepts that same array of settings fields to save or update on submit. And because both of theme accept that same array we create a methodget_settings
to return that array instead of writing it twice (once in each method) so ourget_settings
should look like this:/** * get_settings * Used to define the WooCommerce settings tab fields * @return void */ function get_settings(){ $settings = array( 'section_title' => array( 'name' => __('GWP Custom Tabs','GWP'), 'type' => 'title', 'desc' => '', 'id' => 'wc_'.$this->id.'_section_title' ), 'title' => array( 'name' => __( 'Global Custom Tabs', 'GWP' ), 'type' => $this->post_type, 'desc' => __( 'Start typing the Custom Tab name, Used for including custom tabs on all products.', 'GWP' ), 'desc_tip' => true, 'default' => '', 'id' => 'wc_'.$this->id.'_globals' ), 'section_end' => array( 'type' => 'sectionend', 'id' => 'wc_'.$this->id.'_section_end' ) ); return apply_filters( 'wc_'.$this->id.'_settings', $settings ); }WooCommerce comes ready with these field types:
- text
- color
- image_width
- select
- checkbox
- textarea
- single_select_page
- single_select_country
- multi_select_countries
- pasword
- number
- multiselect
- radio
- Maybe a few others
Other then that you define your own custom type, providing the implementation via the woocommerce_admin_field_{field_type}
action hook to display it and woocommerce_update_option_{field_type}
action hook to save its value. You can see that we define 3 settings fields in our get_settings
first a simple `title` field, last `sectionend` field (which tells WooCommerce its the end of our settings section and in the middle we create a custom type field with the same name of our custom tabs post type (using $this->post_type
). So we need to define a method named show_c_p_tab_field
which we will hook using the woocommerce_admin_field_c_p_tab
action hook to display our custom settings field and another method named save_c_p_tab_field
to save the settings which we will hook using woocommerce_update_option_c_p_tab
action hook:
/** * show_c_p_tab_field * Used to print the settings field of the custom type c_p_tab * @param array $field * @return void */ function show_c_p_tab_field($field){ global $woocommerce; ?><tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field['id'] ); ?>"><?php echo esc_html( $field['title'] ); ?></label> <?php echo '<img class="help_tip" data-tip="' . esc_attr( $field['desc'] ) . '" src="' . $woocommerce->plugin_url() . '/assets/images/help.png" height="16" width="16" />'; ?> </th> <td class="forminp forminp-<?php echo sanitize_title( $field['type'] ) ?>"> <p class="form-field custom_product_tabs"> <select id="custom_product_tabs" style="min-width: 350px;" name="<?php echo $field['id'];?>[]" class="ajax_chosen_select_tabs" multiple="multiple" data-placeholder="<?php _e( 'Search for a custom tab…', 'GWP' ); ?>"> <?php $tabs_ids = get_option($field['id']); $_ids = ! empty( $tabs_ids ) ? array_map( 'absint', $tabs_ids ) : null; if ( $_ids ) { foreach ( $_ids as $id ) { $tab_name = get_the_title($id); echo '<option value="' . esc_attr( $id ) . '" selected="selected">' . esc_html( $tab_name ) . '</option>'; } } ?> </select> </p> </td> </tr><?php add_action('admin_footer',array($this,'ajax_footer_js')); } /** * save_c_p_tab_field * Used to save the settings field of the custom type c_p_tab * @param array $field * @return void */ function save_c_p_tab_field($field){ if (isset($_POST[$field['id']])){ $option_value = $_POST[$field['id']]; update_option($field['id'],$option_value); }else{ delete_option($field['id']); } }In
save_c_p_tab_field
method we simple save the value if it is posted as an option or delete the option if nothing is posted.Upgrade to GenerateWP Premium Enjoy better WordPress generators Upgrade Now
In
show_c_p_tab_field
we mimic the markup of the “Products Select Field Type” which is used by WooCommerce for selecting linked products, up sale products, cross sale products and probbly in other places as well. We do that to get the same functionality of these fields which is: you start typing the product name or id and an AJAX call is made to search the database for products with that string in the name or with that id (depends on what you are typing).
We change the select tag element class attribute to
ajax_chosen_select_tabs
because we don’t want to search for products, we want to search for tabs. So next we need to tell WooCommerce (well not as much WooCommerce asajaxChosen
library which is included by WooCommerce) to search for tabs when we type in a name or an id and we do so by hooking another method namedajax_footer_js
on line 33, so let implement that:/** * ajax_footer_js * Used to add needed javascript to product edit screen and custom settings tab * @return void */ function ajax_footer_js(){ ?> <script type="text/javascript"> jQuery(document).ready(function($){ // Ajax Chosen Product Selectors jQuery("select.ajax_chosen_select_tabs").ajaxChosen({ method: 'GET', url: ajaxurl, dataType: 'json', afterTypeDelay: 100, data: { action : 'woocommerce_json_custom_tabs', security: '<?php echo wp_create_nonce( "search-products-tabs" ); ?>' } }, function (data) { var terms = {}; $.each(data, function (i, val) { terms[i] = val; }); return terms; }); }); </script> <?php }Notce:
A better practice solution would be to include this method as an external JavaScript file usingwp_enqueue_script
but for this tutorial I wanted to keep it as a snigle file plugin.We simply define a GET request method, json data type, admin ajax url and data parameters action named
woocommerce_json_custom_tabs
and security nonce. This tell `ajaxChosen` to make an ajax request to the server to look for tabs, so we need to implement that and we do it inwoocommerce_json_custom_tabs
method:/** * woocommerce_json_custom_tabs * An AJAX handler to list tabs for tabs field * prints out json of {tab_id: tab_name} * @return void */ function woocommerce_json_custom_tabs(){ check_ajax_referer( 'search-products-tabs', 'security' ); header( 'Content-Type: application/json; charset=utf-8' ); $term = (string) urldecode(stripslashes(strip_tags($_GET['term']))); if (empty($term)) die(); $post_types = array($this->post_type); if ( is_numeric( $term ) ) { //by tab id $args = array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'post__in' => array(0, $term), 'fields' => 'ids' ); $args2 = array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'post_parent' => $term, 'fields' => 'ids' ); $posts = array_unique(array_merge( get_posts( $args ), get_posts( $args2 ))); } else { //by tab name $args = array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 's' => $term, 'fields' => 'ids' ); $posts = array_unique( get_posts( $args ) ); } $found_tabs = array(); if ( $posts ) foreach ( $posts as $post_id ) { $found_tabs[ $post_id ] = get_the_title($post_id); } $found_tabs = apply_filters( 'woocommerce_json_search_found_tabs', $found_tabs ); echo json_encode( $found_tabs ); die(); }Breakdown – first on line 8 we validate the request to prevent processing requests external of the site using
check_ajax_referer
. Then we define the response header on line 9. On line 10 we sanitize the search term and check that its not empty on line 11. moving on to lines 13-43 we check if the search term is numeric then we threat it as a tab id and we search for custom product tabs with that id, if its not numeric we search for custom product tabs with that string. On line 45-55 we format the found tabs in array oftab id => tab title
printout a json encoded version of that array anddie()
to end the ajax request.It seem like a lot but we are all most half way there. We can already navigate to our custom WooCommerce settings panel tab and select and save our global custom product tabs.
So moving on, we said that we are going to provide an easy way to link a custom product tab to specific single product and to do that we are going to create a new tab in the WooCommerce product data metabox which will have two field which will be tab select field just like the one we used in the custom settings panel tab:
- product Tabs select field to include/link specific product tabs to the product
- product Tabs select field to exclude/unlink global product tabs from the product
To do that we first tell WooCommerce to add our fields tab to the metabox using the
woocommerce_product_write_panel_tabs
action hook in the code>woocommerce_product_write_panel_tabs method:/** * woocommerce_product_write_panel_tabs * Used to add a product custom tab to product edit screen * @return void */ function woocommerce_product_write_panel_tabs(){ ?> <li class="custom_tab"> <a href="#custom_tab_data_ctabs"> <?php _e('Custom Tabs', 'GWP'); ?> </a> </li> <?php }Then we render the content of the metabox tab using the
woocommerce_product_write_panels
action hook in the code>woocommerce_product_write_panels method:/** * woocommerce_product_write_panels * Used to display a product custom tab content (fields) to product edit screen * @return void */ function woocommerce_product_write_panels() { global $post,$woocommerce; $fields = array( array( 'key' => 'custom_tabs_ids', 'label' => __( 'Select Custom Tabs', 'GWP' ), 'desc' => __( 'Start typing the Custom Tab name, Used for including custom tabs.', 'GWP' ) ), array( 'key' => 'exclude_custom_tabs_ids', 'label' => __( 'Select Global Tabs to exclude', 'GWP' ), 'desc' => __( 'Start typing the Custom Tab name. used for excluding global tabs.', 'GWP' ) ) ); ?> <div id="custom_tab_data_ctabs" class="panel woocommerce_options_panel"> <?php foreach ($fields as $f) { $tabs_ids = get_post_meta( $post->ID, $f['key'], true ); $_ids = ! empty( $tabs_ids ) ? array_map( 'absint', $tabs_ids ) : null; ?> <div class="options_group"> <p class="form-field custom_product_tabs"> <label for="custom_product_tabs"><?php echo $f['label']; ?></label> <select id="<?php echo $f['key']; ?>" name="<?php echo $f['key']; ?>[]" class="ajax_chosen_select_tabs" multiple="multiple" data-placeholder="<?php _e( 'Search for a custom tab…', 'GWP' ); ?>"> <?php if ( $_ids ) { foreach ( $_ids as $id ) { $tab_name = get_the_title($id); echo '<option value="' . esc_attr( $id ) . '" selected="selected">' . esc_html( $tab_name ) . '</option>'; } } ?> </select> <img class="help_tip" data-tip="<?php echo esc_attr($f['desc']); ?>" src="<?php echo $woocommerce->plugin_url(); ?>/assets/images/help.png" height="16" width="16" /> </p> </div> <?php } ?> </div> <?php add_action('admin_footer',array($this,'ajax_footer_js')); }Breakdown On lines 8-19 we define an array of the two fields arguments and on line 23-45 we simply output the fields markup one at a time. Last one line 48 we hook our
ajax_footer_js
method from before which we will use once (reusable code my 5th love in life 🙂 ).To finish up the meta box methods we need to implement the
method which we will use to save these fields values as product meta.
/** * woocommerce_process_product_meta * used to save product custom tabs meta * @param int $post_id * @return void */ function woocommerce_process_product_meta( $post_id ) { foreach (array('exclude_custom_tabs_ids','custom_tabs_ids') as $key) { if (isset($_POST[$key])) update_post_meta( $post_id, $key, $_POST[$key]); else delete_post_meta( $post_id, $key); } }And we now have a functional working WooCommerce product data metabox custom tab with two fields.
Which brings us to the last part of this plugin and that is actually displaying the custom product tabs. We implement thewoocommerce_product_tabs
method which is hooked to thewoocommerce_product_tabs
filter hook:/** * woocommerce_product_tabs * Used to add tabs to product view page * @param array $tabs * @return array */ function woocommerce_product_tabs($tabs){ global $post; //get global tabs $global_tabs = get_option('wc_'.$this->id.'_globals'); $global_tabs_ids = ! empty( $global_tabs ) ? array_map( 'absint', $global_tabs ) : array(); //get tabs to exclude from this product $exclude_tabs = get_post_meta( $post->ID, 'exclude_custom_tabs_ids', true ); $exclude_tabs_ids = ! empty($exclude_tabs ) ? array_map( 'absint', $exclude_tabs ) : array(); //get tabs to include with current product $product_tabs = get_post_meta( $post->ID, 'custom_tabs_ids', true ); $_ids = ! empty($product_tabs ) ? array_map( 'absint', $product_tabs ) : null; //combine global and product specific tabs and remove excluded tabs $_ids = array_merge((array)$_ids,(array)array_diff((array)$global_tabs_ids, (array)$exclude_tabs_ids)); if ($_ids){ //fix order $_ids = array_reverse($_ids); //loop over tabs and add them foreach ($_ids as $id) { $tabs['customtab_'.$id] = array( 'title' => get_the_title($id), 'priority' => 50, 'callback' => array($this,'render_tab'), 'content' => apply_filters('the_content',get_post_field( 'post_content', $id)) //this allows shortcodes in custom tabs ); } } return $tabs; }Breakdown on lines 10-11 we get the global tabs, on lines 14-15 we get global tabs to exclude from current product, on lines 18-19 we get tabs to include with current product and one line 22 we combine global and product specific tabs to a single array removing excluded tabs. Then on lines 28-35 we loop over the tabs and add them to the tabs array, setting the callback argument as our
render_tab
method which will implement in a second and last on line 37 we return the modified array of tabs to display.Upgrade to GenerateWP Premium Enjoy better WordPress generators Upgrade Now
And after we added the tabs to the tab array we only need to display theme so our
render_tab
method should look like this:/** * render_tab * Used to render tabs on product view page * @param string $key * @param array $tab * @return void */ function render_tab($key,$tab){ global $post; echo '<h2>'.apply_filters('GWP_custom_tab_title',$tab['title'],$tab,$key).'</h2>'; echo apply_filters('GWP_custom_tab_content',$tab['content'],$tab,$key); }And making sure our constructor has all the hooks in place:
/** * __construct * class constructor will set the needed filter and action hooks */ function __construct(){ if (is_admin()){ //add settings tab add_filter( 'woocommerce_settings_tabs_array', array($this,'woocommerce_settings_tabs_array'), 50 ); //show settings tab add_action( 'woocommerce_settings_tabs_'.$this->id, array($this,'show_settings_tab' )); //save settings tab add_action( 'woocommerce_update_options_'.$this->id, array($this,'update_settings_tab' )); //add tabs select field add_action('woocommerce_admin_field_'.$this->post_type,array($this,'show_'.$this->post_type.'_field' ),10); //save tabs select field add_action( 'woocommerce_update_option_'.$this->post_type,array($this,'save_'.$this->post_type.'_field' ),10); //add product tab link in admin add_action( 'woocommerce_product_write_panel_tabs', array($this,'woocommerce_product_write_panel_tabs' )); //add product tab content in admin add_action('woocommerce_product_write_panels', array($this,'woocommerce_product_write_panels')); //save product selected tabs add_action('woocommerce_process_product_meta', array($this,'woocommerce_process_product_meta'), 10, 2); }else{ //add tabs to product page add_filter( 'woocommerce_product_tabs', array($this,'woocommerce_product_tabs') ); } //ajax search handler add_action('wp_ajax_woocommerce_json_custom_tabs', array($this,'woocommerce_json_custom_tabs')); //register_post_type add_action( 'init', array($this,'custom_product_tabs_post_type'), 0 ); }The whole plugin file:
<?php /* Plugin Name: GWP Custom Product Tabs Plugin URI: Description: A plugin to add Custom product tabs for WooCommerce Version: 1.0 Author: Ohad Raz Author URI: https://generatewp.com */ /** * GWP_Custom_Product_Tabs */ class GWP_Custom_Product_Tabs{ /** * $post_type * holds custo post type name * @var string */ public $post_type = 'c_p_tab'; /** * $id * holds settings tab id * @var string */ public $id = 'gwp_custom_tabs'; /** * __construct * class constructor will set the needed filter and action hooks */ function __construct(){ if (is_admin()){ //add settings tab add_filter( 'woocommerce_settings_tabs_array', array($this,'woocommerce_settings_tabs_array'), 50 ); //show settings tab add_action( 'woocommerce_settings_tabs_'.$this->id, array($this,'show_settings_tab' )); //save settings tab add_action( 'woocommerce_update_options_'.$this->id, array($this,'update_settings_tab' )); //add tabs select field add_action('woocommerce_admin_field_'.$this->post_type,array($this,'show_'.$this->post_type.'_field' ),10); //save tabs select field add_action( 'woocommerce_update_option_'.$this->post_type,array($this,'save_'.$this->post_type.'_field' ),10); //add product tab link in admin add_action( 'woocommerce_product_write_panel_tabs', array($this,'woocommerce_product_write_panel_tabs' )); //add product tab content in admin add_action('woocommerce_product_write_panels', array($this,'woocommerce_product_write_panels')); //save product selected tabs add_action('woocommerce_process_product_meta', array($this,'woocommerce_process_product_meta'), 10, 2); }else{ //add tabs to product page add_filter( 'woocommerce_product_tabs', array($this,'woocommerce_product_tabs') ); } //ajax search handler add_action('wp_ajax_woocommerce_json_custom_tabs', array($this,'woocommerce_json_custom_tabs')); //register_post_type add_action( 'init', array($this,'custom_product_tabs_post_type'), 0 ); } /** * woocommerce_settings_tabs_array * Used to add a WooCommerce settings tab * @param array $settings_tabs * @return array */ function woocommerce_settings_tabs_array( $settings_tabs ) { $settings_tabs[$this->id] = __('GWP Custom Tabs','GWP'); return $settings_tabs; } /** * show_settings_tab * Used to display the WooCommerce settings tab content * @return void */ function show_settings_tab(){ woocommerce_admin_fields($this->get_settings()); } /** * update_settings_tab * Used to save the WooCommerce settings tab values * @return void */ function update_settings_tab(){ woocommerce_update_options($this->get_settings()); } /** * get_settings * Used to define the WooCommerce settings tab fields * @return void */ function get_settings(){ $settings = array( 'section_title' => array( 'name' => __('GWP Custom Tabs','GWP'), 'type' => 'title', 'desc' => '', 'id' => 'wc_'.$this->id.'_section_title' ), 'title' => array( 'name' => __( 'Global Custom Tabs', 'GWP' ), 'type' => $this->post_type, 'desc' => __( 'Start typing the Custom Tab name, Used for including custom tabs on all products.', 'GWP' ), 'desc_tip' => true, 'default' => '', 'id' => 'wc_'.$this->id.'_globals' ), 'section_end' => array( 'type' => 'sectionend', 'id' => 'wc_'.$this->id.'_section_end' ) ); return apply_filters( 'wc_'.$this->id.'_settings', $settings ); } /** * show_c_p_tab_field * Used to print the settings field of the custom type c_p_tab * @param array $field * @return void */ function show_c_p_tab_field($field){ global $woocommerce; ?><tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field['id'] ); ?>"><?php echo esc_html( $field['title'] ); ?></label> <?php echo '<img class="help_tip" data-tip="' . esc_attr( $field['desc'] ) . '" src="' . $woocommerce->plugin_url() . '/assets/images/help.png" height="16" width="16" />'; ?> </th> <td class="forminp forminp-<?php echo sanitize_title( $field['type'] ) ?>"> <p class="form-field custom_product_tabs"> <select id="custom_product_tabs" style="min-width: 350px;" name="<?php echo $field['id'];?>[]" class="ajax_chosen_select_tabs" multiple="multiple" data-placeholder="<?php _e( 'Search for a custom tab…', 'GWP' ); ?>"> <?php $tabs_ids = get_option($field['id']); $_ids = ! empty( $tabs_ids ) ? array_map( 'absint', $tabs_ids ) : null; if ( $_ids ) { foreach ( $_ids as $id ) { $tab_name = get_the_title($id); echo '<option value="' . esc_attr( $id ) . '" selected="selected">' . esc_html( $tab_name ) . '</option>'; } } ?> </select> </p> </td> </tr><?php add_action('admin_footer',array($this,'ajax_footer_js')); } /** * save_c_p_tab_field * Used to save the settings field of the custom type c_p_tab * @param array $field * @return void */ function save_c_p_tab_field($field){ if (isset($_POST[$field['id']])){ $option_value = $_POST[$field['id']]; update_option($field['id'],$option_value); }else{ delete_option($field['id']); } } /** * ajax_footer_js * Used to add needed javascript to product edit screen and custom settings tab * @return void */ function ajax_footer_js(){ ?> <script type="text/javascript"> jQuery(document).ready(function($){ // Ajax Chosen Product Selectors jQuery("select.ajax_chosen_select_tabs").ajaxChosen({ method: 'GET', url: ajaxurl, dataType: 'json', afterTypeDelay: 100, data: { action : 'woocommerce_json_custom_tabs', security: '<?php echo wp_create_nonce( "search-products-tabs" ); ?>' } }, function (data) { var terms = {}; $.each(data, function (i, val) { terms[i] = val; }); return terms; }); }); </script> <?php } /** * woocommerce_product_write_panel_tabs * Used to add a product custom tab to product edit screen * @return void */ function woocommerce_product_write_panel_tabs(){ ?> <li class="custom_tab"> <a href="#custom_tab_data_ctabs"> <?php _e('Custom Tabs', 'GWP'); ?> </a> </li> <?php } /** * woocommerce_product_write_panels * Used to display a product custom tab content (fields) to product edit screen * @return void */ function woocommerce_product_write_panels() { global $post,$woocommerce; $fields = array( array( 'key' => 'custom_tabs_ids', 'label' => __( 'Select Custom Tabs', 'GWP' ), 'desc' => __( 'Start typing the Custom Tab name, Used for including custom tabs.', 'GWP' ) ), array( 'key' => 'exclude_custom_tabs_ids', 'label' => __( 'Select Global Tabs to exclude', 'GWP' ), 'desc' => __( 'Start typing the Custom Tab name. used for excluding global tabs.', 'GWP' ) ) ); ?> <div id="custom_tab_data_ctabs" class="panel woocommerce_options_panel"> <?php foreach ($fields as $f) { $tabs_ids = get_post_meta( $post->ID, $f['key'], true ); $_ids = ! empty( $tabs_ids ) ? array_map( 'absint', $tabs_ids ) : null; ?> <div class="options_group"> <p class="form-field custom_product_tabs"> <label for="custom_product_tabs"><?php echo $f['label']; ?></label> <select id="<?php echo $f['key']; ?>" name="<?php echo $f['key']; ?>[]" class="ajax_chosen_select_tabs" multiple="multiple" data-placeholder="<?php _e( 'Search for a custom tab…', 'GWP' ); ?>"> <?php if ( $_ids ) { foreach ( $_ids as $id ) { $tab_name = get_the_title($id); echo '<option value="' . esc_attr( $id ) . '" selected="selected">' . esc_html( $tab_name ) . '</option>'; } } ?> </select> <img class="help_tip" data-tip="<?php echo esc_attr($f['desc']); ?>" src="<?php echo $woocommerce->plugin_url(); ?>/assets/images/help.png" height="16" width="16" /> </p> </div> <?php } ?> </div> <?php add_action('admin_footer',array($this,'ajax_footer_js')); } /** * woocommerce_process_product_meta * used to save product custom tabs meta * @param int $post_id * @return void */ function woocommerce_process_product_meta( $post_id ) { foreach (array('exclude_custom_tabs_ids','custom_tabs_ids') as $key) { if (isset($_POST[$key])) update_post_meta( $post_id, $key, $_POST[$key]); else delete_post_meta( $post_id, $key); } } /** * woocommerce_json_custom_tabs * An AJAX handler to list tabs for tabs field * prints out json of {tab_id: tab_name} * @return void */ function woocommerce_json_custom_tabs(){ check_ajax_referer( 'search-products-tabs', 'security' ); header( 'Content-Type: application/json; charset=utf-8' ); $term = (string) urldecode(stripslashes(strip_tags($_GET['term']))); if (empty($term)) die(); $post_types = array($this->post_type); if ( is_numeric( $term ) ) { //by tab id $args = array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'post__in' => array(0, $term), 'fields' => 'ids' ); $args2 = array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'post_parent' => $term, 'fields' => 'ids' ); $posts = array_unique(array_merge( get_posts( $args ), get_posts( $args2 ))); } else { //by name $args = array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 's' => $term, 'fields' => 'ids' ); $posts = array_unique( get_posts( $args ) ); } $found_tabs = array(); if ( $posts ) foreach ( $posts as $post_id ) { $found_tabs[ $post_id ] = get_the_title($post_id); } $found_tabs = apply_filters( 'woocommerce_json_search_found_tabs', $found_tabs ); echo json_encode( $found_tabs ); die(); } /** * woocommerce_product_tabs * Used to add tabs to product view page * @param array $tabs * @return array */ function woocommerce_product_tabs($tabs){ global $post; //get global tabs $global_tabs = get_option('wc_'.$this->id.'_globals'); $global_tabs_ids = ! empty( $global_tabs ) ? array_map( 'absint', $global_tabs ) : array(); //get tabs to exclude from this product $exclude_tabs = get_post_meta( $post->ID, 'exclude_custom_tabs_ids', true ); $exclude_tabs_ids = ! empty($exclude_tabs ) ? array_map( 'absint', $exclude_tabs ) : array(); //get tabs to include with current product $product_tabs = get_post_meta( $post->ID, 'custom_tabs_ids', true ); $_ids = ! empty($product_tabs ) ? array_map( 'absint', $product_tabs ) : null; //combine global and product specific tabs and remove excluded tabs $_ids = array_merge((array)$_ids,(array)array_diff((array)$global_tabs_ids, (array)$exclude_tabs_ids)); if ($_ids){ //fix order $_ids = array_reverse($_ids); //loop over tabs and add them foreach ($_ids as $id) { $tabs['customtab_'.$id] = array( 'title' => get_the_title($id), 'priority' => 50, 'callback' => array($this,'render_tab'), 'content' => apply_filters('the_content',get_post_field( 'post_content', $id)) //this allows shortcodes in custom tabs ); } } return $tabs; } /** * render_tab * Used to render tabs on product view page * @param string $key * @param array $tab * @return void */ function render_tab($key,$tab){ global $post; echo '<h2>'.apply_filters('GWP_custom_tab_title',$tab['title'],$tab,$key).'</h2>'; echo apply_filters('GWP_custom_tab_content',$tab['content'],$tab,$key); } /** * custom_product_tabs_post_type * Register custom tabs Post Type * @return void */ function custom_product_tabs_post_type() { $labels = array( 'name' => _x( 'Product Tabs', 'Post Type General Name', 'GWP' ), 'singular_name' => _x( 'Product Tab', 'Post Type Singular Name', 'GWP' ), 'menu_name' => __( 'product Tabs', 'GWP' ), 'parent_item_colon' => __( '', 'GWP' ), 'all_items' => __( 'Product Tabs', 'GWP' ), 'view_item' => __( '', 'GWP' ), 'add_new_item' => __( 'Add Product Tab', 'GWP' ), 'add_new' => __( 'Add New', 'GWP' ), 'edit_item' => __( 'Edit Product Tab', 'GWP' ), 'update_item' => __( 'Update Product Tab', 'GWP' ), 'search_items' => __( 'Search Product Tab', 'GWP' ), 'not_found' => __( 'Not found', 'GWP' ), 'not_found_in_trash' => __( 'Not found in Trash', 'GWP' ), ); $args = array( 'label' => __( 'Product Tabs', 'GWP' ), 'description' => __( 'Custom Product Tabs', 'GWP' ), 'labels' => $labels, 'supports' => array( 'title', 'editor', ), 'hierarchical' => false, 'public' => true, 'show_ui' => true, 'show_in_menu' => 'edit.php?post_type=product', 'show_in_nav_menus' => false, 'show_in_admin_bar' => true, 'menu_position' => 5, 'menu_icon' => 'dashicons-feedback', 'can_export' => true, 'has_archive' => false, 'exclude_from_search' => true, 'publicly_queryable' => false, 'capability_type' => 'post', ); register_post_type( 'c_p_tab', $args ); } }//end GWP_Custom_Product_Tabs class. new GWP_Custom_Product_Tabs();Making it better
Well for those of you brave souls who made it this far, don’t worry we are not going to add anything but I do have some ideas on how we can improve this plugin by adding more features like the ability to remove default native WooCommerce tabs globally and on per product bases, reordering the tabs globally and on per product bases. Maybe feature like configure tabs based on product category, product tags ,based on sells count or on product stock count. The possibilities are endless.
Conclusion
Was this the simplest best way to create this kind of plugin with these features? No, I could have simply created a simple custom field in the custom tab post type and set it as global from there, but I did want to cover adding a custom WooCommerce settings tab and field type. I could have used a simple select drop-down field with the names of the tabs but I wanted to cover creating a custom product data meta-box tab with ajaxChosen field and some minor ajax handling. If you look back we covered:
- Adding WooCommerce Custom product tab.
- Custom WooCommerce product data meta-box tab.
- Custom WooCommerce settings tab.
- Custom WooCommerce Settings field type.
- ajaxChosen JavaScript trigger and server side ajax handler.
- And some other gems if you know how to read between the lines 🙂
As always, feedback is welcome.
61 Comments:
Mantis
Really nice plugins..Nice interface to use Global tab features. Checking how shortcodes working.
thanks
Mantis
I used Accordion from Light Shortcodes. It behave improperly.
Ryan
I’m looking or a solution for an apparel store. However sizing for infants, mens, women, shoe’s, etc… are all different. I would like the custom tab name that displays to just read Size Chart. How can I accomplish this while being able to create sizing pages for all those listed above and them having a unique identifying name on the product page for me to chose. As it is right now, whatever name I put when I create the product tab is what displays to the customer.
Ohad Raz
I see what you mean, To solve it you can add use an extra custom field on the tab custom post type with the name to display (in your case its “Size Chart”) and have the custom tab title be anything else you want.
Then just modify the
woocommerce_product_tabs
method to use the custom field as the title instead of the post title if it exists in theforeach
loop, something like this:just make sure the custom field is named
tab_display_title
Ohad Raz
Download the latest version from github and you are set.
blenki
Hi – thank you for this help – it has been very useful. However – I have tried doing the adjustment you detail above – and it doesn’t seem to override the original custom tab name. I am trying to achieve the same as Ryan – where by I have a delivery tab – which I just want to say Delivery – but admin can create many delivery tabs for small light items, large heavy ones etc – but the tab displayed to the user needs to just read Delivery – regardless. I have tried creating a custom post type (sorry – not too familiar with this) called tab_display_title and I’ve added your code above – but it doesn’t seem to pick it up. Any help appriciated.
Andres Valle
When i delete a custom tab, the tab still appearing in the product page, but empty and without the title.
Ohad Raz
did you remove it from the product page and global tabs?
Andres Valle
of course
Ohad Raz
I fixed it with a simple check if the tab custom post exists. Just download the latest version from github and let me know.
blenki
I apologize – I have just seen your latest post – and downloaded the version from GIT hub – works like a dream… Thank you very much.
bob
How do i change the priority of a tab. Can you explain how to use the custom fields and what can be entered.
Thank you
Ohad Raz
To change the title create a custom field on the custom tab post type edit page named
tab_display_title
and set the value to the desired title,to change the priority of the tab create a custom field on the custom tab post type edit page named
tab_priority
and set the value to the desired priority which defaults to 50, the lower value comes first.bob
Thank you for your quick response.
Sorry, but one more question. When I try to view the page that I set up for a TAB, I get a 404 but the tab works perfectly on the product pages. Is that normal?
Ohad Raz
Yep.
bob
Thank you for all your help, really do appreciate it.
Bob
Irfan
Thank you for the plugin Ohad Raz.
Plugin was installed successfully but when I created a new tab and tried to
enter tab’s name in search box it does not show any option to select just show’s
“Looking for my_tab”
Ohad Raz
I can only guess that you have some kind of php notice or error on the ajax request.
look at the response using your browsers console and search for the error.
kumbhanimehulhul Kumbhani
I am not able to display product specific description, specification. Can you please guide me how i can do this.
Ohad Raz
Humm… what have you tried?
Edgr
Hi Raz, I wanted to add a Specification tab for different products. Is there a way to have a text box to key in my specification for different products? Really appreciate your help
Brady
I added your plugin as quick way to get custom tabs in woocommerce. Works great so far.
——
Unfortunately, I do not see a way to edit the created custom tab per single product entry?
Are the tab contents static when creating them (global or not)? And what would be best way to accomplish getting dynamic content per tab per single product?
So basically, I need _custom content_ on each custom tab for each single product.
For instance I have a tab called “Community Overview” which hold detailed information on a hospital (which is the product). This differs for each product.. so custom tab content needs to change for every single one.
——
2nd question – not as important:
I reordered my tabs by using functions.php – and used the internal name created “customtab_3073” in this case:
add_filter( ‘woocommerce_product_tabs’, ‘woo_reorder_tabs’, 98 );
function woo_reorder_tabs( $tabs ) {
$tabs[‘description’][‘priority’] = 5; // description 1st
$tabs[‘customtab_3073’][‘priority’] = 10; // Custom Tab1 2nd (practice_setting) (GWP-custom woo tabs)
(……removed some code for brevity…..)
return $tabs;
}
Is there a better way of doing this instead of using the internal assigned tab names? Or is this okay way of doing it? Seems like it will mess up easily if your plugin changes something on the way it defines created tabs/etc?
thanks for your time and work.
Ohad Raz
Its simpler then you think,
Lets say you have a product named
item1
then you create a custom tab named “item1 content” and you include it only on the item1 edit screen. If you want the tab title to be something else just create a custom field namedtab_display_title
and set the value of the title you want.Brady
Never mind priority of tab question – I see your answer above re: custom field. That worked..
Tommy Mærsk
Is it possible, to make a tab with featured products, just like related products, already made one with related products, displaying 6 related products, however I cant find the codes for doing a tab with featured products? Anybody got info on that? Thanks
Ohad Raz
Yep you can use the Featured Products shortcode eg:
[featured_products per_page="12" columns="4"]
Brady
Maybe you misunderstand (or me you?)
If I add a custom tab and then edit a single product(s) under products, how do I add _unique content_ to each product for the _single_ custom tab I made?
There is no content pane for the tab(s) I made when I edit it the product..
For instance, on one of woo built-in tabs called “Description” you can add your description content when you edit that product. This of course differs for each product but remains same “named” tab.
I see no pane for content on tabs I create under edit product.
Only the content I added to tab when creating under Products/Product Tabs. And that remains the same no matter the product I add it to.
Static for all?I do no want to add a million custom tabs for each single product, either. This defeats most of the purpose as the name of tab would remain the same… (like ‘Description’ tab/content).
Ohad Raz
There is no pane per each product. But if you want you can create a custom field in the product edit screen and make a shortcode which will pull that custom field in the tab.
I made something like this for a client, in less then 5 minutes.
eherman24
Nice implementation. It would be nice to generate the tabs on the fly , from within the product creation screen.
Ohad Raz
Yep that would be nice 😉
jolajoola
Thanks aloooooooot for the plugin , why dont you upload it on wordpress.org , it is the best plugin ever
luis
Great plugin!
Regarding your solution for dynamic content per product:
“There is no pane per each product. But you can create a custom field in the product edit screen and make a shortcode which will pull that custom field in the tab.”
Can you show us how to do it. ?
Ohad Raz
I can, but that is outside the scope of this (already long and informative) tutorial 🙂
mikethecow
Hi Ohad
Fantastic work, many thanks for making your plugin available for free.
PLEASE explain briefly how to generate dynamic content via custom fields / shortcodes.
IE for each product I want to generate some shortcode driven content on a tab from an ID number pulled from a custom field…
I know this is outside the scope of this tutorial 😉
I am able to do this manually as there are only about 30 products but for larger product ranges would be a really useful element to explain for us.
Thanks again
Danilo Casati
If I upload PHP file to /plugins/ directory, I can’t activate the plugin.
It doesn’t appear in plugin list.
How can I upload it to WordPress?
Ohad Raz
Simply upload the plugin in its own folder.
saunakauf24
It seems to be very basic at the first look, but it does what it should. It also works with shortcodes from other plug-ins. So you can generate a product inquiry form with e.g. Fast Secure Contact Form.
Thank you for this great plugin!
Christian Hristov
After Woo 2.3.3 update this plugin is incompatible and broke some ajax related to products features. Disabling the plugin solve the misbehaving of WC.
saunakauf24
Since WooCommerce 2.3.3 it is no longer possible to add custum tabs to products. There is still the tab “Custom Tabs”, but no fields to enter anymore.
Could you check that, please?
Amrita
Please resolve the issue after WooCommerce 2.3.3.
Ohad Raz
Fixed the issue in the github repo. make sure to use version 1.2
saunakauf24
Works again like a charm.
Thank you very much, Ohad.
Stoyan Nakov
Thank you, I strongly recommend. You saved my time. I wish you Good luck
Valeriy Maksymiv
I got error in settings tab of the plugin: Warning: in_array() expects parameter 2 to be array, null given in path\wp-content\plugins\Simple_Custom_Product_Tabs.php on line 139
QueenEve
be sure you are copying the right code , when you copy and paste there is extra code copied from the source , be sure your code starts with <?php
/*
Plugin Name: GWP Custom Product Tabs
Plugin URI:
Description: A plugin to add Custom product tabs for WooCommerce
Version: 1.2
Author: Ohad Raz
and ends with
}//end GWP_Custom_Product_Tabs class.
new GWP_Custom_Product_Tabs();
and remove the codes before and after
wish this helps you
Jason Lee Neri
Do the fields end up in the database serialized? I am trying to find a solution to adding custom tabs and fields to JUST the back end. and i have one, but the fields are put into the database serialized so currently the tab is the meta_key and then the fields are an array for the meta_value. i would like to find a way to make my custom fields be their own meta keys so that importing and exporting will be easier using existing plugins.
ludovic
hello,
i have Warning: in_array() expects parameter 2 to be array, null given in C:\wamp\www\wp-content\plugins\GWP-Custom-Product-Tabs-master\Simple_Custom_Product_Tabs.php on line 139
???
I just copy the code from github
Ohad Raz
I just pushed an update to GitHub with a fix.
Thanks for reporting.
saunakauf24
Hello,
I just updated the plugin with the newest version from GitHub. Now my site is broken. If I try to invoke the login page, for example, the GitHub site of the plugin is displayed.
This leaves me with many ???
I have to deactivate the plugin by changing the filename extension via ftp
Now all my tabs aren’t working anymore 🙁
I hope you get this fixed very soon!
Ohad Raz
Don’t know what you did or how your site is setup but it work’s fine on my end. Also the only change was replacing “null” with “array” you can see for yourself https://github.com/bainternet/GWP-Custom-Product-Tabs/commit/78dde1ae3edeca3ac01ff684581c0209ddfaa0a7
Juha
Hi, This is exactly what I need but for some reason I can’t get it installed. Sorry for beeing a rookie, but I need this! Could somebody please slowly walk with me the installation process, so I will see what is missing. Many thanks in advance.
saunakauf24
Last time I downloaded the .php file it was a copy of the whole github page. I don’t know why and how this happened. Now I d’loaded the file once more, and everything works fine again.
Thanks for your immediate support!
QueenEve
I am trying to use the plugin with a newly installed woocommerce , but created tabs donot appear on the product page, is the plugin still working?
Ravi142
Hello Ohad,
Thank you for share wonderful plugin.
I have add addition code in above plugin structure.
When i add add_filter in _construct() then given Warning: call_user_func_array() expects parameter 1 to be a valid callback,
add_filter(‘woocommerce_stock_html’,’woocommerce_stock_html_mod’, 10, 1);
function woocommerce_stock_html_mod($availability_html){
global $product;
// Change Out of Stock Text
if ( $product->stock_status == ‘outofstock’ ) {
$availability_html = “Sorry, sold out! Email us for availability”;
}
return $availability_html;
}
Suggest me.
Thanks.
louie171
great plugin, does exactly what I need (global) tabs for all products
Jigar Shah
That’s Amazing… It’s looking extremely powerful WooCommerce Plugin. I like your way of idea. It’s unique and effective. Thanks for that.
Francisco Quiros
HI, that’s amazing! But i have a question.. i want to create a plugin to when i create a product with variations like colors, etc, i want to put accessories too, and u can select or don’t select the accessories when u have to buy it. What i have to do to make that? thanks!!
mechint
Hi Ohad Raz,
Thanks for this customiztion. Hats off!!
I have installed this plugin and custom tab is appearing, but not showing typing window for:
Select Custom Tabs
Select Global Tabs to exclude
as shown after ajax_footer_js code.
Thanks in advance!
daniel
oh no, i accidently found that snipped. Seems really good and lightweight. Only option, to customize content for each product is missing. Content of tabs are static.