/** * Render the product/global tab content. Templates are loaded in the * following order: * * theme / woocommerce / single-product / tabs / content-{tab-name-slug}.php * theme / woocommerce / single-product / tabs / content.php * woocommerce-tab-manager / templates / single-product / tabs / content.php * * $tab structure: * Array( * 'title' => (string) Tab title, * 'priority' => (string) Tab priority, * 'callback' => (mixed) callback function, * 'id' => (int) tab post identifier, * ) * * @access public * @since 1.0.5 * @global WC_Tab_Manager wc_tab_manager() * @global WC_Product $product * * @param string $key tab key, this is the sanitized tab title with possibly a numerical suffix to avoid key clashes * @param array $tab tab data */ function woocommerce_tab_manager_tab_content($key, $tab) { global $product; $tab = wc_tab_manager()->get_product_tab($product->id, $tab['id'], true); if ($tab) { // first look for a template specific for this tab $template_name = "single-product/tabs/content-{$tab['name']}.php"; $located = locate_template(array(trailingslashit(WC()->template_path()) . $template_name, $template_name)); // if not found, fallback to the general template if (!$located) { $template_name = 'single-product/tabs/content.php'; } wc_get_template($template_name, array('tab' => $tab), '', wc_tab_manager()->get_plugin_path() . '/templates/'); } }
/** * Add necessary admin scripts * @access public */ function wc_tab_manager_admin_enqueue_scripts() { // Get admin screen id $screen = get_current_screen(); // on the admin product page if ('product' == $screen->id || isset($_REQUEST['page']) && 'tab_manager' == $_REQUEST['page']) { wp_enqueue_script('wc_tab_manager_admin', wc_tab_manager()->get_plugin_url() . '/assets/js/admin/wc-tab-manager.min.js', array('jquery')); $wc_tab_manager_admin_params = array('remove_product_tab' => __('Remove this product tab?', WC_Tab_Manager::TEXT_DOMAIN), 'remove_label' => __('Remove', WC_Tab_Manager::TEXT_DOMAIN), 'click_to_toggle' => __('Click to toggle', WC_Tab_Manager::TEXT_DOMAIN), 'title_label' => __('Title', WC_Tab_Manager::TEXT_DOMAIN), 'title_description' => __('The tab title, this appears in the tab', WC_Tab_Manager::TEXT_DOMAIN), 'content_label' => __('Content', WC_Tab_Manager::TEXT_DOMAIN), 'ajax_url' => admin_url('admin-ajax.php'), 'get_editor_nonce' => wp_create_nonce('get-editor')); wp_localize_script('wc_tab_manager_admin', 'wc_tab_manager_admin_params', $wc_tab_manager_admin_params); } // we're going to make sortable boxes in our custom default tab layout page, the same as on the product edit page if (isset($_REQUEST['page']) && 'tab_manager' == $_REQUEST['page']) { wp_enqueue_script('woocommerce_admin'); wp_enqueue_script('jquery-ui-sortable'); wp_enqueue_script('media-upload'); wp_enqueue_script('thickbox'); $params = array('ajax_url' => admin_url('admin-ajax.php')); wp_enqueue_script('woocommerce_admin_meta_boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes.min.js', array('jquery', 'jquery-ui-datepicker', 'jquery-ui-sortable', 'accounting', 'round'), WC()->version); // keep the woocommerce_admin_meta_boxes script happy wp_localize_script('woocommerce_admin_meta_boxes', 'woocommerce_admin_meta_boxes', $params); } }
/** * Helper function to render the sortable tabs admin UI used both on the product * edit pages, as well as the custom Default Tab Layout admin page * * @access public * @param array $tabs tab data */ function wc_tab_manager_sortable_product_tabs($tabs) { global $typenow; $style = ''; ?> <style type="text/css"> #woocommerce-product-data ul.product_data_tabs li.product_tabs_tab a { <?php echo $style; ?> } .wc-metaboxes-wrapper .wc-metabox table.woocommerce_product_tab_data td label { display: inline; line-height: inherit; } .woocommerce_product_tabs .options_group label { float: left; padding: 0; width: 150px; } .woocommerce_product_tabs .options_group p { font-size: 12px; line-height: 24px; } .wc-metaboxes-wrapper .woocommerce_product_tabs .wc-metabox table td input { font-size: inherit; } .woocommerce_product_tabs .description { margin: 0 0 0 7px; padding: 0; } .woocommerce_product_tabs .options_group .form-field label { font-weight:bold; } .wc-metaboxes-wrapper .toolbar { text-align:right; } .wc-metaboxes-wrapper .toolbar label { line-height:22px; } .wc-metaboxes-wrapper .toolbar input { margin-top:0; } .wc-metaboxes-wrapper .toolbar input { margin-top:0; } #woocommerce_product_tabs .quicktags-toolbar input { background-color: #EEEEEE; background-image: -moz-linear-gradient(center bottom , #E3E3E3, #FFFFFF); border: 1px solid #C3C3C3; border-radius: 3px 3px 3px 3px; color: #464646; display: inline-block; font: 12px/18px Arial,Helvetica,sans-serif normal; margin: 2px 1px 4px; width:auto; min-width: 26px; padding: 2px 4px; float: none; } #woocommerce_product_tabs .wp-editor-area { -moz-box-sizing: border-box; border: 0 none; font-family: Consolas,Monaco,monospace; line-height: 150%; outline: medium none; padding: 10px; resize: vertical; font-size:inherit; color:#333333; } #wc_tab_manager_block { background-color: white; height: 100%; left: 0; opacity: 0.6; position: absolute; top: 0; width: 100%; display:none; } </style> <div id="woocommerce_product_tabs" class="panel wc-metaboxes-wrapper"> <p class="toolbar"> <?php if ('product' == $typenow) { global $post; $override_tab_layout = get_post_meta($post->ID, '_override_tab_layout', true); ?> <label for="_override_tab_layout"><?php _e('Override default tab layout:', WC_Tab_Manager::TEXT_DOMAIN); ?> </label> <input type="checkbox" name="_override_tab_layout" id="_override_tab_layout" <?php checked($override_tab_layout, "yes"); ?> /> <?php } ?> <a href="#" class="close_all"><?php _e('Close all', WC_Tab_Manager::TEXT_DOMAIN); ?> </a> / <a href="#" class="expand_all"><?php _e('Expand all', WC_Tab_Manager::TEXT_DOMAIN); ?> </a> </p> <div style="position:relative;"> <div class="woocommerce_product_tabs wc-metaboxes"> <?php // the core woocommerce tabs $core_tabs = wc_tab_manager()->get_core_tabs(); // get any global tabs $global_tabs = array(); $posts = get_posts(array('numberposts' => -1, 'post_type' => 'wc_product_tab', 'post_parent' => 0, 'post_status' => 'publish', 'suppress_filters' => false)); foreach ($posts as $_post) { $tab = array('id' => $_post->ID, 'position' => 0, 'type' => 'global', 'title' => $_post->post_title); list($tab['content']) = explode("\n", wordwrap(str_replace("\n", "", strip_shortcodes(strip_tags($_post->post_content))), 155)); // content excerpt if (strlen($_post->post_content) > 155) { $tab['content'] .= '...'; } if ($tab['content']) { $tab['content'] .= ' - '; } $tab['content'] .= ' <a href="' . get_edit_post_link($_post->ID) . '">' . __('Edit Global Tab Content', WC_Tab_Manager::TEXT_DOMAIN) . '</a>'; $global_tabs['global_tab_' . $_post->ID] = $tab; } // get any 3rd party tabs $third_party_tabs = array(); foreach (wc_tab_manager()->get_third_party_tabs() as $id => $tab) { if (!isset($tab['ignore']) || false == $tab['ignore']) { $third_party_tabs[$id] = array('id' => $tab['id'], 'position' => 0, 'type' => 'third_party', 'title' => $tab['title'], 'description' => $tab['description']); } } // if no tabs are set (for this product) try defaulting to the default tab layout, if it exists if (!is_array($tabs)) { $tabs = get_option('wc_tab_manager_default_layout', false); } // if no default tab layout either, default to the core + 3rd party tabs if (!is_array($tabs)) { $tabs = $core_tabs + $third_party_tabs; } else { // otherwise, get the content and title for any product/global tabs, and verify that any global/3rd party tabs still exist foreach ($tabs as $id => $tab) { if ('global' == $tab['type']) { // global tab: get an excerpt of the content to display if any, or if the tab has been removed or trashed, remove it from view if (isset($global_tabs[$id])) { $tabs[$id]['title'] = $global_tabs[$id]['title']; $tabs[$id]['content'] = $global_tabs[$id]['content']; } else { // global tab is gone unset($tabs[$id]); } } elseif ('third_party' == $tab['type']) { // 3rd party tab, does the plugin still exist? if (isset($third_party_tabs[$id])) { $tabs[$id]['title'] = $third_party_tabs[$id]['title']; $tabs[$id]['description'] = $third_party_tabs[$id]['description']; } else { unset($tabs[$id]); } } elseif ('product' == $tab['type']) { // get any custom product tab content from the underlying post $tab_post = get_post($tab['id']); if ($tab_post && 'publish' == $tab_post->post_status) { $tabs[$id]['content'] = $tab_post->post_content; $tabs[$id]['title'] = $tab_post->post_title; } else { // product tab is gone unset($tabs[$id]); } } } } // markup for all core, global and 3rd party tabs will be rendered, and if not currently added to the product, they will be hidden until added $combined_tabs = array_merge($core_tabs, $global_tabs, $third_party_tabs, $tabs); $i = 0; foreach ($combined_tabs as $id => $tab) { $position = $tab['position']; $active = isset($tabs[$id]); // for the core tabs, even if the title is changed, keep the displayed name the same in the bar so there's less confusion $name = 'core' == $tab['type'] ? $core_tabs[$id]['title'] : $tab['title']; // handle the Reviews tab specially by cutting off the ' (%d)' which looks like garbage in the sortable tab list if ('core' == $tab['type'] && 'reviews' == $tab['id']) { $name = substr($name, 0, -4); } ?> <div class="woocommerce_product_tab wc-metabox <?php echo sanitize_html_class('product_tab_' . $tab['type']); ?> <?php echo sanitize_html_class($id); ?> " rel="<?php echo esc_attr($position); ?> " <?php if (!$active) { echo ' style="display:none;"'; } ?> > <h3> <button type="button" class="remove_row button"><?php _e('Remove', WC_Tab_Manager::TEXT_DOMAIN); ?> </button> <div class="handlediv" title="<?php _e('Click to toggle', WC_Tab_Manager::TEXT_DOMAIN); ?> "></div> <strong class="product_tab_name"><?php echo esc_html($name); ?> </strong> </h3> <table class="woocommerce_product_tab_data wc-metabox-content"> <tr> <td> <?php if (isset($core_tabs[$id]['description'])) { ?> <p><em><?php echo esc_html($core_tabs[$id]['description']); ?> </em></p> <?php } ?> <?php if ('third_party' == $tab['type']) { ?> <p><em><?php echo esc_html($tab['description'] ? $tab['description'] : __('The title/content for this tab will be provided by a third party plugin', WC_Tab_Manager::TEXT_DOMAIN)); ?> </em></p> <?php } ?> <div class="options_group"> <?php if ('third_party' != $tab['type']) { ?> <p class="form-field product_tab_title_field"> <label for="product_tab_title_<?php echo $i; ?> "><?php _e('Title', WC_Tab_Manager::TEXT_DOMAIN); ?> </label> <?php if ('global' == $tab['type']) { ?> <span><?php echo esc_html($tab['title']); ?> </span> <input type="hidden" name="product_tab_title[<?php echo $i; ?> ]" value="<?php echo esc_attr($tab['title']); ?> " /> <?php } else { ?> <input type="text" value="<?php echo esc_attr($tab['title']); ?> " id="product_tab_title_<?php echo $i; ?> " name="product_tab_title[<?php echo $i; ?> ]" class="short product_tab_title"> <span class="description"><?php esc_html_e("The tab title, this appears in the tab", WC_Tab_Manager::TEXT_DOMAIN); ?> </span> <?php } ?> </p> <?php } ?> <?php if (isset($core_tabs[$id]['heading']) && $core_tabs[$id]['heading']) { ?> <p class="form-field product_tab_heading_field"> <label for="product_tab_heading_<?php echo $i; ?> "><?php _e('Heading', WC_Tab_Manager::TEXT_DOMAIN); ?> </label> <input type="text" value="<?php echo esc_attr($tab['heading']); ?> " id="product_tab_heading_<?php echo $i; ?> " name="product_tab_heading[<?php echo $i; ?> ]" class="short"> <span class="description"><?php esc_html_e("The tab heading, this appears just before the tab content", WC_Tab_Manager::TEXT_DOMAIN); ?> </span> </p> <?php } ?> <?php if ('global' == $tab['type']) { ?> <p class="form-field product_tab_heading_field"> <label for="product_tab_content_<?php echo $i; ?> "><?php _e('Content', WC_Tab_Manager::TEXT_DOMAIN); ?> </label> <span><?php echo wp_kses_post($tab['content']); ?> </span> </p> <?php } ?> </div> <?php if ('product' == $tab['type'] && isset($tab['content'])) { ?> <?php /* Because the editor is within a movable block, we must disable the rich visual MCE editor, and use only the quicktags editor */ wp_editor($tab['content'], 'producttabcontent' . $i, array('textarea_name' => 'product_tab_content[' . $i . ']', 'tinymce' => false, 'textarea_rows' => 10)); ?> <?php } ?> <input type="hidden" name="product_tab_active[<?php echo $i; ?> ]" class="product_tab_active" value="<?php echo esc_attr($active); ?> " /> <input type="hidden" name="product_tab_position[<?php echo $i; ?> ]" class="product_tab_position" value="<?php echo esc_attr($position); ?> " /> <input type="hidden" name="product_tab_type[<?php echo $i; ?> ]" class="product_tab_type" value="<?php echo esc_attr($tab['type']); ?> " /> <input type="hidden" name="product_tab_id[<?php echo $i; ?> ]" class="product_tab_id" value="<?php echo esc_attr($tab['id']); ?> " /> </td> </tr> </table> </div> <?php $i++; } ?> </div> <?php $select_tabs = $core_tabs + $global_tabs + $third_party_tabs; ?> <p class="toolbar"> <button type="button" class="button button-primary add_product_tab"><?php _e('Add', WC_Tab_Manager::TEXT_DOMAIN); ?> </button> <select name="product_tab" class="product_tab"> <?php if ('product' == $typenow) { ?> <option value=""><?php _e('Custom Tab', WC_Tab_Manager::TEXT_DOMAIN); ?> </option><?php } ?> <?php foreach ($select_tabs as $id => $tab) { echo '<option value="' . esc_attr($id) . '">' . esc_html__($tab['title'], WC_Tab_Manager::TEXT_DOMAIN) . '</option>'; } ?> </select> </p> <div class="clear"></div> <div id="wc_tab_manager_block"></div> </div> </div> <?php }
function init_woocommerce_tab_manager() { /** * The WooCommerce Tab Manager allows product tabs to be ordered, created, * updated, and removed. * * For the purposes of this plugin the tabs available for management can be * broken down into the following categories: * * - core tabs: The default Description, Additional Information and Reviews tabs * - global tabs: These are tabs which can be added to any product * - 3rd party tabs: Tabs added by 3rd party plugins, ie WooCommerce Product Enquiry Form * - product level tabs: Tabs added to a particular product * * Note: the terminology surrounding "product tabs" is somewhat confusing in * this plugin as I use the term to refer both to any tab which can be added to * a product, as well as the "product level tabs". Hopefully the code context * makes it clear enough though. * * Global and Product Level tabs themselves are represented by a new post_type * named wc_product_tab. * * Tabs can be configured globally from within the Tab Manager submenu, and * overridden at the individual product level. At the global level the tab layout * is stored as a wordpress option. At the product level, the tab layout is * stored in the exact same structure, as a post meta. * * Database: * - Option named 'wc_tab_manager_db_version' with the current plugin version * - Option named 'wc_tab_manager_default_layout' with the global default layout * - Postmeta named '_override_tab_layout' attached to products, indicating * whether the global default layout is to be used * - Postmeta named '_product_tabs' attached to products, with the product-level * tab layout (same structure as the global default layout) * * @since 1.0 */ class WC_Tab_Manager extends SV_WC_Plugin { /** Plugin version */ const VERSION = '1.3.0'; /** @var WC_Tab_Manager single instance of this plugin */ protected static $instance; /** The plugins id, used for various slugs and such */ const PLUGIN_ID = 'tab_manager'; /** plugin text domain */ const TEXT_DOMAIN = 'woocommerce-tab-manager'; /** * Local cache array of product tabs, keyed off product id * @var array */ private $product_tabs = array(); /** * Array of third party tabs * @var array */ private $third_party_tabs; /** * @var boolean Temporary member used to defer plugin installation, until the plugin framework supports this */ private $wp_loaded_action = false; /** * Setup main plugin class * * @since 1.0 * @see SV_WC_Plugin::__construct() */ public function __construct() { parent::__construct(self::PLUGIN_ID, self::VERSION, self::TEXT_DOMAIN); $this->includes(); add_action('init', array($this, 'init')); add_action('init', array($this, 'include_template_functions'), 25); add_filter('woocommerce_product_tabs', array($this, 'setup_tabs'), 98); // allow the use of shortcodes within the tab content add_filter('woocommerce_tab_manager_tab_panel_content', 'do_shortcode'); } /** * Load plugin text domain. * * @since 1.1 * @see SV_WC_Plugin::load_translation() */ public function load_translation() { load_plugin_textdomain('woocommerce-tab-manager', false, dirname(plugin_basename($this->get_file())) . '/i18n/languages'); } /** * Init WooCommerce Tab Manager */ public function init() { // Init user roles $this->init_user_roles(); // Init WooCommerce Product Tab taxonomy $this->init_taxonomy(); } /** * Function used to init WooCommerce Tab Manager Template Functions, making them pluggable by plugins and themes */ public function include_template_functions() { require_once $this->get_plugin_path() . '/woocommerce-tab-manager-template.php'; } /** * Files required by both the admin and frontend */ private function includes() { if (is_admin()) { $this->admin_includes(); } if (defined('DOING_AJAX') && DOING_AJAX) { $this->ajax_includes(); } } /** * Include required admin files */ private function admin_includes() { require_once $this->get_plugin_path() . '/admin/woocommerce-tab-manager-admin-init.php'; // Admin section } /** * Include required ajax files. */ private function ajax_includes() { require_once $this->get_plugin_path() . '/woocommerce-tab-manager-ajax.php'; // Ajax functions for admin and the front-end } /** * Init WooCommerce Tab Manager user role */ private function init_user_roles() { global $wp_roles; if (class_exists('WP_Roles') && !isset($wp_roles)) { $wp_roles = new WP_Roles(); } // it's fine if this gets executed more than once if (is_object($wp_roles)) { $wp_roles->add_cap('shop_manager', 'manage_woocommerce_tab_manager'); $wp_roles->add_cap('administrator', 'manage_woocommerce_tab_manager'); } } /** * Init custom post type */ private function init_taxonomy() { // bail if the post type was already registered if (post_type_exists('wc_product_tab')) { return; } if (current_user_can('manage_woocommerce')) { $show_in_menu = 'woocommerce'; } else { $show_in_menu = true; } register_post_type("wc_product_tab", array('labels' => array('name' => __('Tabs', self::TEXT_DOMAIN), 'singular_name' => __('Tab', self::TEXT_DOMAIN), 'menu_name' => _x('Tab Manager', 'Admin menu name', self::TEXT_DOMAIN), 'add_new' => __('Add Tab', self::TEXT_DOMAIN), 'add_new_item' => __('Add New Tab', self::TEXT_DOMAIN), 'edit' => __('Edit', self::TEXT_DOMAIN), 'edit_item' => __('Edit Tab', self::TEXT_DOMAIN), 'new_item' => __('New Tab', self::TEXT_DOMAIN), 'view' => __('View Tabs', self::TEXT_DOMAIN), 'view_item' => __('View Tab', self::TEXT_DOMAIN), 'search_items' => __('Search Tabs', self::TEXT_DOMAIN), 'not_found' => __('No Tabs found', self::TEXT_DOMAIN), 'not_found_in_trash' => __('No Tabs found in trash', self::TEXT_DOMAIN)), 'description' => __('This is where you can add new tabs that you can add to products.', self::TEXT_DOMAIN), 'public' => true, 'show_ui' => true, 'capability_type' => 'post', 'capabilities' => array('publish_posts' => 'manage_woocommerce_tab_manager', 'edit_posts' => 'manage_woocommerce_tab_manager', 'edit_others_posts' => 'manage_woocommerce_tab_manager', 'delete_posts' => 'manage_woocommerce_tab_manager', 'delete_others_posts' => 'manage_woocommerce_tab_manager', 'read_private_posts' => 'manage_woocommerce_tab_manager', 'edit_post' => 'manage_woocommerce_tab_manager', 'delete_post' => 'manage_woocommerce_tab_manager', 'read_post' => 'manage_woocommerce_tab_manager'), 'publicly_queryable' => true, 'exclude_from_search' => true, 'show_in_menu' => $show_in_menu, 'hierarchical' => false, 'rewrite' => false, 'query_var' => false, 'supports' => array('title', 'editor'), 'show_in_nav_menus' => false)); } /** * Return the plugin action links. This will only be called if the plugin * is active. * * @since 1.0.9 * @param array $actions associative array of action names to anchor tags * @return array associative array of plugin action links */ public function add_plugin_action_links($actions) { $custom_actions = array('configure' => sprintf('<a href="%s">%s</a>', admin_url('edit.php?post_type=wc_product_tab'), __('Configure', self::TEXT_DOMAIN)), 'docs' => sprintf('<a href="%s">%s</a>', 'http://docs.woothemes.com/document/tab-manager/', __('Docs', self::TEXT_DOMAIN)), 'support' => sprintf('<a href="%s">%s</a>', 'http://support.woothemes.com/', __('Support', self::TEXT_DOMAIN)), 'review' => sprintf('<a href="%s">%s</a>', $this->get_review_url(), __('Write a Review', self::TEXT_DOMAIN))); // add the links to the front of the actions list return array_merge($custom_actions, $actions); } /** Frontend methods ******************************************************/ /** * Organizes the product tabs as configured within the Tab Manager * * $tabs structure: * Array( * id => Array( * 'title' => (string) Tab title, * 'priority' => (string) Tab priority, * 'callback' => (mixed) callback function, * ) * ) * * @since 1.0.5 * @param array $tabs array representing the product tabs * @return array representing the product tabs */ public function setup_tabs($tabs) { global $product; // first off, make sure that we're dealing with an array rather than null if (is_null($tabs)) { $tabs = array(); } $new_tabs = $tabs; $product_tabs = isset($product->id) ? $this->get_product_tabs($product->id) : null; // if product tabs have been configured for this product or globally (otherwise, allow default behavior) if (is_array($product_tabs)) { // start fresh $new_tabs = array(); // unhook and load any third party tabs that have been added by this point $third_party_tabs = $this->get_third_party_tabs($tabs); foreach ($product_tabs as $key => $tab) { $priority = ($tab['position'] + 1) * 10; $tab_id = $tab['id']; if ('core' == $tab['type']) { // the core tabs can be suppressed for a variety of reasons: description tab due to no content, attributes tab due to no attributes, etc if (!isset($tabs[$tab_id])) { continue; } // set the review (comment) count for the reviews tab, if they used the '%d' substitution if ('reviews' == $tab['id'] && false !== strpos($tab['title'], '%d')) { $tab['title'] = str_replace('%d', get_comments_number($product->id), $tab['title']); } // add the core tab to the new tab set $new_tabs[$tab_id] = array('title' => $tab['title'], 'priority' => $priority, 'callback' => $tabs[$tab_id]['callback']); // handle core tab headings (displays just before the tab content) if ('additional_information' == $tab_id) { add_filter('woocommerce_product_additional_information_heading', array($this, 'core_tab_heading')); } elseif ('description' == $tab_id) { add_filter('woocommerce_product_description_heading', array($this, 'core_tab_heading')); } } elseif ('third_party' == $tab['type']) { // third-party provided tab: ensure it's still available if (!isset($third_party_tabs[$key]) || isset($third_party_tabs[$key]['ignore']) && true == $third_party_tabs[$key]['ignore']) { continue; } // add the 3rd party tab in with the new priority $new_tabs[$tab_id] = $third_party_tabs[$key]; $new_tabs[$tab_id]['priority'] = $priority; } else { // product/global tabs // skip any global/product tabs that have been deleted $tab_post = get_post($tab_id); if (!$tab_post || 'publish' != $tab_post->post_status || !$tab_post->post_title) { continue; } $new_tabs[$tab['name']] = array('title' => $tab_post->post_title, 'priority' => $priority, 'callback' => 'woocommerce_tab_manager_tab_content', 'id' => $tab['id']); } } // finally add in any non-managed 3rd party tabs with their own priority foreach ($third_party_tabs as $key => $tab) { if (isset($tab['ignore']) && true == $tab['ignore']) { $new_tabs[$key] = $tab; } } } return apply_filters('wc_tab_manager_product_tabs', $new_tabs); } /** * Filter to modify the Description and Additional Information core tab headings. * The heading is not what shows up in the "tab" itself, this is the * heading for the tab content area. * * @param string $heading the tab heading * @return string the tab heading */ public function core_tab_heading($heading) { global $product; $tabs = $this->get_product_tabs($product->id); $current_filter = current_filter(); if ('woocommerce_product_additional_information_heading' == $current_filter) { return $tabs['core_tab_additional_information']['heading']; } elseif ('woocommerce_product_description_heading' == $current_filter) { return $tabs['core_tab_description']['heading']; } return $heading; } /** Helper methods ******************************************************/ /** * Main Tab Manager Instance, ensures only one instance is/can be loaded * * @since 1.2.0 * @see wc_tab_manager() * @return WC_Tab_Manager */ public static function instance() { if (is_null(self::$instance)) { self::$instance = new self(); } return self::$instance; } /** * Get any third party tabs which have been added via the * woocommerce_product_tabs action. Any third party tabs so found are collected * so they can be re-added by the manager in the appropriate order. In the * admin a human readable title is automatically generated (allowing for * automatic integration of 3rd party plugin tabs) and three filters are * fired to allow for improved integration with customized titles/descriptions: * * - woocommerce_tab_manager_integration_tab_allowed: allows plugin to mark tab as not available for management, its priority will not be modified and it will not appear within the Tab Manager Admin UI * - woocommerce_tab_manager_integration_tab_title: allows plugin to provide a more descriptive tab title to display within the Tab Manager Admin UI * - woocommerce_tab_manager_integration_tab_description: allows plugin to provide a description to display within the Tab Manager Admin UI * * $tabs structure: * Array( * key => Array( * 'title' => (string) Tab title, * 'priority' => (string) Tab priority, * 'callback' => (mixed) callback function, * 'description' => (string) An optional tab description added by this method to the return array, * 'ignore' => (boolean) Optional marker indicating this tab is not managed by the Tab Manager plugin and added by this method to the return array, * 'id' => (string) the original tab key, * ) * ) * Where key is: third_party_tab_{id} * * @since 1.0.5 * @param array $tabs optional array representing the product tabs * @return array representing the product tabs */ public function get_third_party_tabs($tabs = null) { global $wp_filter; if (is_null($this->third_party_tabs)) { // gather the tabs if not provided if (is_null($tabs)) { // In WC 2.1+ the woocommerce_default_product_tabs filter (which // requires a global $post/$product) is hooked into from the admin // so unhook to avoid a fatal error (has no effect in pre WC 2.1) if (is_admin()) { remove_filter('woocommerce_product_tabs', 'woocommerce_default_product_tabs'); remove_filter('woocommerce_product_tabs', 'woocommerce_sort_product_tabs', 99); } $tabs = apply_filters('woocommerce_product_tabs', array()); } $this->third_party_tabs = array(); // remove the core tabs (if any) leaving only 3rd party tabs (if any) unset($tabs['additional_information'], $tabs['reviews'], $tabs['description']); foreach ($tabs as $key => $tab) { // is this tab available for management by the Tab Manager plugin? if (apply_filters('woocommerce_tab_manager_integration_tab_allowed', true, $tab)) { if (is_admin()) { if (!isset($tab['title']) || !$tab['title']) { // on the off chance that the 3rd party tab doesn't have a title, provide it a default one based on the callback so it can be identified within the admin // get a title for the tab. Default to humanizing the function name, or class name if (is_array($tab['callback'])) { $tab_title = is_object($tab['callback'][0]) ? get_class($tab['callback'][0]) : $tab['callback'][0]; } else { $tab_title = (string) $tab['callback']; } $tab_title = ucwords(str_replace('_', ' ', $tab_title)); $tab_title = str_ireplace(array('woocommerce', 'wordpress'), array('WooCommerce', 'WordPress'), $tab_title); // fix some common words $tab['title'] = $tab_title; } // improved 3rd party integration by allowing plugins to provide a more descriptive title/description for their tabs $tab['title'] = apply_filters('woocommerce_tab_manager_integration_tab_title', $tab['title'], $tab); $tab['description'] = apply_filters('woocommerce_tab_manager_integration_tab_description', '', $tab); } $tab['id'] = $key; } else { // this tab is not managed by the Tab Manager, so mark it as such $tab['ignore'] = true; } // save the tab $this->third_party_tabs['third_party_tab_' . $key] = $tab; } } return $this->third_party_tabs; } /** * Get the default core tabs datastructure * * @return array the core tabs */ public function get_core_tabs() { // the core woocommerce tabs $core_tabs = array('core_tab_description' => array('id' => 'description', 'position' => 0, 'type' => 'core', 'title' => __('Description', 'woocommerce'), 'description' => __('Displays the product content set in the main content editor.', 'woocommerce'), 'heading' => __('Product Description', 'woocommerce')), 'core_tab_additional_information' => array('id' => 'additional_information', 'position' => 1, 'type' => 'core', 'title' => __('Additional Information', 'woocommerce'), 'description' => __('Displays the product attributes and properties configured in the Product Data panel.', 'woocommerce'), 'heading' => __('Additional Information', 'woocommerce')), 'core_tab_reviews' => array('id' => 'reviews', 'position' => 2, 'type' => 'core', 'title' => __('Reviews (%d)', 'woocommerce'), 'description' => __('Displays the product review form and any reviews. Use %d in the Title to substitute the number of reviews for the product.', 'woocommerce'))); return $core_tabs; } /** * Gets the product tabs (if any) for the identified product. If not * configured at the product level, the default layout (if any) will be * returned. * * returned tabs structure: * Array( * key => Array( * 'position' => (int) 0-indexed ordered position from the Tab Manager Admin UI, * 'type' => (string) one of 'core', 'global', 'third_party' or 'product', * 'id' => (string) Tab identifier, ie 'description', 'reviews', 'additional_information' for the core tabs, post id for product/global, and woocommerce_product_tabs key for third party tabs, * 'title' => (string) The tab title to display on the frontend (not used for 3rd party tabs, though it could be), * 'heading' => (string) Tab heading (core description/additional_information tabs only), * 'name' => (string) Product/Global tabs only, this is the sanitized title, and is used to key the tab in the final woocommerce tab data structure, * ) * ) * Where key is: {type}_tab_{id} * * @param int $product_id product identifier * * @return array product tabs data */ public function get_product_tabs($product_id) { if (!isset($this->product_tabs[$product_id])) { $override_tab_layout = get_post_meta($product_id, '_override_tab_layout', true); if ('yes' == $override_tab_layout) { // product defines its own tab layout? $this->product_tabs[$product_id] = get_post_meta($product_id, '_product_tabs', true); } else { // otherwise, get the default layout if any $this->product_tabs[$product_id] = get_option('wc_tab_manager_default_layout', false); } } return $this->product_tabs[$product_id]; } /** * Gets the product tab or null if the tab cannot be found * * @param int $product_id product identifier * @param int $tab_id tab identifier * @param boolean $get_the_content whether to get the tab content and title * * @return array tab array, or null */ public function get_product_tab($product_id, $tab_id, $get_the_content = false) { $tab = null; // load the tabs $this->get_product_tabs($product_id); if (is_array($this->product_tabs[$product_id])) { foreach ($this->product_tabs[$product_id] as $id => $tab) { if ($tab['id'] == $tab_id) { // get the tab content, if needed if ($get_the_content && !isset($tab['content'])) { $tab_post = get_post($tab_id); $content = apply_filters('the_content', $tab_post->post_content); $content = str_replace(']]>', ']]>', $content); $this->product_tabs[$product_id][$id]['content'] = $content; $this->product_tabs[$product_id][$id]['title'] = $tab_post->post_title; } $tab = $this->product_tabs[$product_id][$id]; break; } } } return apply_filters('wc_tab_manager_get_product_tab', $tab, $product_id, $tab_id, $get_the_content); } /** * Returns the plugin name, localized * * @since 1.1 * @see SV_WC_Plugin::get_plugin_name() * @return string the plugin name */ public function get_plugin_name() { return __('WooCommerce Tab Manager', self::TEXT_DOMAIN); } /** * Returns __FILE__ * * @since 1.1 * @see * @return string the full path and filename of the plugin file */ protected function get_file() { return __FILE__; } /** * Gets the plugin configuration URL * * @since 1.1 * @see SV_WC_Plugin::get_settings_url()() * @see SV_WC_Plugin::get_settings_link() * @param string $plugin_id optional plugin identifier. Note that this can be a * sub-identifier for plugins with multiple parallel settings pages * (ie a gateway that supports both credit cards and echecks) * @return string plugin settings URL */ public function get_settings_url($plugin_id = null) { return admin_url('edit.php?post_type=wc_product_tab'); } /** * Gets the plugin documentation URL * * @since 1.3.0 * @see SV_WC_Plugin::get_documentation_url() * @return string */ public function get_documentation_url() { return 'http://docs.woothemes.com/document/tab-manager/'; } /** * Gets the plugin support URL * * @since 1.3.0 * @see SV_WC_Plugin::get_support_url() * @return string */ public function get_support_url() { return 'http://support.woothemes.com/'; } /** * Returns true if on the admin tab configuration page * * @since 1.0.1 * @return boolean true if on the admin plugin settings page */ public function is_plugin_settings() { return isset($_GET['post_type']) && 'wc_product_tab' == $_GET['post_type']; } /** Lifecycle methods ******************************************************/ /** * Run every time. Used since the activation hook is not executed when updating a plugin * * @see SV_WC_Plugin::install() */ protected function install() { global $wpdb; // check for a pre 1.1 version $legacy_version = get_option('wc_tab_manager_db_version'); if (false !== $legacy_version) { // upgrade path from previous version, trash old version option delete_option('wc_tab_manager_db_version'); // upgrade path $this->upgrade($legacy_version); // and we're done return; } // any Custom Product Lite Tabs? $results = $wpdb->get_results("SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key='frs_woo_product_tabs'"); // prepare the core tabs $core_tabs = $this->get_core_tabs(); foreach ($core_tabs as $id => $tab) { unset($core_tabs[$id]['description']); } // foreach product with a custom lite tab foreach ($results as $result) { $old_tabs = maybe_unserialize($result->meta_value); $new_tabs = array('core_tab_description' => $core_tabs['core_tab_description'], 'core_tab_additional_information' => $core_tabs['core_tab_additional_information']); // keep track of tab names to avoid clashes $found_names = array('description' => 1, 'additional_information' => 1, 'reviews' => 1); foreach ($old_tabs as $tab) { if ($tab['title'] && $tab['content']) { // create the product tab $new_tab = array('position' => count($new_tabs), 'type' => 'product'); $new_tab_data = array('post_title' => $tab['title'], 'post_content' => $tab['content'], 'post_status' => 'publish', 'ping_status' => 'closed', 'post_author' => get_current_user_id(), 'post_type' => 'wc_product_tab', 'post_parent' => $result->post_id, 'post_password' => uniqid('tab_')); // create the post and get the id $id = wp_insert_post($new_tab_data); $new_tab['id'] = $id; // determine the unique tab name $tab_name = sanitize_title($tab['title']); if (!isset($found_names[$tab_name])) { $found_names[$tab_name] = 1; } else { $found_names[$tab_name]++; } if ($found_names[$tab_name] > 1) { $tab_name .= '-' . ($found_names[$tab_name] - 1); } $new_tab['name'] = $tab_name; // tab is complete $new_tabs['product_tab_' . $id] = $new_tab; } } // add the core reviews tab on at the end $new_tabs['core_tab_reviews'] = $core_tabs['core_tab_reviews']; $new_tabs['core_tab_reviews']['position'] = count($new_tabs) - 1; if (count($new_tabs) > 3) { // if we actually had any product tabs add_post_meta($result->post_id, '_product_tabs', $new_tabs, true); add_post_meta($result->post_id, '_override_tab_layout', 'yes', true); } } } /** * Run when plugin version number changes * * @see SV_WC_Plugin::upgrade() */ protected function upgrade($installed_version) { global $wpdb; if (version_compare($installed_version, "1.0.4.1", '<=')) { // in this version and before: // * custom product lite tabs were imported but their status was set // to 'future' meaning they appeared in the Tab Manager menu, but // not at the product level // * product tab layout had 'tab_name' rather than 'name' for imported // custom product lite tabs // * imported custom product lite tabs attached to products did not // have the '_override_tab_layout' meta set $tabs = get_posts(array('numberposts' => '', 'post_type' => 'wc_product_tab', 'nopaging' => true, 'post_status' => 'future')); if (is_array($tabs)) { foreach ($tabs as $tab) { // make the tab post status 'publish' $update_tab = array('ID' => $tab->ID, 'post_status' => 'publish'); wp_update_post($update_tab); add_post_meta($tab->ID, '_migrated_future', 'yes'); // mark the tab as migrated, in case we need to reference them one day // fix the product tab layout 'tab_name' field, which should be 'name' $fixed = false; $product_tabs = get_post_meta($tab->post_parent, '_product_tabs', true); foreach ($product_tabs as $index => $product_tab) { if (isset($product_tab['tab_name']) && $product_tab['tab_name'] && !isset($product_tab['name'])) { $product_tabs[$index]['name'] = $product_tab['tab_name']; unset($product_tabs[$index]['tab_name']); $fixed = true; } } if ($fixed) { update_post_meta($tab->post_parent, '_product_tabs', $product_tabs); } // It seems that setting the tab layout override in existing stores would be too dangerous, so for now the following is not used // enable the product '_override_tab_layout' so the product tab is actually used // update_post_meta( $tab->post_parent, '_override_tab_layout', 'yes' ); } } unset($tabs); } // In version 1.0.5 the core tab previously referred to as 'attributes' now // needs to be referrred to as 'additional_information' for consistency with // WC 2.0+, so fix the global and any product tab layouts if (version_compare($installed_version, "1.0.5", '<=')) { // fix global tab layout $tab_layout = get_option('wc_tab_manager_default_layout', false); if ($tab_layout && isset($tab_layout['core_tab_attributes'])) { $tab_layout['core_tab_additional_information'] = $tab_layout['core_tab_attributes']; $tab_layout['core_tab_additional_information']['id'] = 'additional_information'; unset($tab_layout['core_tab_attributes']); update_option('wc_tab_manager_default_layout', $tab_layout); } // fix any product-level tab layouts $results = $wpdb->get_results("SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key='_product_tabs'"); if (is_array($results)) { foreach ($results as $row) { $tab_layout = maybe_unserialize($row->meta_value); if ($tab_layout && isset($tab_layout['core_tab_attributes'])) { $tab_layout['core_tab_additional_information'] = $tab_layout['core_tab_attributes']; $tab_layout['core_tab_additional_information']['id'] = 'additional_information'; unset($tab_layout['core_tab_attributes']); update_post_meta($row->post_id, '_product_tabs', $tab_layout); } } } unset($results); } } } // class WC_Tab_Manager /** * Returns the One True Instance of Tab Manager * * @since 1.2.0 * @return WC_Tab_Manager */ function wc_tab_manager() { return WC_Tab_Manager::instance(); } /** * The WC_Tab_Manager global object * * @deprecated 1.2.0 * @name $wc_tab_manager * @global WC_Tab_Manager $GLOBALS['wc_tab_manager'] */ $GLOBALS['wc_tab_manager'] = wc_tab_manager(); }
* @copyright Copyright (c) 2012-2015, SkyVerge, Inc. * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 */ /** * WooCommerce Tab Manager Write Panels * * Sets up the write panels added by the Tab Manager */ if (!defined('ABSPATH')) { exit; } // Exit if accessed directly /** Product data Tab panel */ include_once wc_tab_manager()->get_plugin_path() . '/admin/post-types/writepanels/writepanel-product_data-tabs.php'; /** Product Tab Actions writepanel */ include_once wc_tab_manager()->get_plugin_path() . '/admin/post-types/writepanels/writepanel-product-tab_actions.php'; /** * Save meta boxes */ add_action('save_post', 'wc_tab_manager_meta_boxes_save', 1, 2); /** * Runs when a post is saved and does an action which the write panel save scripts can hook into. * * @access public * @param int $post_id post identifier * @param object $post post object */ function wc_tab_manager_meta_boxes_save($post_id, $post) { if (empty($post_id) || empty($post) || empty($_POST)) { return;