Plugin Directory

Changeset 3405807


Ignore:
Timestamp:
11/29/2025 01:24:08 PM (3 months ago)
Author:
stackwc
Message:

Update to version 1.0.2 from GitHub

Location:
product-tabs-for-woo
Files:
482 added
6 deleted
12 edited
1 copied

Legend:

Unmodified
Added
Removed
  • product-tabs-for-woo/assets/banner-1544x500.jpg

    • Property svn:mime-type changed from application/octet-stream to image/jpeg
  • product-tabs-for-woo/assets/banner-772x250.jpg

    • Property svn:mime-type changed from application/octet-stream to image/jpeg
  • product-tabs-for-woo/assets/icon-128x128.png

    • Property svn:mime-type changed from application/octet-stream to image/png
  • product-tabs-for-woo/assets/icon-256x256.png

    • Property svn:mime-type changed from application/octet-stream to image/png
  • product-tabs-for-woo/tags/1.0.2/assets/css/admin.css

    r3270803 r3405807  
    2323    color: #757575;
    2424    font-style: italic;
     25}
     26
     27/* Select2 WordPress Admin Styling */
     28.select2-container {
     29    max-width: 400px;
     30}
     31.select2-container--default .select2-selection--multiple {
     32    border: 1px solid #8c8f94;
     33    border-radius: 4px;
     34    min-height: 30px;
     35    padding: 2px;
     36}
     37.select2-container--default.select2-container--focus .select2-selection--multiple {
     38    border-color: #2271b1;
     39    box-shadow: 0 0 0 1px #2271b1;
     40}
     41.select2-container--default .select2-selection--multiple .select2-selection__choice {
     42    background-color: #2271b1;
     43    border: 1px solid #2271b1;
     44    color: #fff;
     45    padding: 2px 8px;
     46    margin: 2px;
     47}
     48.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
     49    color: #fff;
     50    margin-right: 5px;
     51}
     52.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
     53    color: #f0f0f1;
    2554}
  • product-tabs-for-woo/tags/1.0.2/assets/js/admin.js

    r3270803 r3405807  
    11jQuery(document).ready(function($) {
     2    // --- Shared icon picker modal helpers (used on Settings and Post Editor) ---
     3    var $iptfwIconModal = null, iptfwAllIcons = null, iptfwIconTarget = null;
     4    function iptfwGetAllDashicons(){
     5        if (iptfwAllIcons) return iptfwAllIcons;
     6        try {
     7            var classes = new Set();
     8            for (var i=0;i<document.styleSheets.length;i++){
     9                var ss = document.styleSheets[i];
     10                if (!ss || !ss.href || ss.href.indexOf('dashicons') === -1) continue;
     11                var rules = ss.cssRules || ss.rules; if (!rules) continue;
     12                for (var j=0;j<rules.length;j++){
     13                    var sel = rules[j].selectorText || '';
     14                    if (sel && sel.indexOf('.dashicons-') !== -1){
     15                        sel.split(',').forEach(function(part){
     16                            var m = (part.trim().match(/\.dashicons-([a-z0-9\-]+)/));
     17                            if (m) classes.add('dashicons-' + m[1]);
     18                        });
     19                    }
     20                }
     21            }
     22            iptfwAllIcons = Array.from(classes).sort();
     23        } catch(e){
     24            iptfwAllIcons = ['dashicons-admin-generic','dashicons-star-filled','dashicons-yes','dashicons-heart','dashicons-cart','dashicons-products','dashicons-format-video','dashicons-clipboard','dashicons-analytics','dashicons-download'];
     25        }
     26        return iptfwAllIcons;
     27    }
     28    function iptfwEnsureIconModal(){
     29        if ($iptfwIconModal) return $iptfwIconModal;
     30        $iptfwIconModal = $('<div id="iptfw-icon-modal" class="iptfw-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:100000;">\
     31            <div class="iptfw-modal-inner" style="background:#fff;max-width:700px;margin:60px auto;padding:16px;border-radius:6px;">\
     32                <div style="display:flex;justify-content:space-between;align-items:center;">\
     33                    <h2 style="margin:0 0 12px;">Select an Icon</h2>\
     34                    <button type="button" class="button" id="iptfw-icon-close">Close</button>\
     35                </div>\
     36                <input type="text" id="iptfw-icon-search" placeholder="Search..." class="regular-text" style="width:100%;margin-bottom:12px;"/>\
     37                <div class="iptfw-icon-grid" style="display:grid;grid-template-columns:repeat(6,1fr);gap:10px;max-height:360px;overflow:auto;"></div>\
     38            </div>\
     39        </div>');
     40        $('body').append($iptfwIconModal);
     41        function renderIcons(filter){
     42            var icons = iptfwGetAllDashicons();
     43            var $grid = $iptfwIconModal.find('.iptfw-icon-grid').empty();
     44            icons.forEach(function(cls){
     45                if (filter && cls.toLowerCase().indexOf(filter.toLowerCase()) === -1) return;
     46                var $item = $('<button type="button" class="button" style="height:48px;display:flex;align-items:center;justify-content:center;">\
     47                    <span class="dashicons '+cls+'"></span></button>').data('icon', cls);
     48                $grid.append($item);
     49            });
     50        }
     51        $iptfwIconModal.on('click', '#iptfw-icon-close', function(){ $iptfwIconModal.hide(); });
     52        $iptfwIconModal.on('click', function(e){ if (e.target === $iptfwIconModal[0]) { $iptfwIconModal.hide(); } });
     53        $iptfwIconModal.on('keyup', '#iptfw-icon-search', function(){ renderIcons($(this).val()); });
     54        $iptfwIconModal.on('click', '.iptfw-icon-grid .button', function(){ if (!iptfwIconTarget) return; iptfwIconTarget.val($(this).data('icon')).trigger('change'); $iptfwIconModal.hide(); });
     55        // Initial fill
     56        renderIcons('');
     57        return $iptfwIconModal;
     58    }
    259    // Display type toggle functionality
    360    $('#display_type').on('change', function() {
    4         $('#specific_products, #product_categories, #product_tags').hide();
     61        $('#specific_products, #product_type, #product_categories, #product_tags, #advanced_conditions').hide();
    562        switch($(this).val()) {
    663            case 'specific':
    764                $('#specific_products').show();
    865                break;
     66            case 'product_type':
     67                $('#product_type').show();
     68                break;
    969            case 'categories':
    1070                $('#product_categories').show();
     
    1373                $('#product_tags').show();
    1474                break;
     75            case 'advanced':
     76                $('#advanced_conditions').show();
     77                break;
    1578        }
    1679    });
     
    1982    $('#display_type').trigger('change');
    2083
    21     // Settings button functionality
    22     function initSettingsButton() {
    23         var $addNewButton = $('.wrap h1.wp-heading-inline').next('.page-title-action');
    24         if ($addNewButton.length) {
    25             $addNewButton.after(
    26                 '<a href="' + iptfw_admin.settings_url + '" class="page-title-action" style="margin-left: 10px;">' +
    27                 iptfw_admin.settings_text + '</a>'
    28             );
    29         }
    30     }
    31 
    32     if (iptfw_admin.is_product_tab_page) {
    33         initSettingsButton();
     84    // Initialize Select2 for product search
     85    if ($('.iptfw-product-search').length) {
     86        $('.iptfw-product-search').select2({
     87            ajax: {
     88                url: iptfw_admin.ajax_url,
     89                dataType: 'json',
     90                delay: 250,
     91                data: function (params) {
     92                    return {
     93                        q: params.term,
     94                        action: 'iptfw_search_products',
     95                        nonce: iptfw_admin.admin_nonce
     96                    };
     97                },
     98                processResults: function (data) {
     99                    return {
     100                        results: data.results
     101                    };
     102                },
     103                cache: true
     104            },
     105            minimumInputLength: 2,
     106            placeholder: 'Search for products...',
     107            allowClear: true
     108        });
     109    }
     110
     111    // Settings and Reorder are now handled by tabs navigation
     112
     113    // Global icon picker handler (works on settings page and block editor)
     114    $(document).on('click', '.iptfw-icon-picker', function(e){
     115        e.preventDefault();
     116        if (!iptfw_admin.has_premium) { window.alert('This is a Pro feature.'); return; }
     117        iptfwEnsureIconModal();
     118        iptfwIconTarget = $('#'+$(this).data('target'));
     119        $('#iptfw-icon-search').val('');
     120        $iptfwIconModal.show();
     121        $('#iptfw-icon-search').trigger('focus');
     122    });
     123
     124    // Settings: icon picker modal
     125    if (iptfw_admin.is_settings_page) {
     126
     127        // Add picker buttons and preview next to icon fields; hide raw input
     128        ['icon_description','icon_additional_information','icon_reviews'].forEach(function(id){
     129            var $input = $('#'+id);
     130            if ($input.length) {
     131                var $btn = $('<button type="button" class="button iptfw-icon-picker" style="margin-left:8px;">Select Icon</button>').attr('data-target', id);
     132                var $preview = $('<span class="iptfw-icon-preview" style="display:inline-flex;align-items:center;justify-content:center;margin-left:8px;min-width:28px;position:relative;height:28px;width:28px;"></span>');
     133                function syncPreview(){
     134                    var cls = $input.val();
     135                    $preview.empty();
     136                    if (cls){
     137                        $preview.append($('<span/>').addClass('dashicons').addClass(cls));
     138                        // Simple clear button (×) at top-right
     139                        var $clear = $('<button type="button" class="iptfw-remove-icon" aria-label="Remove icon">×</button>').attr('title','Remove icon');
     140                        $clear.css({
     141                            position:'absolute',
     142                            top:'-6px',
     143                            right:'-6px',
     144                            width:'18px',
     145                            height:'18px',
     146                            lineHeight:'14px',
     147                            textAlign:'center',
     148                            background:'#f0f0f1',
     149                            border:'1px solid #ccd0d4',
     150                            borderRadius:'50%',
     151                            cursor:'pointer',
     152                            padding:0,
     153                            fontWeight:'bold'
     154                        });
     155                        $preview.append($clear);
     156                    }
     157                }
     158                $input.css({width:'0',padding:'0',border:'0',opacity:0,position:'absolute'});
     159                $input.after($btn).after($preview);
     160                syncPreview();
     161                $input.on('change', syncPreview);
     162            }
     163        });
     164
     165        // Remove icon handler
     166        $(document).on('click', '.iptfw-remove-icon', function(e){
     167            e.preventDefault();
     168            var $wrap = $(this).closest('.iptfw-icon-preview');
     169            var $input = $wrap.prevAll('input[type="text"]').first();
     170            if ($input.length){
     171                $input.val('').trigger('change');
     172            }
     173        });
     174    }
     175
     176    // Post editor: apply picker/preview to custom tab sidebar field if present
     177    (function(){
     178        var $input = $('#iptfw_icon');
     179        if ($input.length) {
     180            var $btn = $('.iptfw-icon-picker[data-target="iptfw_icon"]');
     181            var $preview = $input.siblings('.iptfw-icon-preview');
     182            // Hide the raw input; keep value for submission
     183            $input.css({width:'0',padding:'0',border:'0',opacity:0,position:'absolute'});
     184            // Click handler to open modal on editor
     185            $(document).on('click', '.iptfw-icon-picker[data-target="iptfw_icon"]', function(e){
     186                e.preventDefault();
     187                if (!iptfw_admin.has_premium) { window.alert('This is a Pro feature.'); return; }
     188                iptfwEnsureIconModal();
     189                iptfwIconTarget = $input;
     190                $('#iptfw-icon-search').val('');
     191                $iptfwIconModal.show();
     192                $('#iptfw-icon-search').trigger('focus');
     193            });
     194            function syncPreview(){
     195                var cls = $input.val();
     196                $preview.empty();
     197                if (cls){
     198                    $preview.append($('<span/>').addClass('dashicons').addClass(cls));
     199                    var $clear = $('<button type="button" class="iptfw-remove-icon" aria-label="Remove icon">×</button>').attr('title','Remove icon');
     200                    $clear.css({position:'absolute',top:'-6px',right:'-6px',width:'18px',height:'18px',lineHeight:'14px',textAlign:'center',background:'#f0f0f1',border:'1px solid #ccd0d4',borderRadius:'50%',cursor:'pointer',padding:0,fontWeight:'bold'});
     201                    $preview.append($clear);
     202                }
     203            }
     204            $input.on('change', syncPreview);
     205            syncPreview();
     206
     207            $(document).on('click', '.iptfw-remove-icon', function(e){
     208                if ($preview.has(this).length || $preview.is($(this).parent())) {
     209                    e.preventDefault();
     210                    $input.val('').trigger('change');
     211                }
     212            });
     213        }
     214    })();
     215
     216    // Reorder page sortable + save
     217    if (iptfw_admin.is_reorder_page) {
     218        var $tbody = $('#iptfw-reorder-list');
     219        if (iptfw_admin.can_reorder) {
     220            $tbody.sortable({
     221                items: 'tr',
     222                cursor: 'move',
     223                axis: 'y',
     224                containment: 'parent'
     225            });
     226        } else {
     227            // Disable interaction
     228            $tbody.find('tr').addClass('iptfw-disabled');
     229        }
     230
     231        $('#iptfw-save-order').on('click', function(e) {
     232            e.preventDefault();
     233            var $btn = $(this);
     234            var order = [];
     235            $tbody.find('tr').each(function() {
     236                order.push($(this).data('id').toString());
     237            });
     238            $btn.prop('disabled', true).text('Saving...');
     239            if (!iptfw_admin.can_reorder) {
     240                return false;
     241            }
     242            $.post(iptfw_admin.ajax_url, {
     243                action: 'iptfw_save_order',
     244                nonce: iptfw_admin.nonce,
     245                order: order
     246            }).done(function(resp) {
     247                var ok = resp && (resp.success === true || resp === '1');
     248                var $notice = $('<div class="notice notice-success is-dismissible"><p>Order saved.</p></div>');
     249                if (!ok) {
     250                    $notice = $('<div class="notice notice-error is-dismissible"><p>Failed to save order.</p></div>');
     251                }
     252                $('.wrap h1').after($notice);
     253            }).fail(function(xhr) {
     254                var msg = 'Failed to save order';
     255                if (xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
     256                    msg = xhr.responseJSON.data.message;
     257                }
     258                var $notice = $('<div class="notice notice-error is-dismissible"><p>'+ msg +'</p></div>');
     259                $('.wrap h1').after($notice);
     260            }).always(function(){
     261                $btn.prop('disabled', false).text('Save Order');
     262            });
     263        });
    34264    }
    35265});
  • product-tabs-for-woo/tags/1.0.2/product-tabs-for-woo.php

    r3294577 r3405807  
    11<?php
     2
    23/**
    3  * Plugin Name: Product Tabs for Woo
     4 * Plugin Name: Product Tabs for WooCommerce
    45 * Plugin URI: https://stackwc.com/plugins/product-tabs-for-woo/
    5  * Description: Adds custom product tabs to WooCommerce products.
    6  * Version: 1.0.1
     6 * Description: Add custom product tabs to your WooCommerce products with advanced display conditions, priority settings, and powerful customization options.
     7 * Version: 1.0.2
    78 * Author: StackWC
    89 * Author URI: https://stackwc.com/
     
    1011 * Domain Path: /languages
    1112 * Requires at least: 5.0
    12  * Requires PHP: 7.2
     13 * Requires PHP: 7.4
    1314 * License: GPLv3
    1415 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1516 * WC requires at least: 7.2
    16  * WC tested up to: 9.8.5
     17 * WC tested up to: 10.3.5
    1718 * Requires Plugins: woocommerce
    1819 *
    1920 * Disclaimer: This plugin is not affiliated with or endorsed by WooCommerce or Automattic.
    2021 * "WooCommerce" is a trademark of Automattic Inc. This is an independent plugin.
    21  * Copyright: ilmosys infotech LLP
     22 * Copyright: StackWC brand by ilmosys infotech LLP
    2223 */
    23 
    24 if (!defined('ABSPATH')) {
     24if ( !defined( 'ABSPATH' ) ) {
    2525    exit;
    2626}
    27 
    28 // Define plugin constants first
    29 define('IPTFW_VERSION', '1.0.1');
    30 define('IPTFW_FILE', __FILE__);
    31 define('IPTFW_PATH', plugin_dir_path(__FILE__));
    32 define('IPTFW_URL', plugin_dir_url(__FILE__));
    33 
    34 // Check if WooCommerce is active
    35 function iptfw_is_woocommerce_active() {
    36     return in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')));
    37 }
    38 
    39 // Declare WooCommerce compatibility
    40 function iptfw_declare_woocommerce_compatibility() {
    41     if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
    42         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
    43         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('cart_checkout_blocks', __FILE__, true);
    44         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('analytics', __FILE__, true);
    45         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_block_editor', __FILE__, true);
    46         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_block_templates', __FILE__, true);
    47         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_editor', __FILE__, true);
    48         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_custom_fields', __FILE__, true);
    49     }
    50 }
    51 add_action('before_woocommerce_init', 'iptfw_declare_woocommerce_compatibility');
    52 
    53 // Check WooCommerce compatibility
    54 function iptfw_check_woocommerce_compatibility() {
    55     if (!iptfw_is_woocommerce_active()) {
    56         return;
    57     }
    58 
    59     // Get WooCommerce version
    60     $wc_version = WC()->version;
    61    
    62     // Get required WooCommerce version from plugin header
    63     $required_wc_version = '7.2';
    64    
    65     // Check if WooCommerce version meets requirements
    66     if (version_compare($wc_version, $required_wc_version, '<')) {
    67         add_action('admin_notices', function() use ($required_wc_version, $wc_version) {
    68             ?>
    69             <div class="notice notice-error">
    70                 <p><?php
    71                     /* translators: 1: Required WooCommerce version, 2: Current WooCommerce version */
    72                     printf(
    73                         /* translators: 1: Required WooCommerce version, 2: Current WooCommerce version */
    74                         esc_html__('Custom Product Tabs for WooCommerce requires WooCommerce version %1$s or higher. You are running version %2$s. Please update WooCommerce to use this plugin.', 'product-tabs-for-woo'),
    75                         esc_html($required_wc_version),
    76                         esc_html($wc_version)
    77                     );
    78                 ?></p>
    79             </div>
    80             <?php
    81         });
    82         return false;
    83     }
    84 
    85     return true;
    86 }
    87 add_action('admin_init', 'iptfw_check_woocommerce_compatibility');
    88 
    89 // Disable activation if WooCommerce is not active
    90 function iptfw_disable_activation() {
    91     if (!iptfw_is_woocommerce_active()) {
    92         // Get the plugin file
    93         $plugin_file = plugin_basename(IPTFW_FILE);
    94        
    95         // Add a notice about WooCommerce requirement
    96         add_action('admin_notices', function() {
    97             ?>
    98             <div class="notice notice-error">
    99                 <p><?php esc_html_e('Product Tabs for WooCommerce requires WooCommerce to be installed and activated.', 'product-tabs-for-woo'); ?></p>
    100             </div>
    101             <?php
    102         });
    103        
    104         // Add a notice in the plugins list
    105         add_action('after_plugin_row_' . $plugin_file, function() {
    106             ?>
    107             <tr class="plugin-update-tr">
    108                 <td colspan="4" class="plugin-update colspanchange">
    109                     <div class="notice notice-error inline">
    110                         <p><?php esc_html_e('This plugin cannot be activated because required plugins are missing or inactive.', 'product-tabs-for-woo'); ?></p>
    111                         <p><?php esc_html_e('Requires: WooCommerce', 'product-tabs-for-woo'); ?></p>
    112                     </div>
    113                 </td>
    114             </tr>
    115             <?php
    116         });
    117        
    118         // Disable the activate link
    119         add_filter('plugin_action_links_' . $plugin_file, function($actions) {
    120             if (isset($actions['activate'])) {
    121                 $actions['activate'] = '<span class="button button-disabled">' . esc_html__('Activate', 'product-tabs-for-woo') . '</span>';
    122             }
    123             return $actions;
    124         });
    125 
    126         // Prevent activation
    127         add_filter('plugin_action_links_' . $plugin_file, function($actions) {
    128             if (isset($actions['activate'])) {
    129                 unset($actions['activate']);
    130             }
    131             return $actions;
    132         });
    133     }
    134 }
    135 add_action('admin_init', 'iptfw_disable_activation');
    136 
    137 // Prevent activation if WooCommerce is not active
    138 function iptfw_prevent_activation($plugin) {
    139     if ($plugin === plugin_basename(IPTFW_FILE) && !iptfw_is_woocommerce_active()) {
    140         wp_die(
    141             esc_html__('This plugin requires WooCommerce to be installed and activated.', 'product-tabs-for-woo'),
    142             esc_html__('Plugin Activation Error', 'product-tabs-for-woo'),
    143             array('back_link' => true)
    144         );
    145     }
    146 }
    147 add_action('activate_plugin', 'iptfw_prevent_activation');
    148 
    149 // Add compatibility information to WooCommerce status
    150 function iptfw_add_compatibility_info($sections) {
    151     // Verify nonce for admin actions
    152     if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_compatibility_nonce')) {
    153         return $sections;
    154     }
    155 
    156     $sections['iptfw_compatibility'] = array(
    157         'title' => esc_html__('Custom Product Tabs Compatibility', 'product-tabs-for-woo'),
    158         'callback' => 'iptfw_compatibility_section'
    159     );
    160     return $sections;
    161 }
    162 add_filter('woocommerce_admin_status_tabs', 'iptfw_add_compatibility_info');
    163 
    164 function iptfw_compatibility_section() {
    165     // Verify nonce for admin actions
    166     if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_compatibility_nonce')) {
    167         return;
    168     }
    169 
    170     ?>
    171     <h2><?php esc_html_e('Custom Product Tabs Compatibility', 'product-tabs-for-woo'); ?></h2>
    172     <table class="widefat" cellspacing="0">
    173         <thead>
    174             <tr>
    175                 <th><?php esc_html_e('Feature', 'product-tabs-for-woo'); ?></th>
    176                 <th><?php esc_html_e('Status', 'product-tabs-for-woo'); ?></th>
    177             </tr>
    178         </thead>
    179         <tbody>
    180             <tr>
    181                 <td><?php esc_html_e('WooCommerce Version', 'product-tabs-for-woo'); ?></td>
    182                 <td>
    183                     <?php
    184                     $wc_version = WC()->version;
    185                     $required_wc_version = '7.2';
    186                     if (version_compare($wc_version, $required_wc_version, '>=')) {
    187                         echo '<mark class="yes">' . esc_html($wc_version) . '</mark>';
    188                     } else {
    189                         echo '<mark class="error">' . esc_html($wc_version) . ' (' . esc_html__('Update Required', 'product-tabs-for-woo') . ')</mark>';
    190                     }
    191                     ?>
    192                 </td>
    193             </tr>
    194             <?php if (class_exists('WC_Admin_Features')): ?>
    195             <tr>
    196                 <td><?php esc_html_e('Analytics', 'product-tabs-for-woo'); ?></td>
    197                 <td>
    198                     <?php
    199                     $features = WC_Admin_Features::get_compatible_features();
    200                     if (isset($features['analytics']) && $features['analytics']) {
    201                         echo '<mark class="warning">' . esc_html__('Enabled - May need testing', 'product-tabs-for-woo') . '</mark>';
    202                     } else {
    203                         echo '<mark class="yes">' . esc_html__('Disabled', 'product-tabs-for-woo') . '</mark>';
    204                     }
    205                     ?>
    206                 </td>
    207             </tr>
    208             <?php endif; ?>
    209         </tbody>
    210     </table>
    211     <?php
    212 }
    213 
    214 // Then include required files
    215 require_once IPTFW_PATH . 'includes/class-iptfw-settings.php';
    216 require_once IPTFW_PATH . 'includes/class-iptfw-admin.php';
    217 
    218 if (!class_exists('IPTFW_Product_Tabs')) {
     27// Freemius Integration
     28if ( !function_exists( 'iptfw_fs' ) ) {
     29    // Create a helper function for easy SDK access.
     30    function iptfw_fs() {
     31        global $iptfw_fs;
     32        if ( !isset( $iptfw_fs ) ) {
     33            // Include Freemius SDK.
     34            require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
     35            $iptfw_fs = fs_dynamic_init( array(
     36                'id'             => '19118',
     37                'slug'           => 'product-tabs-for-woo',
     38                'premium_slug'   => 'product-tabs-for-woo-pro',
     39                'type'           => 'plugin',
     40                'public_key'     => 'pk_16d450f47372bb8f7243e970e61b8',
     41                'is_premium'     => false,
     42                'premium_suffix' => 'Pro',
     43                'has_addons'     => true,
     44                'has_paid_plans' => true,
     45                'menu'           => array(
     46                    'first-path' => 'plugins.php',
     47                    'support'    => false,
     48                ),
     49                'is_live'        => true,
     50            ) );
     51        }
     52        return $iptfw_fs;
     53    }
     54
     55    // Init Freemius.
     56    iptfw_fs();
     57    // Signal that SDK was initiated.
     58    do_action( 'iptfw_fs_loaded' );
     59}
     60// Define plugin constants
     61define( 'IPTFW_VERSION', '1.0.2' );
     62define( 'IPTFW_FILE', __FILE__ );
     63define( 'IPTFW_PATH', plugin_dir_path( __FILE__ ) );
     64define( 'IPTFW_URL', plugin_dir_url( __FILE__ ) );
     65// Include core files
     66require_once IPTFW_PATH . 'stackwc-core/stackwc-core.php';
     67require_once IPTFW_PATH . 'includes/class-stackwc-compatibility.php';
     68require_once IPTFW_PATH . 'includes/class-stackwc-post-type.php';
     69require_once IPTFW_PATH . 'includes/class-stackwc-settings.php';
     70require_once IPTFW_PATH . 'includes/class-stackwc-admin.php';
     71require_once IPTFW_PATH . 'includes/class-stackwc-meta-boxes.php';
     72require_once IPTFW_PATH . 'includes/class-stackwc-tabs.php';
     73require_once IPTFW_PATH . 'includes/class-stackwc-conditions.php';
     74require_once IPTFW_PATH . 'includes/class-stackwc-ajax.php';
     75require_once IPTFW_PATH . 'includes/class-stackwc-columns.php';
     76// WooCommerce Compatibility
     77add_action( 'before_woocommerce_init', array('IPTFW_Compatibility', 'declare_woocommerce_compatibility') );
     78add_action( 'admin_init', array('IPTFW_Compatibility', 'check_woocommerce_compatibility') );
     79add_action( 'admin_init', array('IPTFW_Compatibility', 'disable_activation') );
     80add_action( 'activate_plugin', array('IPTFW_Compatibility', 'prevent_activation') );
     81add_filter( 'woocommerce_admin_status_tabs', array('IPTFW_Compatibility', 'add_compatibility_info') );
     82// Register plugin core menu
     83add_action( 'init', function () {
     84    stackwc_register_plugin_menu( array(
     85        'menu_title'  => __( 'Product Tabs for Woo', 'product-tabs-for-woo' ),
     86        'page_title'  => __( 'Product Tabs for WooCommerce Settings', 'product-tabs-for-woo' ),
     87        'capability'  => 'manage_woocommerce',
     88        'menu_slug'   => 'iptfw_product_tab',
     89        'description' => __( 'Adds custom product tabs to WooCommerce products.', 'product-tabs-for-woo' ),
     90        'menu_url'    => admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ),
     91        'callback'    => function () {
     92            // Check if user has permission to manage WooCommerce
     93            if ( !current_user_can( 'manage_woocommerce' ) ) {
     94                wp_die( __( 'Sorry, you are not allowed to access this page.', 'product-tabs-for-woo' ) );
     95            }
     96            // Redirect immediately
     97            wp_safe_redirect( admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ) );
     98            exit;
     99        },
     100    ) );
     101} );
     102// Modify submenu URL to point directly to the post type edit page
     103// Run on both admin_menu (late) and admin_init (late) to catch menu registration
     104add_action( 'admin_menu', function () {
     105    global $submenu;
     106    if ( isset( $submenu['stackwc'] ) ) {
     107        foreach ( $submenu['stackwc'] as $key => $item ) {
     108            if ( isset( $item[2] ) && $item[2] === 'iptfw_product_tab' ) {
     109                // Replace the menu slug with the direct URL
     110                $submenu['stackwc'][$key][2] = 'edit.php?post_type=iptfw_product_tab&tab=settings';
     111                break;
     112            }
     113        }
     114    }
     115}, 999 );
     116add_action( 'admin_init', function () {
     117    global $submenu;
     118    if ( isset( $submenu['stackwc'] ) ) {
     119        foreach ( $submenu['stackwc'] as $key => $item ) {
     120            if ( isset( $item[2] ) && $item[2] === 'iptfw_product_tab' ) {
     121                // Replace the menu slug with the direct URL
     122                $submenu['stackwc'][$key][2] = 'edit.php?post_type=iptfw_product_tab';
     123                break;
     124            }
     125        }
     126    }
     127}, 999 );
     128// Early redirect hook to catch the page load before WordPress permission checks
     129add_action( 'admin_init', function () {
     130    if ( isset( $_GET['page'] ) && $_GET['page'] === 'iptfw_product_tab' && !isset( $_GET['post_type'] ) ) {
     131        // Check if user has permission
     132        if ( current_user_can( 'manage_woocommerce' ) ) {
     133            wp_safe_redirect( admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ) );
     134            exit;
     135        }
     136    }
     137}, 1 );
     138// Provide plugin-specific template path
     139add_filter(
     140    'stackwc_plugin_template_path',
     141    function ( $template_path, $current_page ) {
     142        if ( $current_page === 'wc-settings&tab=stackwc_qbnbfw' ) {
     143            return STACKWC_QBNBFW_PATH . 'includes/templates/wc-settings&tab=stackwc_qbnbfw.php';
     144        }
     145        return $template_path;
     146    },
     147    10,
     148    2
     149);
     150/**
     151 * Main Plugin Class
     152 */
     153if ( !class_exists( 'IPTFW_Product_Tabs' ) ) {
    219154    class IPTFW_Product_Tabs {
    220155        private static $instance = null;
     156
    221157        public $settings;
     158
    222159        public $admin;
    223         private $current_page = '';
    224         private $current_tab = '';
    225160
    226161        public static function instance() {
    227             if (null === self::$instance) {
     162            if ( null === self::$instance ) {
    228163                self::$instance = new self();
    229164            }
     
    232167
    233168        public function __construct() {
    234             if (!iptfw_is_woocommerce_active()) {
     169            if ( !IPTFW_Compatibility::is_woocommerce_active() ) {
    235170                return;
    236171            }
    237 
    238172            $this->init_hooks();
    239             $this->set_current_page();
    240             $this->set_current_tab();
    241         }
    242 
    243         private function set_current_page() {
    244             // Verify nonce for admin actions
    245             if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_page_nonce')) {
    246                 return;
    247             }
    248             $this->current_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    249         }
    250 
    251         private function set_current_tab() {
    252             // Verify nonce for admin actions
    253             if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_tab_nonce')) {
    254                 return;
    255             }
    256             $this->current_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'list';
    257         }
    258 
    259         public function is_woocommerce_active() {
    260             return in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')));
    261         }
    262 
    263         public function woocommerce_missing_notice() {
    264             ?>
    265             <div class="notice notice-error">
    266                 <p><?php esc_html_e('Custom Product Tabs requires WooCommerce to be installed and active.', 'product-tabs-for-woo'); ?></p>
    267             </div>
    268             <?php
    269173        }
    270174
     
    273177            $this->admin = new IPTFW_Admin($this);
    274178            $this->settings = new IPTFW_Settings();
    275 
    276179            // Add text domain loading
    277             add_action('init', array($this, 'load_textdomain'));
    278            
    279             add_action('init', array($this, 'register_post_type'));
    280             add_action('admin_init', array($this, 'admin_init'));
    281             add_action('add_meta_boxes', array($this, 'add_meta_boxes'));
    282             add_action('save_post', array($this, 'save_meta_boxes'));
    283             add_filter('woocommerce_product_tabs', array($this, 'add_product_tabs'));
    284 
    285             // Add plugin action links
    286             add_filter('plugin_action_links_' . plugin_basename(IPTFW_FILE), array($this, 'add_plugin_action_links'));
    287 
    288             // Add these column filters back
    289             add_filter('manage_iptfw_product_tab_posts_columns', array($this, 'set_custom_columns'));
    290             add_action('manage_iptfw_product_tab_posts_custom_column', array($this, 'custom_column_content'), 10, 2);
    291             add_filter('manage_edit-iptfw_product_tab_sortable_columns', array($this, 'sortable_columns'));
     180            add_action( 'init', array($this, 'load_textdomain') );
     181            // Post type and meta fields
     182            add_action( 'init', array('IPTFW_Post_Type', 'register_post_type') );
     183            add_action( 'init', array('IPTFW_Post_Type', 'register_meta_fields') );
     184            // Meta boxes
     185            add_action( 'add_meta_boxes', array('IPTFW_Meta_Boxes', 'add_meta_boxes') );
     186            add_action( 'save_post', array('IPTFW_Meta_Boxes', 'save_meta_boxes') );
     187            // Product tabs
     188            add_filter( 'woocommerce_product_tabs', array('IPTFW_Tabs', 'add_product_tabs') );
     189            // Plugin action links
     190            add_filter( 'plugin_action_links_' . plugin_basename( IPTFW_FILE ), array($this, 'add_plugin_action_links') );
     191            // Admin columns
     192            add_filter( 'manage_iptfw_product_tab_posts_columns', array('IPTFW_Columns', 'set_custom_columns') );
     193            add_action(
     194                'manage_iptfw_product_tab_posts_custom_column',
     195                array('IPTFW_Columns', 'custom_column_content'),
     196                10,
     197                2
     198            );
     199            add_filter( 'manage_edit-iptfw_product_tab_sortable_columns', array('IPTFW_Columns', 'sortable_columns') );
     200            // AJAX handlers
     201            add_action( 'wp_ajax_iptfw_save_order', array('IPTFW_Ajax', 'save_order') );
     202            add_action( 'wp_ajax_iptfw_search_products', array('IPTFW_Ajax', 'search_products') );
    292203        }
    293204
    294205        public function load_textdomain() {
    295             load_plugin_textdomain(
    296                 'product-tabs-woocommerce',
    297                 false,
    298                 dirname(plugin_basename(IPTFW_FILE)) . '/languages'
    299             );
    300         }
    301 
    302         public function register_post_type() {
    303             $labels = array(
    304                 'name' => esc_html__('Product Tabs', 'product-tabs-for-woo'),
    305                 'singular_name' => esc_html__('Product Tab', 'product-tabs-for-woo'),
    306                 'add_new' => esc_html__('Add New', 'product-tabs-for-woo'),
    307                 'add_new_item' => esc_html__('Add New Product Tab', 'product-tabs-for-woo'),
    308                 'edit_item' => esc_html__('Edit Product Tab', 'product-tabs-for-woo'),
    309                 'new_item' => esc_html__('New Product Tab', 'product-tabs-for-woo'),
    310                 'view_item' => esc_html__('View Product Tab', 'product-tabs-for-woo'),
    311                 'search_items' => esc_html__('Search Product Tabs', 'product-tabs-for-woo'),
    312                 'not_found' => esc_html__('No product tabs found', 'product-tabs-for-woo'),
    313                 'not_found_in_trash' => esc_html__('No product tabs found in trash', 'product-tabs-for-woo'),
    314                 'menu_name' => esc_html__('Product Tabs', 'product-tabs-for-woo')
    315             );
    316 
    317             $args = array(
    318                 'labels' => $labels,
    319                 'public' => false,
    320                 'show_ui' => true,
    321                 'show_in_menu' => false,
    322                 'capability_type' => 'post',
    323                 'hierarchical' => false,
    324                 'supports' => array('title', 'editor'),
    325                 'show_in_rest' => true,
    326                 'rewrite' => false
    327             );
    328 
    329             register_post_type('iptfw_product_tab', $args);
    330         }
    331 
    332         public function admin_init() {
    333             global $parent_file, $submenu_file;
    334            
    335             // Verify nonce for admin actions
    336             if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_admin_nonce')) {
    337                 return;
    338             }
    339            
    340             // Keep Product Tabs menu active when viewing settings
    341             if (isset($_GET['page']) && sanitize_text_field(wp_unslash($_GET['page'])) === 'iptfw-settings') {
    342                 $parent_file = 'edit.php?post_type=product';
    343                 $submenu_file = 'edit.php?post_type=iptfw_product_tab';
    344             }
    345         }
    346 
    347         public function add_meta_boxes() {
    348             add_meta_box(
    349                 'iptfw_tab_settings',
    350                 esc_html__('Tab Settings', 'product-tabs-for-woo'),
    351                 array($this, 'render_tab_settings'),
    352                 'iptfw_product_tab',
    353                 'side',
    354                 'high'
    355             );
    356         }
    357 
    358         public function render_tab_settings($post) {
    359             wp_nonce_field('iptfw_save_meta', 'iptfw_meta_nonce');
    360            
    361             $priority = get_post_meta($post->ID, '_iptfw_tab_priority', true);
    362             $display_type = get_post_meta($post->ID, '_iptfw_display_type', true) ?: 'all';
    363             $products = get_post_meta($post->ID, '_iptfw_products', true);
    364             $categories = get_post_meta($post->ID, '_iptfw_categories', true);
    365             $tags = get_post_meta($post->ID, '_iptfw_tags', true);
    366             ?>
    367             <div class="iptfw-settings-wrapper">
    368                 <div class="components-base-control">
    369                     <label class="components-base-control__label" for="tab_priority">
    370                         <?php esc_html_e('Priority', 'product-tabs-for-woo'); ?>
    371                     </label>
    372                     <input type="number"
    373                            id="tab_priority"
    374                            name="tab_priority"
    375                            value="<?php echo esc_attr($priority); ?>"
    376                            class="components-text-control__input" />
    377                     <p class="components-base-control__help">
    378                         <?php esc_html_e('Lower numbers appear first', 'product-tabs-for-woo'); ?>
    379                     </p>
    380                 </div>
    381 
    382                 <div class="components-base-control">
    383                     <label class="components-base-control__label">
    384                         <?php esc_html_e('Display Conditions', 'product-tabs-for-woo'); ?>
    385                     </label>
    386                     <select name="display_type"
    387                             id="display_type"
    388                             class="components-select-control__input">
    389                         <option value="all" <?php selected($display_type, 'all'); ?>>
    390                             <?php esc_html_e('All Products', 'product-tabs-for-woo'); ?>
    391                         </option>
    392                         <option value="specific" <?php selected($display_type, 'specific'); ?>>
    393                             <?php esc_html_e('Specific Products', 'product-tabs-for-woo'); ?>
    394                         </option>
    395                         <option value="categories" <?php selected($display_type, 'categories'); ?>>
    396                             <?php esc_html_e('Product Categories', 'product-tabs-for-woo'); ?>
    397                         </option>
    398                         <option value="tags" <?php selected($display_type, 'tags'); ?>>
    399                             <?php esc_html_e('Product Tags', 'product-tabs-for-woo'); ?>
    400                         </option>
    401                     </select>
    402                 </div>
    403 
    404                 <div id="specific_products" class="components-base-control" style="display: <?php echo $display_type === 'specific' ? 'block' : 'none'; ?>">
    405                     <label class="components-base-control__label" for="tab_products">
    406                         <?php esc_html_e('Product IDs', 'product-tabs-for-woo'); ?>
    407                     </label>
    408                     <input type="text"
    409                            id="tab_products"
    410                            name="tab_products"
    411                            value="<?php echo esc_attr($products); ?>"
    412                            class="components-text-control__input" />
    413                     <p class="components-base-control__help">
    414                         <?php esc_html_e('Enter product IDs separated by commas', 'product-tabs-for-woo'); ?>
    415                     </p>
    416                 </div>
    417 
    418                 <div id="product_categories" class="components-base-control" style="display: <?php echo $display_type === 'categories' ? 'block' : 'none'; ?>">
    419                     <label class="components-base-control__label" for="tab_categories">
    420                         <?php esc_html_e('Select Categories', 'product-tabs-for-woo'); ?>
    421                     </label>
    422                     <select name="tab_categories[]"
    423                             id="tab_categories"
    424                             class="components-select-control__input"
    425                             multiple>
    426                         <?php
    427                         $product_categories = get_terms(array(
    428                             'taxonomy' => 'product_cat',
    429                             'hide_empty' => false
    430                         ));
    431                         $selected_cats = explode(',', $categories);
    432                         foreach ($product_categories as $category) {
    433                             printf(
    434                                 '<option value="%s" %s>%s</option>',
    435                                 esc_attr($category->term_id),
    436                                 selected(in_array($category->term_id, $selected_cats), true, false),
    437                                 esc_html($category->name)
    438                             );
    439                         }
    440                         ?>
    441                     </select>
    442                 </div>
    443 
    444                 <div id="product_tags" class="components-base-control" style="display: <?php echo $display_type === 'tags' ? 'block' : 'none'; ?>">
    445                     <label class="components-base-control__label" for="tab_tags">
    446                         <?php esc_html_e('Select Tags', 'product-tabs-for-woo'); ?>
    447                     </label>
    448                     <select name="tab_tags[]"
    449                             id="tab_tags"
    450                             class="components-select-control__input"
    451                             multiple>
    452                         <?php
    453                         $product_tags = get_terms(array(
    454                             'taxonomy' => 'product_tag',
    455                             'hide_empty' => false
    456                         ));
    457                         $selected_tags = explode(',', $tags);
    458                         foreach ($product_tags as $tag) {
    459                             printf(
    460                                 '<option value="%s" %s>%s</option>',
    461                                 esc_attr($tag->term_id),
    462                                 selected(in_array($tag->term_id, $selected_tags), true, false),
    463                                 esc_html($tag->name)
    464                             );
    465                         }
    466                         ?>
    467                     </select>
    468                 </div>
    469             </div>
    470         </div>
    471         <?php
    472     }
    473 
    474     public function save_meta_boxes($post_id) {
    475         if (!isset($_POST['iptfw_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['iptfw_meta_nonce'])), 'iptfw_save_meta')) {
    476             return;
    477         }
    478 
    479         if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
    480             return;
    481         }
    482 
    483         if (!current_user_can('edit_post', $post_id)) {
    484             return;
    485         }
    486 
    487         if (isset($_POST['tab_priority'])) {
    488             update_post_meta($post_id, '_iptfw_tab_priority', sanitize_text_field(wp_unslash($_POST['tab_priority'])));
    489         }
    490 
    491         if (isset($_POST['display_type'])) {
    492             update_post_meta($post_id, '_iptfw_display_type', sanitize_text_field(wp_unslash($_POST['display_type'])));
    493         }
    494 
    495         if (isset($_POST['tab_products'])) {
    496             update_post_meta($post_id, '_iptfw_products', sanitize_text_field(wp_unslash($_POST['tab_products'])));
    497         }
    498 
    499         if (isset($_POST['tab_categories'])) {
    500             $categories = isset($_POST['tab_categories']) ? array_map('absint', wp_unslash($_POST['tab_categories'])) : array();
    501             update_post_meta($post_id, '_iptfw_categories', implode(',', $categories));
    502         }
    503 
    504         if (isset($_POST['tab_tags'])) {
    505             $tags = isset($_POST['tab_tags']) ? array_map('absint', wp_unslash($_POST['tab_tags'])) : array();
    506             update_post_meta($post_id, '_iptfw_tags', implode(',', $tags));
    507         }
    508     }
    509 
    510     public function set_custom_columns($columns) {
    511         $new_columns = array();
    512         $new_columns['cb'] = $columns['cb'];
    513         $new_columns['title'] = esc_html__('Tab Title', 'product-tabs-for-woo');
    514         $new_columns['priority'] = esc_html__('Priority', 'product-tabs-for-woo');
    515         $new_columns['display_rules'] = esc_html__('Display Rules', 'product-tabs-for-woo');
    516         $new_columns['date'] = $columns['date'];
    517         return $new_columns;
    518     }
    519 
    520     public function custom_column_content($column, $post_id) {
    521         switch ($column) {
    522             case 'priority':
    523                 $priority = get_post_meta($post_id, '_iptfw_tab_priority', true);
    524                 echo esc_html($priority ?: '50');
    525                 break;
    526 
    527             case 'display_rules':
    528                 $display_type = get_post_meta($post_id, '_iptfw_display_type', true) ?: 'all';
    529                 switch ($display_type) {
    530                     case 'all':
    531                         esc_html_e('All Products', 'product-tabs-for-woo');
    532                         break;
    533                     case 'specific':
    534                         $products = get_post_meta($post_id, '_iptfw_products', true);
    535                         echo esc_html__('Specific Products', 'product-tabs-for-woo') . ': ' . esc_html($products);
    536                         break;
    537                     case 'categories':
    538                         $categories = get_post_meta($post_id, '_iptfw_categories', true);
    539                         $cat_names = array();
    540                         foreach (explode(',', $categories) as $cat_id) {
    541                             $term = get_term($cat_id, 'product_cat');
    542                             if ($term) {
    543                                 $cat_names[] = $term->name;
    544                             }
    545                         }
    546                         echo esc_html__('Categories', 'product-tabs-for-woo') . ': ' . esc_html(implode(', ', $cat_names));
    547                         break;
    548                     case 'tags':
    549                         $tags = get_post_meta($post_id, '_iptfw_tags', true);
    550                         $tag_names = array();
    551                         foreach (explode(',', $tags) as $tag_id) {
    552                             $term = get_term($tag_id, 'product_tag');
    553                             if ($term) {
    554                                 $tag_names[] = $term->name;
    555                             }
    556                         }
    557                         echo esc_html__('Tags', 'product-tabs-for-woo') . ': ' . esc_html(implode(', ', $tag_names));
    558                         break;
    559                 }
    560                 break;
    561         }
    562     }
    563 
    564     public function sortable_columns($columns) {
    565         $columns['priority'] = 'priority';
    566         return $columns;
    567     }
    568 
    569     public function add_product_tabs($tabs) {
    570         // Handle default tab visibility
    571         $options = get_option('iptfw_settings', array());
    572         $default_tabs = array('description', 'additional_information', 'reviews');
    573        
    574         foreach ($default_tabs as $tab_id) {
    575             $show_key = 'show_' . $tab_id;
    576             // Check if the setting exists and is explicitly set to 0/false
    577             if (isset($options[$show_key]) && !$options[$show_key]) {
    578                 unset($tabs[$tab_id]);
    579             }
    580         }
    581 
    582         // Add custom tabs - Using a more efficient query
    583         $args = array(
    584             'post_type' => 'iptfw_product_tab',
    585             'posts_per_page' => -1,
    586             'post_status' => 'publish',
    587             'orderby' => 'menu_order',
    588             'order' => 'ASC'
    589         );
    590 
    591         // Get all tabs first
    592         $custom_tabs = get_posts($args);
    593         $product_id = get_the_ID();
    594 
    595         // Cache priorities to avoid multiple meta queries
    596         $tab_priorities = array();
    597         foreach ($custom_tabs as $tab) {
    598             $tab_priorities[$tab->ID] = get_post_meta($tab->ID, '_iptfw_tab_priority', true);
    599         }
    600 
    601         // Sort tabs by priority
    602         usort($custom_tabs, function($a, $b) use ($tab_priorities) {
    603             $priority_a = empty($tab_priorities[$a->ID]) ? 50 : intval($tab_priorities[$a->ID]);
    604             $priority_b = empty($tab_priorities[$b->ID]) ? 50 : intval($tab_priorities[$b->ID]);
    605             return $priority_a - $priority_b;
    606         });
    607 
    608         foreach ($custom_tabs as $tab) {
    609             if ($this->should_display_tab($tab->ID, $product_id)) {
    610                 $priority = empty($tab_priorities[$tab->ID]) ? 50 : intval($tab_priorities[$tab->ID]);
    611 
    612                 $tabs['iptfw_tab_' . $tab->ID] = array(
    613                     'title' => $tab->post_title,
    614                     'priority' => $priority,
    615                     'callback' => array($this, 'render_tab_content'),
    616                     'tab_id' => $tab->ID
    617                 );
    618             }
    619         }
    620 
    621         return $tabs;
    622     }
    623 
    624     public function render_tab_content($key, $tab) {
    625         $content = get_post_field('post_content', $tab['tab_id']);
    626         echo wp_kses_post(apply_filters('the_content', $content));
    627     }
    628 
    629     private function should_display_tab($tab_id, $product_id) {
    630         $display_type = get_post_meta($tab_id, '_iptfw_display_type', true) ?: 'all';
    631 
    632         switch ($display_type) {
    633             case 'all':
    634                 return true;
    635 
    636             case 'specific':
    637                 $products = get_post_meta($tab_id, '_iptfw_products', true);
    638                 if (empty($products)) {
    639                     return false;
    640                 }
    641                 $product_ids = array_map('trim', explode(',', $products));
    642                 return in_array($product_id, $product_ids);
    643 
    644             case 'categories':
    645                 $tab_categories = get_post_meta($tab_id, '_iptfw_categories', true);
    646                 if (empty($tab_categories)) {
    647                     return false;
    648                 }
    649                 $tab_cat_ids = array_map('trim', explode(',', $tab_categories));
    650                 $product_cats = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
    651                 return !empty(array_intersect($tab_cat_ids, $product_cats));
    652 
    653             case 'tags':
    654                 $tab_tags = get_post_meta($tab_id, '_iptfw_tags', true);
    655                 if (empty($tab_tags)) {
    656                     return false;
    657                 }
    658                 $tab_tag_ids = array_map('trim', explode(',', $tab_tags));
    659                 $product_tags = wp_get_post_terms($product_id, 'product_tag', array('fields' => 'ids'));
    660                 return !empty(array_intersect($tab_tag_ids, $product_tags));
    661 
    662             default:
    663                 return false;
    664         }
    665     }
    666 
    667     /**
    668      * Add settings link to plugin action links
    669      */
    670     public function add_plugin_action_links($links) {
    671         $settings_link = sprintf(
    672             '<a href="%s">%s</a>',
    673             esc_url(admin_url('admin.php?page=iptfw-settings')),
    674             esc_html__('Settings', 'product-tabs-for-woo')
    675         );
    676        
    677         // Add settings link to the beginning of the array
    678         array_unshift($links, $settings_link);
    679        
    680         return $links;
    681     }
    682 }
    683 
     206            load_plugin_textdomain( 'product-tabs-for-woo', false, dirname( plugin_basename( IPTFW_FILE ) ) . '/languages' );
     207        }
     208
     209        /**
     210         * Add settings link to plugin action links
     211         */
     212        public function add_plugin_action_links( $links ) {
     213            $settings_link = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ) ), esc_html__( 'Settings', 'product-tabs-for-woo' ) );
     214            array_unshift( $links, $settings_link );
     215            return $links;
     216        }
     217
     218    }
     219
     220}
    684221// Initialize the plugin
    685222function IPTFW() {
     
    688225
    689226// Start the plugin
    690 add_action('plugins_loaded', 'IPTFW');
    691 }
     227add_action( 'plugins_loaded', 'IPTFW' );
  • product-tabs-for-woo/tags/1.0.2/readme.txt

    r3294575 r3405807  
    1 === Product Tabs for Woo ===
    2 Contributors: ilmosys, stackwc, mahdiali
     1=== Product Tabs for WooCommerce ===
     2Contributors: stackwc, ilmosys, mahdiali
    33Tags: woocommerce, product tabs, custom tabs, product tabs for WooCommerce
    44Requires at least: 5.0
    5 Tested up to: 6.8
    6 Requires PHP: 7.2
    7 Stable tag: 1.0.1
     5Tested up to: 6.9
     6Requires PHP: 7.4
     7Stable tag: 1.0.2
    88License: GPLv3
    99License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1010WC requires at least: 7.2
    11 WC tested up to: 9.8.5
     11WC tested up to: 10.3.5
    1212
    13 Add custom product tabs to your WooCommerce products with advanced display conditions and priority settings.
     13Add custom product tabs to your WooCommerce products with advanced display conditions, priority settings, and powerful customization options.
    1414
    1515== Description ==
    1616
    17 Custom Product Tabs for WooCommerce is a lightweight yet powerful plugin that lets you create, edit, and manage product tabs effortlessly. With a built-in block editor, you can design custom tabs or import ready-made patterns for a seamless experience. The plugin also includes a settings page where you can control tab priorities and show or hide WooCommerce default tabs like Description, Additional Information, and Reviews. Enhance your product pages with structured, engaging content and improve the shopping experience with flexible tab management!
     17<a href="https://stackwc.com/plugins/product-tabs-for-woo/?utm_source=wprepo&utm_medium=link&utm_campaign=plugin" title="StackWC - Product Tabs for WooCommerce Plugin"> Product Tabs for WooCommerce </a> is a powerful and flexible plugin that allows you to create unlimited custom tabs for your WooCommerce products. With an intuitive interface, advanced display conditions, and comprehensive customization options, you can enhance your product pages with rich, engaging content that improves the shopping experience.
    1818
    19 = Key Features =
     19The plugin features a modern block editor integration, allowing you to design beautiful tab content using WordPress blocks. You can also use the classic editor for quick content creation. With Pro features, you get access to advanced display conditions, tab reordering, icon customization, and much more.
    2020
    21 * Create unlimited custom tabs for your products
    22 * Set display conditions based on specific products, categories, or tags
    23 * Control tab priority and order
    24 * Rich text editor for tab content
    25 * SEO-friendly tab implementation
     21<p>⭐ <a href="https://stackwc.com/plugins/product-tabs-for-woo/?utm_source=wprepo&utm_medium=link&utm_campaign=upgrade-pro"> Upgrade to Pro </a> | 🚀 <a href="https://stackwc.com/plugins/product-tabs-for-woo/"> Try the Demo< /a> | 🛟 <a href="https://stackwc.com/support/"> Get Support </a> </p>
     22
     23= Key Features (Free Version) =
     24
     25* Create unlimited custom product tabs
     26* Block editor support for rich content creation
     27* Classic editor support for quick editing
     28* Display conditions: All products, Specific products, Product types
     29* Tab priority system for controlling display order
     30* Show/hide default WooCommerce tabs (Description, Additional Information, Reviews)
     31* SEO-friendly implementation
    2632* Responsive design compatible
    27 * Easy to use interface
     33* Easy-to-use interface
     34* Product search for selecting specific products
     35* Support for multiple product types (Simple, Variable, Grouped, External, Downloadable, Virtual)
     36
     37= Pro Features =
     38
     39* Advanced Display Conditions:
     40  * Product Categories - Show tabs based on product categories
     41  * Product Tags - Display tabs for products with specific tags
     42  * User Roles - Control tab visibility based on user roles (including guest users)
     43  * Price Range - Show tabs for products within specific price ranges
     44  * Stock Status - Display tabs based on product stock status (In Stock, Out of Stock, On Backorder)
     45  * Sale Status - Show tabs for products on sale or regular price
     46  * Date Range (Seasonal) - Display tabs only within specific date ranges
     47  * Shipping Classes - Show tabs based on product shipping classes
     48  * Combine multiple conditions for precise control
     49
     50* Tab Management:
     51  * Drag & Drop Reorder - Visually reorder tabs with drag and drop interface
     52  * Tab Icons - Add Dashicons or custom CSS classes to tab titles
     53  * Rename Default Tabs - Customize names of Description, Additional Information, and Reviews tabs
     54  * Icon Support for Default Tabs - Add icons to default WooCommerce tabs
    2855
    2956= Use Cases =
    3057
    31 * Add product specifications
    32 * Display shipping information
    33 * Show size guides
    34 * Include warranty information
    35 * Add custom product features
    36 * Display related products
    37 * Show product comparisons
    38 * Include video tutorials
    39 * Design and customize any content you want using the block editor
     58* Product Specifications - Add detailed technical specifications
     59* Shipping Information - Display shipping policies and delivery times
     60* Size Guides - Include size charts and measurement guides
     61* Warranty Information - Show warranty terms and conditions
     62* Custom Product Features - Highlight unique selling points
     63* Related Products - Display related or complementary products
     64* Product Comparisons - Show comparison tables
     65* Video Tutorials - Embed instructional videos
     66* Customer Reviews - Add custom review sections
     67* FAQs - Include frequently asked questions
     68* Installation Guides - Provide setup instructions
     69* Seasonal Promotions - Show time-limited offers
     70* Role-based Content - Display different content for different user types
     71* Price-based Tabs - Show special tabs for products in specific price ranges
     72
     73= Display Conditions Explained =
     74
     75The plugin offers flexible display conditions to control when tabs appear:
     76
     77* All Products - Tab appears on all product pages
     78* Specific Products - Select individual products by ID
     79* Product Type - Show tabs for specific product types (Simple, Variable, Grouped, External, Downloadable, Virtual)
     80* Product Categories (Pro) - Display tabs for products in selected categories
     81* Product Tags (Pro) - Show tabs for products with specific tags
     82* Advanced Conditions (Pro) - Combine multiple conditions:
     83  * User Roles - Show tabs to specific user roles or guests
     84  * Price Range - Display tabs for products within min/max price
     85  * Stock Status - Show tabs based on product availability
     86  * Sale Status - Display tabs for products on sale or regular price
     87  * Date Range - Show tabs only during specific date periods (seasonal content)
     88  * Shipping Classes - Display tabs based on product shipping class
     89
    4090
    4191== Installation ==
    42 1.  Upload the plugin files to the /wp-content/plugins/product-tabs-woocommerce directory or install the plugin directly from the WordPress Plugins screen.
    43 2.  Activate the plugin through the Plugins screen in WordPress.
    44 3.  Navigate to WooCommerce > Products > Product Tabs to access the page.
    45 4.  Click the “Add New Product Tab” button to create your first custom tab.
     92
     931. Upload the plugin files to the `/wp-content/plugins/product-tabs-for-woo` directory, or install the plugin through the WordPress plugins screen directly.
     942. Activate the plugin through the 'Plugins' screen in WordPress.
     953. Ensure WooCommerce is installed and activated (required).
     964. Navigate to WooCommerce > Products > Product Tabs to access the management page.
     975. Click "Add New Product Tab" to create your first custom tab.
     986. Configure display conditions and priority settings.
     997. Add your content using the block editor or classic editor.
     1008. Visit a product page to see your custom tabs in action.
    46101
    47102== Frequently Asked Questions ==
    48103
    49 = Does this plugin require WooCommerce? =
    50 Yes, this plugin requires WooCommerce to be installed and activated on your WordPress site.
    51 
    52104= Can I create multiple product tabs for different products? =
    53 Yes, you can create unlimited custom tabs and set specific display conditions for each tab based on products, categories, or tags.
     105Yes, you can create unlimited custom tabs and set specific display conditions for each tab. You can show tabs on all products, specific products, or based on categories, tags, and other advanced conditions (Pro).
    54106
    55107= Can I control the order of tabs? =
    56 Yes, you can set a priority number for each tab. Lower numbers appear first in the tab order.
     108Yes, you can set a priority number for each tab. Lower numbers appear first in the tab order. Pro users can also use the drag-and-drop reorder interface for visual tab management.
    57109
    58110= Can I use HTML in tab content? =
    59 Yes, the tab content editor supports HTML and rich text formatting.
     111Yes, the tab content editor supports HTML, rich text formatting, and WordPress blocks. You can use the block editor to create rich, structured content.
    60112
    61113= Is this plugin compatible with my theme? =
    62 This plugin is compatible with most WooCommerce themes. It uses standard WooCommerce hooks and filters for tab integration.
     114This plugin is compatible with most WooCommerce themes. It uses standard WooCommerce hooks and filters for tab integration, ensuring compatibility with the WooCommerce ecosystem.
    63115
    64116= Does this plugin affect my site's performance? =
    65 The plugin is optimized for performance and only loads tab content when needed. It uses WordPress caching mechanisms and efficient database queries.
     117The plugin is optimized for performance and only loads tab content when needed. It uses efficient database queries, WordPress caching mechanisms, and lazy loading where appropriate.
     118
     119= Can I hide default WooCommerce tabs? =
     120Yes, you can show or hide the default WooCommerce tabs (Description, Additional Information, Reviews) from the Settings page.
     121
     122= Can I rename default WooCommerce tabs? =
     123Yes, Pro users can rename the default WooCommerce tabs (Description, Additional Information, Reviews) and add icons to them.
     124
     125= Can I show different tabs to different user roles? =
     126Yes, Pro users can set user role-based display conditions, allowing different tabs to be shown to different user types, including guest users.
     127
     128= Can I create seasonal or time-limited tabs? =
     129Yes, Pro users can set date range conditions to display tabs only within specific date periods, perfect for seasonal promotions or limited-time offers.
     130
     131= Does the plugin work with variable products? =
     132Yes, the plugin works with all WooCommerce product types including Simple, Variable, Grouped, External, Downloadable, and Virtual products.
     133
     134= Can I use custom icons for tabs? =
     135Yes, Pro users can add Dashicons or custom CSS classes to tab titles for enhanced visual presentation.
    66136
    67137== Screenshots ==
    68138
    69 1. Product Tabs Management Screen
    70 2. Tab Settings Interface
    71 3. Display Conditions Settings
    72 4. Product Page with Custom Tabs
     1391. Product Tabs Management Screen - Overview of all custom tabs
     1402. Tab Settings Interface - Configure display conditions and priority
     1413. Display Conditions Settings - Advanced condition options (Pro)
     1424. Block Editor Integration - Rich content creation with blocks
     1435. Tab Reorder Interface - Drag and drop tab ordering (Pro)
     1446. Product Page with Custom Tabs - Tabs displayed on product page
     1457. Settings Page - Control default tabs and global settings
     1468. Advanced Conditions - Multiple condition types (Pro)
    73147
    74148== Changelog ==
    75149
     150= 1.0.2 - 29 Nov, 2025 =
     151* Updated: Tested up to WordPress 6.9
     152* Updated: WooCommerce compatibility tested up to 10.3.5
     153* Updated: Minimum PHP requirement to 7.4
     154* Added: Freemius SDK integration
     155* Added: Advanced tab conditions system with custom rules
     156* Added: New admin interface with improved UX
     157* Improved: Codebase restructured for better maintainability
     158* Improved: Security enhancements and capability checks
     159* Improved: Performance optimizations and caching
     160* Fixed: Various UI/UX issues
     161* Removed: Outdated code and deprecated functions
     162
    76163= 1.0.1 - 16 May, 2025 =
    77 * Updated: Tested up to WordPress 6.8.
     164* Updated: Tested up to WordPress 6.8
     165* Improved: WooCommerce compatibility declarations
    78166
    79167= 1.0.0 =
    80168* Initial release
     169* Basic tab creation and management
     170* Display conditions (All, Specific Products, Product Types)
     171* Priority system
     172* Block editor support
    81173
    82174== Upgrade Notice ==
    83 
    84 == Privacy Policy ==
    85 This plugin does not collect or store any personal data. It only uses WordPress and WooCommerce core functionality to display custom tabs on product pages.
    86 
    87 == Disclaimer == 
    88 This plugin is not affiliated with or endorsed by WooCommerce or Automattic. "WooCommerce" is a trademark of Automattic Inc. This plugin is an independent product developed to extend WooCommerce functionality.
  • product-tabs-for-woo/trunk/assets/css/admin.css

    r3270803 r3405807  
    2323    color: #757575;
    2424    font-style: italic;
     25}
     26
     27/* Select2 WordPress Admin Styling */
     28.select2-container {
     29    max-width: 400px;
     30}
     31.select2-container--default .select2-selection--multiple {
     32    border: 1px solid #8c8f94;
     33    border-radius: 4px;
     34    min-height: 30px;
     35    padding: 2px;
     36}
     37.select2-container--default.select2-container--focus .select2-selection--multiple {
     38    border-color: #2271b1;
     39    box-shadow: 0 0 0 1px #2271b1;
     40}
     41.select2-container--default .select2-selection--multiple .select2-selection__choice {
     42    background-color: #2271b1;
     43    border: 1px solid #2271b1;
     44    color: #fff;
     45    padding: 2px 8px;
     46    margin: 2px;
     47}
     48.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
     49    color: #fff;
     50    margin-right: 5px;
     51}
     52.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
     53    color: #f0f0f1;
    2554}
  • product-tabs-for-woo/trunk/assets/js/admin.js

    r3270803 r3405807  
    11jQuery(document).ready(function($) {
     2    // --- Shared icon picker modal helpers (used on Settings and Post Editor) ---
     3    var $iptfwIconModal = null, iptfwAllIcons = null, iptfwIconTarget = null;
     4    function iptfwGetAllDashicons(){
     5        if (iptfwAllIcons) return iptfwAllIcons;
     6        try {
     7            var classes = new Set();
     8            for (var i=0;i<document.styleSheets.length;i++){
     9                var ss = document.styleSheets[i];
     10                if (!ss || !ss.href || ss.href.indexOf('dashicons') === -1) continue;
     11                var rules = ss.cssRules || ss.rules; if (!rules) continue;
     12                for (var j=0;j<rules.length;j++){
     13                    var sel = rules[j].selectorText || '';
     14                    if (sel && sel.indexOf('.dashicons-') !== -1){
     15                        sel.split(',').forEach(function(part){
     16                            var m = (part.trim().match(/\.dashicons-([a-z0-9\-]+)/));
     17                            if (m) classes.add('dashicons-' + m[1]);
     18                        });
     19                    }
     20                }
     21            }
     22            iptfwAllIcons = Array.from(classes).sort();
     23        } catch(e){
     24            iptfwAllIcons = ['dashicons-admin-generic','dashicons-star-filled','dashicons-yes','dashicons-heart','dashicons-cart','dashicons-products','dashicons-format-video','dashicons-clipboard','dashicons-analytics','dashicons-download'];
     25        }
     26        return iptfwAllIcons;
     27    }
     28    function iptfwEnsureIconModal(){
     29        if ($iptfwIconModal) return $iptfwIconModal;
     30        $iptfwIconModal = $('<div id="iptfw-icon-modal" class="iptfw-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:100000;">\
     31            <div class="iptfw-modal-inner" style="background:#fff;max-width:700px;margin:60px auto;padding:16px;border-radius:6px;">\
     32                <div style="display:flex;justify-content:space-between;align-items:center;">\
     33                    <h2 style="margin:0 0 12px;">Select an Icon</h2>\
     34                    <button type="button" class="button" id="iptfw-icon-close">Close</button>\
     35                </div>\
     36                <input type="text" id="iptfw-icon-search" placeholder="Search..." class="regular-text" style="width:100%;margin-bottom:12px;"/>\
     37                <div class="iptfw-icon-grid" style="display:grid;grid-template-columns:repeat(6,1fr);gap:10px;max-height:360px;overflow:auto;"></div>\
     38            </div>\
     39        </div>');
     40        $('body').append($iptfwIconModal);
     41        function renderIcons(filter){
     42            var icons = iptfwGetAllDashicons();
     43            var $grid = $iptfwIconModal.find('.iptfw-icon-grid').empty();
     44            icons.forEach(function(cls){
     45                if (filter && cls.toLowerCase().indexOf(filter.toLowerCase()) === -1) return;
     46                var $item = $('<button type="button" class="button" style="height:48px;display:flex;align-items:center;justify-content:center;">\
     47                    <span class="dashicons '+cls+'"></span></button>').data('icon', cls);
     48                $grid.append($item);
     49            });
     50        }
     51        $iptfwIconModal.on('click', '#iptfw-icon-close', function(){ $iptfwIconModal.hide(); });
     52        $iptfwIconModal.on('click', function(e){ if (e.target === $iptfwIconModal[0]) { $iptfwIconModal.hide(); } });
     53        $iptfwIconModal.on('keyup', '#iptfw-icon-search', function(){ renderIcons($(this).val()); });
     54        $iptfwIconModal.on('click', '.iptfw-icon-grid .button', function(){ if (!iptfwIconTarget) return; iptfwIconTarget.val($(this).data('icon')).trigger('change'); $iptfwIconModal.hide(); });
     55        // Initial fill
     56        renderIcons('');
     57        return $iptfwIconModal;
     58    }
    259    // Display type toggle functionality
    360    $('#display_type').on('change', function() {
    4         $('#specific_products, #product_categories, #product_tags').hide();
     61        $('#specific_products, #product_type, #product_categories, #product_tags, #advanced_conditions').hide();
    562        switch($(this).val()) {
    663            case 'specific':
    764                $('#specific_products').show();
    865                break;
     66            case 'product_type':
     67                $('#product_type').show();
     68                break;
    969            case 'categories':
    1070                $('#product_categories').show();
     
    1373                $('#product_tags').show();
    1474                break;
     75            case 'advanced':
     76                $('#advanced_conditions').show();
     77                break;
    1578        }
    1679    });
     
    1982    $('#display_type').trigger('change');
    2083
    21     // Settings button functionality
    22     function initSettingsButton() {
    23         var $addNewButton = $('.wrap h1.wp-heading-inline').next('.page-title-action');
    24         if ($addNewButton.length) {
    25             $addNewButton.after(
    26                 '<a href="' + iptfw_admin.settings_url + '" class="page-title-action" style="margin-left: 10px;">' +
    27                 iptfw_admin.settings_text + '</a>'
    28             );
    29         }
    30     }
    31 
    32     if (iptfw_admin.is_product_tab_page) {
    33         initSettingsButton();
     84    // Initialize Select2 for product search
     85    if ($('.iptfw-product-search').length) {
     86        $('.iptfw-product-search').select2({
     87            ajax: {
     88                url: iptfw_admin.ajax_url,
     89                dataType: 'json',
     90                delay: 250,
     91                data: function (params) {
     92                    return {
     93                        q: params.term,
     94                        action: 'iptfw_search_products',
     95                        nonce: iptfw_admin.admin_nonce
     96                    };
     97                },
     98                processResults: function (data) {
     99                    return {
     100                        results: data.results
     101                    };
     102                },
     103                cache: true
     104            },
     105            minimumInputLength: 2,
     106            placeholder: 'Search for products...',
     107            allowClear: true
     108        });
     109    }
     110
     111    // Settings and Reorder are now handled by tabs navigation
     112
     113    // Global icon picker handler (works on settings page and block editor)
     114    $(document).on('click', '.iptfw-icon-picker', function(e){
     115        e.preventDefault();
     116        if (!iptfw_admin.has_premium) { window.alert('This is a Pro feature.'); return; }
     117        iptfwEnsureIconModal();
     118        iptfwIconTarget = $('#'+$(this).data('target'));
     119        $('#iptfw-icon-search').val('');
     120        $iptfwIconModal.show();
     121        $('#iptfw-icon-search').trigger('focus');
     122    });
     123
     124    // Settings: icon picker modal
     125    if (iptfw_admin.is_settings_page) {
     126
     127        // Add picker buttons and preview next to icon fields; hide raw input
     128        ['icon_description','icon_additional_information','icon_reviews'].forEach(function(id){
     129            var $input = $('#'+id);
     130            if ($input.length) {
     131                var $btn = $('<button type="button" class="button iptfw-icon-picker" style="margin-left:8px;">Select Icon</button>').attr('data-target', id);
     132                var $preview = $('<span class="iptfw-icon-preview" style="display:inline-flex;align-items:center;justify-content:center;margin-left:8px;min-width:28px;position:relative;height:28px;width:28px;"></span>');
     133                function syncPreview(){
     134                    var cls = $input.val();
     135                    $preview.empty();
     136                    if (cls){
     137                        $preview.append($('<span/>').addClass('dashicons').addClass(cls));
     138                        // Simple clear button (×) at top-right
     139                        var $clear = $('<button type="button" class="iptfw-remove-icon" aria-label="Remove icon">×</button>').attr('title','Remove icon');
     140                        $clear.css({
     141                            position:'absolute',
     142                            top:'-6px',
     143                            right:'-6px',
     144                            width:'18px',
     145                            height:'18px',
     146                            lineHeight:'14px',
     147                            textAlign:'center',
     148                            background:'#f0f0f1',
     149                            border:'1px solid #ccd0d4',
     150                            borderRadius:'50%',
     151                            cursor:'pointer',
     152                            padding:0,
     153                            fontWeight:'bold'
     154                        });
     155                        $preview.append($clear);
     156                    }
     157                }
     158                $input.css({width:'0',padding:'0',border:'0',opacity:0,position:'absolute'});
     159                $input.after($btn).after($preview);
     160                syncPreview();
     161                $input.on('change', syncPreview);
     162            }
     163        });
     164
     165        // Remove icon handler
     166        $(document).on('click', '.iptfw-remove-icon', function(e){
     167            e.preventDefault();
     168            var $wrap = $(this).closest('.iptfw-icon-preview');
     169            var $input = $wrap.prevAll('input[type="text"]').first();
     170            if ($input.length){
     171                $input.val('').trigger('change');
     172            }
     173        });
     174    }
     175
     176    // Post editor: apply picker/preview to custom tab sidebar field if present
     177    (function(){
     178        var $input = $('#iptfw_icon');
     179        if ($input.length) {
     180            var $btn = $('.iptfw-icon-picker[data-target="iptfw_icon"]');
     181            var $preview = $input.siblings('.iptfw-icon-preview');
     182            // Hide the raw input; keep value for submission
     183            $input.css({width:'0',padding:'0',border:'0',opacity:0,position:'absolute'});
     184            // Click handler to open modal on editor
     185            $(document).on('click', '.iptfw-icon-picker[data-target="iptfw_icon"]', function(e){
     186                e.preventDefault();
     187                if (!iptfw_admin.has_premium) { window.alert('This is a Pro feature.'); return; }
     188                iptfwEnsureIconModal();
     189                iptfwIconTarget = $input;
     190                $('#iptfw-icon-search').val('');
     191                $iptfwIconModal.show();
     192                $('#iptfw-icon-search').trigger('focus');
     193            });
     194            function syncPreview(){
     195                var cls = $input.val();
     196                $preview.empty();
     197                if (cls){
     198                    $preview.append($('<span/>').addClass('dashicons').addClass(cls));
     199                    var $clear = $('<button type="button" class="iptfw-remove-icon" aria-label="Remove icon">×</button>').attr('title','Remove icon');
     200                    $clear.css({position:'absolute',top:'-6px',right:'-6px',width:'18px',height:'18px',lineHeight:'14px',textAlign:'center',background:'#f0f0f1',border:'1px solid #ccd0d4',borderRadius:'50%',cursor:'pointer',padding:0,fontWeight:'bold'});
     201                    $preview.append($clear);
     202                }
     203            }
     204            $input.on('change', syncPreview);
     205            syncPreview();
     206
     207            $(document).on('click', '.iptfw-remove-icon', function(e){
     208                if ($preview.has(this).length || $preview.is($(this).parent())) {
     209                    e.preventDefault();
     210                    $input.val('').trigger('change');
     211                }
     212            });
     213        }
     214    })();
     215
     216    // Reorder page sortable + save
     217    if (iptfw_admin.is_reorder_page) {
     218        var $tbody = $('#iptfw-reorder-list');
     219        if (iptfw_admin.can_reorder) {
     220            $tbody.sortable({
     221                items: 'tr',
     222                cursor: 'move',
     223                axis: 'y',
     224                containment: 'parent'
     225            });
     226        } else {
     227            // Disable interaction
     228            $tbody.find('tr').addClass('iptfw-disabled');
     229        }
     230
     231        $('#iptfw-save-order').on('click', function(e) {
     232            e.preventDefault();
     233            var $btn = $(this);
     234            var order = [];
     235            $tbody.find('tr').each(function() {
     236                order.push($(this).data('id').toString());
     237            });
     238            $btn.prop('disabled', true).text('Saving...');
     239            if (!iptfw_admin.can_reorder) {
     240                return false;
     241            }
     242            $.post(iptfw_admin.ajax_url, {
     243                action: 'iptfw_save_order',
     244                nonce: iptfw_admin.nonce,
     245                order: order
     246            }).done(function(resp) {
     247                var ok = resp && (resp.success === true || resp === '1');
     248                var $notice = $('<div class="notice notice-success is-dismissible"><p>Order saved.</p></div>');
     249                if (!ok) {
     250                    $notice = $('<div class="notice notice-error is-dismissible"><p>Failed to save order.</p></div>');
     251                }
     252                $('.wrap h1').after($notice);
     253            }).fail(function(xhr) {
     254                var msg = 'Failed to save order';
     255                if (xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
     256                    msg = xhr.responseJSON.data.message;
     257                }
     258                var $notice = $('<div class="notice notice-error is-dismissible"><p>'+ msg +'</p></div>');
     259                $('.wrap h1').after($notice);
     260            }).always(function(){
     261                $btn.prop('disabled', false).text('Save Order');
     262            });
     263        });
    34264    }
    35265});
  • product-tabs-for-woo/trunk/product-tabs-for-woo.php

    r3294577 r3405807  
    11<?php
     2
    23/**
    3  * Plugin Name: Product Tabs for Woo
     4 * Plugin Name: Product Tabs for WooCommerce
    45 * Plugin URI: https://stackwc.com/plugins/product-tabs-for-woo/
    5  * Description: Adds custom product tabs to WooCommerce products.
    6  * Version: 1.0.1
     6 * Description: Add custom product tabs to your WooCommerce products with advanced display conditions, priority settings, and powerful customization options.
     7 * Version: 1.0.2
    78 * Author: StackWC
    89 * Author URI: https://stackwc.com/
     
    1011 * Domain Path: /languages
    1112 * Requires at least: 5.0
    12  * Requires PHP: 7.2
     13 * Requires PHP: 7.4
    1314 * License: GPLv3
    1415 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1516 * WC requires at least: 7.2
    16  * WC tested up to: 9.8.5
     17 * WC tested up to: 10.3.5
    1718 * Requires Plugins: woocommerce
    1819 *
    1920 * Disclaimer: This plugin is not affiliated with or endorsed by WooCommerce or Automattic.
    2021 * "WooCommerce" is a trademark of Automattic Inc. This is an independent plugin.
    21  * Copyright: ilmosys infotech LLP
     22 * Copyright: StackWC brand by ilmosys infotech LLP
    2223 */
    23 
    24 if (!defined('ABSPATH')) {
     24if ( !defined( 'ABSPATH' ) ) {
    2525    exit;
    2626}
    27 
    28 // Define plugin constants first
    29 define('IPTFW_VERSION', '1.0.1');
    30 define('IPTFW_FILE', __FILE__);
    31 define('IPTFW_PATH', plugin_dir_path(__FILE__));
    32 define('IPTFW_URL', plugin_dir_url(__FILE__));
    33 
    34 // Check if WooCommerce is active
    35 function iptfw_is_woocommerce_active() {
    36     return in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')));
    37 }
    38 
    39 // Declare WooCommerce compatibility
    40 function iptfw_declare_woocommerce_compatibility() {
    41     if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
    42         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
    43         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('cart_checkout_blocks', __FILE__, true);
    44         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('analytics', __FILE__, true);
    45         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_block_editor', __FILE__, true);
    46         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_block_templates', __FILE__, true);
    47         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_editor', __FILE__, true);
    48         \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('product_custom_fields', __FILE__, true);
    49     }
    50 }
    51 add_action('before_woocommerce_init', 'iptfw_declare_woocommerce_compatibility');
    52 
    53 // Check WooCommerce compatibility
    54 function iptfw_check_woocommerce_compatibility() {
    55     if (!iptfw_is_woocommerce_active()) {
    56         return;
    57     }
    58 
    59     // Get WooCommerce version
    60     $wc_version = WC()->version;
    61    
    62     // Get required WooCommerce version from plugin header
    63     $required_wc_version = '7.2';
    64    
    65     // Check if WooCommerce version meets requirements
    66     if (version_compare($wc_version, $required_wc_version, '<')) {
    67         add_action('admin_notices', function() use ($required_wc_version, $wc_version) {
    68             ?>
    69             <div class="notice notice-error">
    70                 <p><?php
    71                     /* translators: 1: Required WooCommerce version, 2: Current WooCommerce version */
    72                     printf(
    73                         /* translators: 1: Required WooCommerce version, 2: Current WooCommerce version */
    74                         esc_html__('Custom Product Tabs for WooCommerce requires WooCommerce version %1$s or higher. You are running version %2$s. Please update WooCommerce to use this plugin.', 'product-tabs-for-woo'),
    75                         esc_html($required_wc_version),
    76                         esc_html($wc_version)
    77                     );
    78                 ?></p>
    79             </div>
    80             <?php
    81         });
    82         return false;
    83     }
    84 
    85     return true;
    86 }
    87 add_action('admin_init', 'iptfw_check_woocommerce_compatibility');
    88 
    89 // Disable activation if WooCommerce is not active
    90 function iptfw_disable_activation() {
    91     if (!iptfw_is_woocommerce_active()) {
    92         // Get the plugin file
    93         $plugin_file = plugin_basename(IPTFW_FILE);
    94        
    95         // Add a notice about WooCommerce requirement
    96         add_action('admin_notices', function() {
    97             ?>
    98             <div class="notice notice-error">
    99                 <p><?php esc_html_e('Product Tabs for WooCommerce requires WooCommerce to be installed and activated.', 'product-tabs-for-woo'); ?></p>
    100             </div>
    101             <?php
    102         });
    103        
    104         // Add a notice in the plugins list
    105         add_action('after_plugin_row_' . $plugin_file, function() {
    106             ?>
    107             <tr class="plugin-update-tr">
    108                 <td colspan="4" class="plugin-update colspanchange">
    109                     <div class="notice notice-error inline">
    110                         <p><?php esc_html_e('This plugin cannot be activated because required plugins are missing or inactive.', 'product-tabs-for-woo'); ?></p>
    111                         <p><?php esc_html_e('Requires: WooCommerce', 'product-tabs-for-woo'); ?></p>
    112                     </div>
    113                 </td>
    114             </tr>
    115             <?php
    116         });
    117        
    118         // Disable the activate link
    119         add_filter('plugin_action_links_' . $plugin_file, function($actions) {
    120             if (isset($actions['activate'])) {
    121                 $actions['activate'] = '<span class="button button-disabled">' . esc_html__('Activate', 'product-tabs-for-woo') . '</span>';
    122             }
    123             return $actions;
    124         });
    125 
    126         // Prevent activation
    127         add_filter('plugin_action_links_' . $plugin_file, function($actions) {
    128             if (isset($actions['activate'])) {
    129                 unset($actions['activate']);
    130             }
    131             return $actions;
    132         });
    133     }
    134 }
    135 add_action('admin_init', 'iptfw_disable_activation');
    136 
    137 // Prevent activation if WooCommerce is not active
    138 function iptfw_prevent_activation($plugin) {
    139     if ($plugin === plugin_basename(IPTFW_FILE) && !iptfw_is_woocommerce_active()) {
    140         wp_die(
    141             esc_html__('This plugin requires WooCommerce to be installed and activated.', 'product-tabs-for-woo'),
    142             esc_html__('Plugin Activation Error', 'product-tabs-for-woo'),
    143             array('back_link' => true)
    144         );
    145     }
    146 }
    147 add_action('activate_plugin', 'iptfw_prevent_activation');
    148 
    149 // Add compatibility information to WooCommerce status
    150 function iptfw_add_compatibility_info($sections) {
    151     // Verify nonce for admin actions
    152     if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_compatibility_nonce')) {
    153         return $sections;
    154     }
    155 
    156     $sections['iptfw_compatibility'] = array(
    157         'title' => esc_html__('Custom Product Tabs Compatibility', 'product-tabs-for-woo'),
    158         'callback' => 'iptfw_compatibility_section'
    159     );
    160     return $sections;
    161 }
    162 add_filter('woocommerce_admin_status_tabs', 'iptfw_add_compatibility_info');
    163 
    164 function iptfw_compatibility_section() {
    165     // Verify nonce for admin actions
    166     if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_compatibility_nonce')) {
    167         return;
    168     }
    169 
    170     ?>
    171     <h2><?php esc_html_e('Custom Product Tabs Compatibility', 'product-tabs-for-woo'); ?></h2>
    172     <table class="widefat" cellspacing="0">
    173         <thead>
    174             <tr>
    175                 <th><?php esc_html_e('Feature', 'product-tabs-for-woo'); ?></th>
    176                 <th><?php esc_html_e('Status', 'product-tabs-for-woo'); ?></th>
    177             </tr>
    178         </thead>
    179         <tbody>
    180             <tr>
    181                 <td><?php esc_html_e('WooCommerce Version', 'product-tabs-for-woo'); ?></td>
    182                 <td>
    183                     <?php
    184                     $wc_version = WC()->version;
    185                     $required_wc_version = '7.2';
    186                     if (version_compare($wc_version, $required_wc_version, '>=')) {
    187                         echo '<mark class="yes">' . esc_html($wc_version) . '</mark>';
    188                     } else {
    189                         echo '<mark class="error">' . esc_html($wc_version) . ' (' . esc_html__('Update Required', 'product-tabs-for-woo') . ')</mark>';
    190                     }
    191                     ?>
    192                 </td>
    193             </tr>
    194             <?php if (class_exists('WC_Admin_Features')): ?>
    195             <tr>
    196                 <td><?php esc_html_e('Analytics', 'product-tabs-for-woo'); ?></td>
    197                 <td>
    198                     <?php
    199                     $features = WC_Admin_Features::get_compatible_features();
    200                     if (isset($features['analytics']) && $features['analytics']) {
    201                         echo '<mark class="warning">' . esc_html__('Enabled - May need testing', 'product-tabs-for-woo') . '</mark>';
    202                     } else {
    203                         echo '<mark class="yes">' . esc_html__('Disabled', 'product-tabs-for-woo') . '</mark>';
    204                     }
    205                     ?>
    206                 </td>
    207             </tr>
    208             <?php endif; ?>
    209         </tbody>
    210     </table>
    211     <?php
    212 }
    213 
    214 // Then include required files
    215 require_once IPTFW_PATH . 'includes/class-iptfw-settings.php';
    216 require_once IPTFW_PATH . 'includes/class-iptfw-admin.php';
    217 
    218 if (!class_exists('IPTFW_Product_Tabs')) {
     27// Freemius Integration
     28if ( !function_exists( 'iptfw_fs' ) ) {
     29    // Create a helper function for easy SDK access.
     30    function iptfw_fs() {
     31        global $iptfw_fs;
     32        if ( !isset( $iptfw_fs ) ) {
     33            // Include Freemius SDK.
     34            require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
     35            $iptfw_fs = fs_dynamic_init( array(
     36                'id'             => '19118',
     37                'slug'           => 'product-tabs-for-woo',
     38                'premium_slug'   => 'product-tabs-for-woo-pro',
     39                'type'           => 'plugin',
     40                'public_key'     => 'pk_16d450f47372bb8f7243e970e61b8',
     41                'is_premium'     => false,
     42                'premium_suffix' => 'Pro',
     43                'has_addons'     => true,
     44                'has_paid_plans' => true,
     45                'menu'           => array(
     46                    'first-path' => 'plugins.php',
     47                    'support'    => false,
     48                ),
     49                'is_live'        => true,
     50            ) );
     51        }
     52        return $iptfw_fs;
     53    }
     54
     55    // Init Freemius.
     56    iptfw_fs();
     57    // Signal that SDK was initiated.
     58    do_action( 'iptfw_fs_loaded' );
     59}
     60// Define plugin constants
     61define( 'IPTFW_VERSION', '1.0.2' );
     62define( 'IPTFW_FILE', __FILE__ );
     63define( 'IPTFW_PATH', plugin_dir_path( __FILE__ ) );
     64define( 'IPTFW_URL', plugin_dir_url( __FILE__ ) );
     65// Include core files
     66require_once IPTFW_PATH . 'stackwc-core/stackwc-core.php';
     67require_once IPTFW_PATH . 'includes/class-stackwc-compatibility.php';
     68require_once IPTFW_PATH . 'includes/class-stackwc-post-type.php';
     69require_once IPTFW_PATH . 'includes/class-stackwc-settings.php';
     70require_once IPTFW_PATH . 'includes/class-stackwc-admin.php';
     71require_once IPTFW_PATH . 'includes/class-stackwc-meta-boxes.php';
     72require_once IPTFW_PATH . 'includes/class-stackwc-tabs.php';
     73require_once IPTFW_PATH . 'includes/class-stackwc-conditions.php';
     74require_once IPTFW_PATH . 'includes/class-stackwc-ajax.php';
     75require_once IPTFW_PATH . 'includes/class-stackwc-columns.php';
     76// WooCommerce Compatibility
     77add_action( 'before_woocommerce_init', array('IPTFW_Compatibility', 'declare_woocommerce_compatibility') );
     78add_action( 'admin_init', array('IPTFW_Compatibility', 'check_woocommerce_compatibility') );
     79add_action( 'admin_init', array('IPTFW_Compatibility', 'disable_activation') );
     80add_action( 'activate_plugin', array('IPTFW_Compatibility', 'prevent_activation') );
     81add_filter( 'woocommerce_admin_status_tabs', array('IPTFW_Compatibility', 'add_compatibility_info') );
     82// Register plugin core menu
     83add_action( 'init', function () {
     84    stackwc_register_plugin_menu( array(
     85        'menu_title'  => __( 'Product Tabs for Woo', 'product-tabs-for-woo' ),
     86        'page_title'  => __( 'Product Tabs for WooCommerce Settings', 'product-tabs-for-woo' ),
     87        'capability'  => 'manage_woocommerce',
     88        'menu_slug'   => 'iptfw_product_tab',
     89        'description' => __( 'Adds custom product tabs to WooCommerce products.', 'product-tabs-for-woo' ),
     90        'menu_url'    => admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ),
     91        'callback'    => function () {
     92            // Check if user has permission to manage WooCommerce
     93            if ( !current_user_can( 'manage_woocommerce' ) ) {
     94                wp_die( __( 'Sorry, you are not allowed to access this page.', 'product-tabs-for-woo' ) );
     95            }
     96            // Redirect immediately
     97            wp_safe_redirect( admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ) );
     98            exit;
     99        },
     100    ) );
     101} );
     102// Modify submenu URL to point directly to the post type edit page
     103// Run on both admin_menu (late) and admin_init (late) to catch menu registration
     104add_action( 'admin_menu', function () {
     105    global $submenu;
     106    if ( isset( $submenu['stackwc'] ) ) {
     107        foreach ( $submenu['stackwc'] as $key => $item ) {
     108            if ( isset( $item[2] ) && $item[2] === 'iptfw_product_tab' ) {
     109                // Replace the menu slug with the direct URL
     110                $submenu['stackwc'][$key][2] = 'edit.php?post_type=iptfw_product_tab&tab=settings';
     111                break;
     112            }
     113        }
     114    }
     115}, 999 );
     116add_action( 'admin_init', function () {
     117    global $submenu;
     118    if ( isset( $submenu['stackwc'] ) ) {
     119        foreach ( $submenu['stackwc'] as $key => $item ) {
     120            if ( isset( $item[2] ) && $item[2] === 'iptfw_product_tab' ) {
     121                // Replace the menu slug with the direct URL
     122                $submenu['stackwc'][$key][2] = 'edit.php?post_type=iptfw_product_tab';
     123                break;
     124            }
     125        }
     126    }
     127}, 999 );
     128// Early redirect hook to catch the page load before WordPress permission checks
     129add_action( 'admin_init', function () {
     130    if ( isset( $_GET['page'] ) && $_GET['page'] === 'iptfw_product_tab' && !isset( $_GET['post_type'] ) ) {
     131        // Check if user has permission
     132        if ( current_user_can( 'manage_woocommerce' ) ) {
     133            wp_safe_redirect( admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ) );
     134            exit;
     135        }
     136    }
     137}, 1 );
     138// Provide plugin-specific template path
     139add_filter(
     140    'stackwc_plugin_template_path',
     141    function ( $template_path, $current_page ) {
     142        if ( $current_page === 'wc-settings&tab=stackwc_qbnbfw' ) {
     143            return STACKWC_QBNBFW_PATH . 'includes/templates/wc-settings&tab=stackwc_qbnbfw.php';
     144        }
     145        return $template_path;
     146    },
     147    10,
     148    2
     149);
     150/**
     151 * Main Plugin Class
     152 */
     153if ( !class_exists( 'IPTFW_Product_Tabs' ) ) {
    219154    class IPTFW_Product_Tabs {
    220155        private static $instance = null;
     156
    221157        public $settings;
     158
    222159        public $admin;
    223         private $current_page = '';
    224         private $current_tab = '';
    225160
    226161        public static function instance() {
    227             if (null === self::$instance) {
     162            if ( null === self::$instance ) {
    228163                self::$instance = new self();
    229164            }
     
    232167
    233168        public function __construct() {
    234             if (!iptfw_is_woocommerce_active()) {
     169            if ( !IPTFW_Compatibility::is_woocommerce_active() ) {
    235170                return;
    236171            }
    237 
    238172            $this->init_hooks();
    239             $this->set_current_page();
    240             $this->set_current_tab();
    241         }
    242 
    243         private function set_current_page() {
    244             // Verify nonce for admin actions
    245             if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_page_nonce')) {
    246                 return;
    247             }
    248             $this->current_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    249         }
    250 
    251         private function set_current_tab() {
    252             // Verify nonce for admin actions
    253             if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_tab_nonce')) {
    254                 return;
    255             }
    256             $this->current_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'list';
    257         }
    258 
    259         public function is_woocommerce_active() {
    260             return in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')));
    261         }
    262 
    263         public function woocommerce_missing_notice() {
    264             ?>
    265             <div class="notice notice-error">
    266                 <p><?php esc_html_e('Custom Product Tabs requires WooCommerce to be installed and active.', 'product-tabs-for-woo'); ?></p>
    267             </div>
    268             <?php
    269173        }
    270174
     
    273177            $this->admin = new IPTFW_Admin($this);
    274178            $this->settings = new IPTFW_Settings();
    275 
    276179            // Add text domain loading
    277             add_action('init', array($this, 'load_textdomain'));
    278            
    279             add_action('init', array($this, 'register_post_type'));
    280             add_action('admin_init', array($this, 'admin_init'));
    281             add_action('add_meta_boxes', array($this, 'add_meta_boxes'));
    282             add_action('save_post', array($this, 'save_meta_boxes'));
    283             add_filter('woocommerce_product_tabs', array($this, 'add_product_tabs'));
    284 
    285             // Add plugin action links
    286             add_filter('plugin_action_links_' . plugin_basename(IPTFW_FILE), array($this, 'add_plugin_action_links'));
    287 
    288             // Add these column filters back
    289             add_filter('manage_iptfw_product_tab_posts_columns', array($this, 'set_custom_columns'));
    290             add_action('manage_iptfw_product_tab_posts_custom_column', array($this, 'custom_column_content'), 10, 2);
    291             add_filter('manage_edit-iptfw_product_tab_sortable_columns', array($this, 'sortable_columns'));
     180            add_action( 'init', array($this, 'load_textdomain') );
     181            // Post type and meta fields
     182            add_action( 'init', array('IPTFW_Post_Type', 'register_post_type') );
     183            add_action( 'init', array('IPTFW_Post_Type', 'register_meta_fields') );
     184            // Meta boxes
     185            add_action( 'add_meta_boxes', array('IPTFW_Meta_Boxes', 'add_meta_boxes') );
     186            add_action( 'save_post', array('IPTFW_Meta_Boxes', 'save_meta_boxes') );
     187            // Product tabs
     188            add_filter( 'woocommerce_product_tabs', array('IPTFW_Tabs', 'add_product_tabs') );
     189            // Plugin action links
     190            add_filter( 'plugin_action_links_' . plugin_basename( IPTFW_FILE ), array($this, 'add_plugin_action_links') );
     191            // Admin columns
     192            add_filter( 'manage_iptfw_product_tab_posts_columns', array('IPTFW_Columns', 'set_custom_columns') );
     193            add_action(
     194                'manage_iptfw_product_tab_posts_custom_column',
     195                array('IPTFW_Columns', 'custom_column_content'),
     196                10,
     197                2
     198            );
     199            add_filter( 'manage_edit-iptfw_product_tab_sortable_columns', array('IPTFW_Columns', 'sortable_columns') );
     200            // AJAX handlers
     201            add_action( 'wp_ajax_iptfw_save_order', array('IPTFW_Ajax', 'save_order') );
     202            add_action( 'wp_ajax_iptfw_search_products', array('IPTFW_Ajax', 'search_products') );
    292203        }
    293204
    294205        public function load_textdomain() {
    295             load_plugin_textdomain(
    296                 'product-tabs-woocommerce',
    297                 false,
    298                 dirname(plugin_basename(IPTFW_FILE)) . '/languages'
    299             );
    300         }
    301 
    302         public function register_post_type() {
    303             $labels = array(
    304                 'name' => esc_html__('Product Tabs', 'product-tabs-for-woo'),
    305                 'singular_name' => esc_html__('Product Tab', 'product-tabs-for-woo'),
    306                 'add_new' => esc_html__('Add New', 'product-tabs-for-woo'),
    307                 'add_new_item' => esc_html__('Add New Product Tab', 'product-tabs-for-woo'),
    308                 'edit_item' => esc_html__('Edit Product Tab', 'product-tabs-for-woo'),
    309                 'new_item' => esc_html__('New Product Tab', 'product-tabs-for-woo'),
    310                 'view_item' => esc_html__('View Product Tab', 'product-tabs-for-woo'),
    311                 'search_items' => esc_html__('Search Product Tabs', 'product-tabs-for-woo'),
    312                 'not_found' => esc_html__('No product tabs found', 'product-tabs-for-woo'),
    313                 'not_found_in_trash' => esc_html__('No product tabs found in trash', 'product-tabs-for-woo'),
    314                 'menu_name' => esc_html__('Product Tabs', 'product-tabs-for-woo')
    315             );
    316 
    317             $args = array(
    318                 'labels' => $labels,
    319                 'public' => false,
    320                 'show_ui' => true,
    321                 'show_in_menu' => false,
    322                 'capability_type' => 'post',
    323                 'hierarchical' => false,
    324                 'supports' => array('title', 'editor'),
    325                 'show_in_rest' => true,
    326                 'rewrite' => false
    327             );
    328 
    329             register_post_type('iptfw_product_tab', $args);
    330         }
    331 
    332         public function admin_init() {
    333             global $parent_file, $submenu_file;
    334            
    335             // Verify nonce for admin actions
    336             if (isset($_GET['_wpnonce']) && !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iptfw_admin_nonce')) {
    337                 return;
    338             }
    339            
    340             // Keep Product Tabs menu active when viewing settings
    341             if (isset($_GET['page']) && sanitize_text_field(wp_unslash($_GET['page'])) === 'iptfw-settings') {
    342                 $parent_file = 'edit.php?post_type=product';
    343                 $submenu_file = 'edit.php?post_type=iptfw_product_tab';
    344             }
    345         }
    346 
    347         public function add_meta_boxes() {
    348             add_meta_box(
    349                 'iptfw_tab_settings',
    350                 esc_html__('Tab Settings', 'product-tabs-for-woo'),
    351                 array($this, 'render_tab_settings'),
    352                 'iptfw_product_tab',
    353                 'side',
    354                 'high'
    355             );
    356         }
    357 
    358         public function render_tab_settings($post) {
    359             wp_nonce_field('iptfw_save_meta', 'iptfw_meta_nonce');
    360            
    361             $priority = get_post_meta($post->ID, '_iptfw_tab_priority', true);
    362             $display_type = get_post_meta($post->ID, '_iptfw_display_type', true) ?: 'all';
    363             $products = get_post_meta($post->ID, '_iptfw_products', true);
    364             $categories = get_post_meta($post->ID, '_iptfw_categories', true);
    365             $tags = get_post_meta($post->ID, '_iptfw_tags', true);
    366             ?>
    367             <div class="iptfw-settings-wrapper">
    368                 <div class="components-base-control">
    369                     <label class="components-base-control__label" for="tab_priority">
    370                         <?php esc_html_e('Priority', 'product-tabs-for-woo'); ?>
    371                     </label>
    372                     <input type="number"
    373                            id="tab_priority"
    374                            name="tab_priority"
    375                            value="<?php echo esc_attr($priority); ?>"
    376                            class="components-text-control__input" />
    377                     <p class="components-base-control__help">
    378                         <?php esc_html_e('Lower numbers appear first', 'product-tabs-for-woo'); ?>
    379                     </p>
    380                 </div>
    381 
    382                 <div class="components-base-control">
    383                     <label class="components-base-control__label">
    384                         <?php esc_html_e('Display Conditions', 'product-tabs-for-woo'); ?>
    385                     </label>
    386                     <select name="display_type"
    387                             id="display_type"
    388                             class="components-select-control__input">
    389                         <option value="all" <?php selected($display_type, 'all'); ?>>
    390                             <?php esc_html_e('All Products', 'product-tabs-for-woo'); ?>
    391                         </option>
    392                         <option value="specific" <?php selected($display_type, 'specific'); ?>>
    393                             <?php esc_html_e('Specific Products', 'product-tabs-for-woo'); ?>
    394                         </option>
    395                         <option value="categories" <?php selected($display_type, 'categories'); ?>>
    396                             <?php esc_html_e('Product Categories', 'product-tabs-for-woo'); ?>
    397                         </option>
    398                         <option value="tags" <?php selected($display_type, 'tags'); ?>>
    399                             <?php esc_html_e('Product Tags', 'product-tabs-for-woo'); ?>
    400                         </option>
    401                     </select>
    402                 </div>
    403 
    404                 <div id="specific_products" class="components-base-control" style="display: <?php echo $display_type === 'specific' ? 'block' : 'none'; ?>">
    405                     <label class="components-base-control__label" for="tab_products">
    406                         <?php esc_html_e('Product IDs', 'product-tabs-for-woo'); ?>
    407                     </label>
    408                     <input type="text"
    409                            id="tab_products"
    410                            name="tab_products"
    411                            value="<?php echo esc_attr($products); ?>"
    412                            class="components-text-control__input" />
    413                     <p class="components-base-control__help">
    414                         <?php esc_html_e('Enter product IDs separated by commas', 'product-tabs-for-woo'); ?>
    415                     </p>
    416                 </div>
    417 
    418                 <div id="product_categories" class="components-base-control" style="display: <?php echo $display_type === 'categories' ? 'block' : 'none'; ?>">
    419                     <label class="components-base-control__label" for="tab_categories">
    420                         <?php esc_html_e('Select Categories', 'product-tabs-for-woo'); ?>
    421                     </label>
    422                     <select name="tab_categories[]"
    423                             id="tab_categories"
    424                             class="components-select-control__input"
    425                             multiple>
    426                         <?php
    427                         $product_categories = get_terms(array(
    428                             'taxonomy' => 'product_cat',
    429                             'hide_empty' => false
    430                         ));
    431                         $selected_cats = explode(',', $categories);
    432                         foreach ($product_categories as $category) {
    433                             printf(
    434                                 '<option value="%s" %s>%s</option>',
    435                                 esc_attr($category->term_id),
    436                                 selected(in_array($category->term_id, $selected_cats), true, false),
    437                                 esc_html($category->name)
    438                             );
    439                         }
    440                         ?>
    441                     </select>
    442                 </div>
    443 
    444                 <div id="product_tags" class="components-base-control" style="display: <?php echo $display_type === 'tags' ? 'block' : 'none'; ?>">
    445                     <label class="components-base-control__label" for="tab_tags">
    446                         <?php esc_html_e('Select Tags', 'product-tabs-for-woo'); ?>
    447                     </label>
    448                     <select name="tab_tags[]"
    449                             id="tab_tags"
    450                             class="components-select-control__input"
    451                             multiple>
    452                         <?php
    453                         $product_tags = get_terms(array(
    454                             'taxonomy' => 'product_tag',
    455                             'hide_empty' => false
    456                         ));
    457                         $selected_tags = explode(',', $tags);
    458                         foreach ($product_tags as $tag) {
    459                             printf(
    460                                 '<option value="%s" %s>%s</option>',
    461                                 esc_attr($tag->term_id),
    462                                 selected(in_array($tag->term_id, $selected_tags), true, false),
    463                                 esc_html($tag->name)
    464                             );
    465                         }
    466                         ?>
    467                     </select>
    468                 </div>
    469             </div>
    470         </div>
    471         <?php
    472     }
    473 
    474     public function save_meta_boxes($post_id) {
    475         if (!isset($_POST['iptfw_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['iptfw_meta_nonce'])), 'iptfw_save_meta')) {
    476             return;
    477         }
    478 
    479         if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
    480             return;
    481         }
    482 
    483         if (!current_user_can('edit_post', $post_id)) {
    484             return;
    485         }
    486 
    487         if (isset($_POST['tab_priority'])) {
    488             update_post_meta($post_id, '_iptfw_tab_priority', sanitize_text_field(wp_unslash($_POST['tab_priority'])));
    489         }
    490 
    491         if (isset($_POST['display_type'])) {
    492             update_post_meta($post_id, '_iptfw_display_type', sanitize_text_field(wp_unslash($_POST['display_type'])));
    493         }
    494 
    495         if (isset($_POST['tab_products'])) {
    496             update_post_meta($post_id, '_iptfw_products', sanitize_text_field(wp_unslash($_POST['tab_products'])));
    497         }
    498 
    499         if (isset($_POST['tab_categories'])) {
    500             $categories = isset($_POST['tab_categories']) ? array_map('absint', wp_unslash($_POST['tab_categories'])) : array();
    501             update_post_meta($post_id, '_iptfw_categories', implode(',', $categories));
    502         }
    503 
    504         if (isset($_POST['tab_tags'])) {
    505             $tags = isset($_POST['tab_tags']) ? array_map('absint', wp_unslash($_POST['tab_tags'])) : array();
    506             update_post_meta($post_id, '_iptfw_tags', implode(',', $tags));
    507         }
    508     }
    509 
    510     public function set_custom_columns($columns) {
    511         $new_columns = array();
    512         $new_columns['cb'] = $columns['cb'];
    513         $new_columns['title'] = esc_html__('Tab Title', 'product-tabs-for-woo');
    514         $new_columns['priority'] = esc_html__('Priority', 'product-tabs-for-woo');
    515         $new_columns['display_rules'] = esc_html__('Display Rules', 'product-tabs-for-woo');
    516         $new_columns['date'] = $columns['date'];
    517         return $new_columns;
    518     }
    519 
    520     public function custom_column_content($column, $post_id) {
    521         switch ($column) {
    522             case 'priority':
    523                 $priority = get_post_meta($post_id, '_iptfw_tab_priority', true);
    524                 echo esc_html($priority ?: '50');
    525                 break;
    526 
    527             case 'display_rules':
    528                 $display_type = get_post_meta($post_id, '_iptfw_display_type', true) ?: 'all';
    529                 switch ($display_type) {
    530                     case 'all':
    531                         esc_html_e('All Products', 'product-tabs-for-woo');
    532                         break;
    533                     case 'specific':
    534                         $products = get_post_meta($post_id, '_iptfw_products', true);
    535                         echo esc_html__('Specific Products', 'product-tabs-for-woo') . ': ' . esc_html($products);
    536                         break;
    537                     case 'categories':
    538                         $categories = get_post_meta($post_id, '_iptfw_categories', true);
    539                         $cat_names = array();
    540                         foreach (explode(',', $categories) as $cat_id) {
    541                             $term = get_term($cat_id, 'product_cat');
    542                             if ($term) {
    543                                 $cat_names[] = $term->name;
    544                             }
    545                         }
    546                         echo esc_html__('Categories', 'product-tabs-for-woo') . ': ' . esc_html(implode(', ', $cat_names));
    547                         break;
    548                     case 'tags':
    549                         $tags = get_post_meta($post_id, '_iptfw_tags', true);
    550                         $tag_names = array();
    551                         foreach (explode(',', $tags) as $tag_id) {
    552                             $term = get_term($tag_id, 'product_tag');
    553                             if ($term) {
    554                                 $tag_names[] = $term->name;
    555                             }
    556                         }
    557                         echo esc_html__('Tags', 'product-tabs-for-woo') . ': ' . esc_html(implode(', ', $tag_names));
    558                         break;
    559                 }
    560                 break;
    561         }
    562     }
    563 
    564     public function sortable_columns($columns) {
    565         $columns['priority'] = 'priority';
    566         return $columns;
    567     }
    568 
    569     public function add_product_tabs($tabs) {
    570         // Handle default tab visibility
    571         $options = get_option('iptfw_settings', array());
    572         $default_tabs = array('description', 'additional_information', 'reviews');
    573        
    574         foreach ($default_tabs as $tab_id) {
    575             $show_key = 'show_' . $tab_id;
    576             // Check if the setting exists and is explicitly set to 0/false
    577             if (isset($options[$show_key]) && !$options[$show_key]) {
    578                 unset($tabs[$tab_id]);
    579             }
    580         }
    581 
    582         // Add custom tabs - Using a more efficient query
    583         $args = array(
    584             'post_type' => 'iptfw_product_tab',
    585             'posts_per_page' => -1,
    586             'post_status' => 'publish',
    587             'orderby' => 'menu_order',
    588             'order' => 'ASC'
    589         );
    590 
    591         // Get all tabs first
    592         $custom_tabs = get_posts($args);
    593         $product_id = get_the_ID();
    594 
    595         // Cache priorities to avoid multiple meta queries
    596         $tab_priorities = array();
    597         foreach ($custom_tabs as $tab) {
    598             $tab_priorities[$tab->ID] = get_post_meta($tab->ID, '_iptfw_tab_priority', true);
    599         }
    600 
    601         // Sort tabs by priority
    602         usort($custom_tabs, function($a, $b) use ($tab_priorities) {
    603             $priority_a = empty($tab_priorities[$a->ID]) ? 50 : intval($tab_priorities[$a->ID]);
    604             $priority_b = empty($tab_priorities[$b->ID]) ? 50 : intval($tab_priorities[$b->ID]);
    605             return $priority_a - $priority_b;
    606         });
    607 
    608         foreach ($custom_tabs as $tab) {
    609             if ($this->should_display_tab($tab->ID, $product_id)) {
    610                 $priority = empty($tab_priorities[$tab->ID]) ? 50 : intval($tab_priorities[$tab->ID]);
    611 
    612                 $tabs['iptfw_tab_' . $tab->ID] = array(
    613                     'title' => $tab->post_title,
    614                     'priority' => $priority,
    615                     'callback' => array($this, 'render_tab_content'),
    616                     'tab_id' => $tab->ID
    617                 );
    618             }
    619         }
    620 
    621         return $tabs;
    622     }
    623 
    624     public function render_tab_content($key, $tab) {
    625         $content = get_post_field('post_content', $tab['tab_id']);
    626         echo wp_kses_post(apply_filters('the_content', $content));
    627     }
    628 
    629     private function should_display_tab($tab_id, $product_id) {
    630         $display_type = get_post_meta($tab_id, '_iptfw_display_type', true) ?: 'all';
    631 
    632         switch ($display_type) {
    633             case 'all':
    634                 return true;
    635 
    636             case 'specific':
    637                 $products = get_post_meta($tab_id, '_iptfw_products', true);
    638                 if (empty($products)) {
    639                     return false;
    640                 }
    641                 $product_ids = array_map('trim', explode(',', $products));
    642                 return in_array($product_id, $product_ids);
    643 
    644             case 'categories':
    645                 $tab_categories = get_post_meta($tab_id, '_iptfw_categories', true);
    646                 if (empty($tab_categories)) {
    647                     return false;
    648                 }
    649                 $tab_cat_ids = array_map('trim', explode(',', $tab_categories));
    650                 $product_cats = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
    651                 return !empty(array_intersect($tab_cat_ids, $product_cats));
    652 
    653             case 'tags':
    654                 $tab_tags = get_post_meta($tab_id, '_iptfw_tags', true);
    655                 if (empty($tab_tags)) {
    656                     return false;
    657                 }
    658                 $tab_tag_ids = array_map('trim', explode(',', $tab_tags));
    659                 $product_tags = wp_get_post_terms($product_id, 'product_tag', array('fields' => 'ids'));
    660                 return !empty(array_intersect($tab_tag_ids, $product_tags));
    661 
    662             default:
    663                 return false;
    664         }
    665     }
    666 
    667     /**
    668      * Add settings link to plugin action links
    669      */
    670     public function add_plugin_action_links($links) {
    671         $settings_link = sprintf(
    672             '<a href="%s">%s</a>',
    673             esc_url(admin_url('admin.php?page=iptfw-settings')),
    674             esc_html__('Settings', 'product-tabs-for-woo')
    675         );
    676        
    677         // Add settings link to the beginning of the array
    678         array_unshift($links, $settings_link);
    679        
    680         return $links;
    681     }
    682 }
    683 
     206            load_plugin_textdomain( 'product-tabs-for-woo', false, dirname( plugin_basename( IPTFW_FILE ) ) . '/languages' );
     207        }
     208
     209        /**
     210         * Add settings link to plugin action links
     211         */
     212        public function add_plugin_action_links( $links ) {
     213            $settings_link = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'edit.php?post_type=iptfw_product_tab&tab=settings' ) ), esc_html__( 'Settings', 'product-tabs-for-woo' ) );
     214            array_unshift( $links, $settings_link );
     215            return $links;
     216        }
     217
     218    }
     219
     220}
    684221// Initialize the plugin
    685222function IPTFW() {
     
    688225
    689226// Start the plugin
    690 add_action('plugins_loaded', 'IPTFW');
    691 }
     227add_action( 'plugins_loaded', 'IPTFW' );
  • product-tabs-for-woo/trunk/readme.txt

    r3294575 r3405807  
    1 === Product Tabs for Woo ===
    2 Contributors: ilmosys, stackwc, mahdiali
     1=== Product Tabs for WooCommerce ===
     2Contributors: stackwc, ilmosys, mahdiali
    33Tags: woocommerce, product tabs, custom tabs, product tabs for WooCommerce
    44Requires at least: 5.0
    5 Tested up to: 6.8
    6 Requires PHP: 7.2
    7 Stable tag: 1.0.1
     5Tested up to: 6.9
     6Requires PHP: 7.4
     7Stable tag: 1.0.2
    88License: GPLv3
    99License URI: https://www.gnu.org/licenses/gpl-3.0.html
    1010WC requires at least: 7.2
    11 WC tested up to: 9.8.5
     11WC tested up to: 10.3.5
    1212
    13 Add custom product tabs to your WooCommerce products with advanced display conditions and priority settings.
     13Add custom product tabs to your WooCommerce products with advanced display conditions, priority settings, and powerful customization options.
    1414
    1515== Description ==
    1616
    17 Custom Product Tabs for WooCommerce is a lightweight yet powerful plugin that lets you create, edit, and manage product tabs effortlessly. With a built-in block editor, you can design custom tabs or import ready-made patterns for a seamless experience. The plugin also includes a settings page where you can control tab priorities and show or hide WooCommerce default tabs like Description, Additional Information, and Reviews. Enhance your product pages with structured, engaging content and improve the shopping experience with flexible tab management!
     17<a href="https://stackwc.com/plugins/product-tabs-for-woo/?utm_source=wprepo&utm_medium=link&utm_campaign=plugin" title="StackWC - Product Tabs for WooCommerce Plugin"> Product Tabs for WooCommerce </a> is a powerful and flexible plugin that allows you to create unlimited custom tabs for your WooCommerce products. With an intuitive interface, advanced display conditions, and comprehensive customization options, you can enhance your product pages with rich, engaging content that improves the shopping experience.
    1818
    19 = Key Features =
     19The plugin features a modern block editor integration, allowing you to design beautiful tab content using WordPress blocks. You can also use the classic editor for quick content creation. With Pro features, you get access to advanced display conditions, tab reordering, icon customization, and much more.
    2020
    21 * Create unlimited custom tabs for your products
    22 * Set display conditions based on specific products, categories, or tags
    23 * Control tab priority and order
    24 * Rich text editor for tab content
    25 * SEO-friendly tab implementation
     21<p>⭐ <a href="https://stackwc.com/plugins/product-tabs-for-woo/?utm_source=wprepo&utm_medium=link&utm_campaign=upgrade-pro"> Upgrade to Pro </a> | 🚀 <a href="https://stackwc.com/plugins/product-tabs-for-woo/"> Try the Demo< /a> | 🛟 <a href="https://stackwc.com/support/"> Get Support </a> </p>
     22
     23= Key Features (Free Version) =
     24
     25* Create unlimited custom product tabs
     26* Block editor support for rich content creation
     27* Classic editor support for quick editing
     28* Display conditions: All products, Specific products, Product types
     29* Tab priority system for controlling display order
     30* Show/hide default WooCommerce tabs (Description, Additional Information, Reviews)
     31* SEO-friendly implementation
    2632* Responsive design compatible
    27 * Easy to use interface
     33* Easy-to-use interface
     34* Product search for selecting specific products
     35* Support for multiple product types (Simple, Variable, Grouped, External, Downloadable, Virtual)
     36
     37= Pro Features =
     38
     39* Advanced Display Conditions:
     40  * Product Categories - Show tabs based on product categories
     41  * Product Tags - Display tabs for products with specific tags
     42  * User Roles - Control tab visibility based on user roles (including guest users)
     43  * Price Range - Show tabs for products within specific price ranges
     44  * Stock Status - Display tabs based on product stock status (In Stock, Out of Stock, On Backorder)
     45  * Sale Status - Show tabs for products on sale or regular price
     46  * Date Range (Seasonal) - Display tabs only within specific date ranges
     47  * Shipping Classes - Show tabs based on product shipping classes
     48  * Combine multiple conditions for precise control
     49
     50* Tab Management:
     51  * Drag & Drop Reorder - Visually reorder tabs with drag and drop interface
     52  * Tab Icons - Add Dashicons or custom CSS classes to tab titles
     53  * Rename Default Tabs - Customize names of Description, Additional Information, and Reviews tabs
     54  * Icon Support for Default Tabs - Add icons to default WooCommerce tabs
    2855
    2956= Use Cases =
    3057
    31 * Add product specifications
    32 * Display shipping information
    33 * Show size guides
    34 * Include warranty information
    35 * Add custom product features
    36 * Display related products
    37 * Show product comparisons
    38 * Include video tutorials
    39 * Design and customize any content you want using the block editor
     58* Product Specifications - Add detailed technical specifications
     59* Shipping Information - Display shipping policies and delivery times
     60* Size Guides - Include size charts and measurement guides
     61* Warranty Information - Show warranty terms and conditions
     62* Custom Product Features - Highlight unique selling points
     63* Related Products - Display related or complementary products
     64* Product Comparisons - Show comparison tables
     65* Video Tutorials - Embed instructional videos
     66* Customer Reviews - Add custom review sections
     67* FAQs - Include frequently asked questions
     68* Installation Guides - Provide setup instructions
     69* Seasonal Promotions - Show time-limited offers
     70* Role-based Content - Display different content for different user types
     71* Price-based Tabs - Show special tabs for products in specific price ranges
     72
     73= Display Conditions Explained =
     74
     75The plugin offers flexible display conditions to control when tabs appear:
     76
     77* All Products - Tab appears on all product pages
     78* Specific Products - Select individual products by ID
     79* Product Type - Show tabs for specific product types (Simple, Variable, Grouped, External, Downloadable, Virtual)
     80* Product Categories (Pro) - Display tabs for products in selected categories
     81* Product Tags (Pro) - Show tabs for products with specific tags
     82* Advanced Conditions (Pro) - Combine multiple conditions:
     83  * User Roles - Show tabs to specific user roles or guests
     84  * Price Range - Display tabs for products within min/max price
     85  * Stock Status - Show tabs based on product availability
     86  * Sale Status - Display tabs for products on sale or regular price
     87  * Date Range - Show tabs only during specific date periods (seasonal content)
     88  * Shipping Classes - Display tabs based on product shipping class
     89
    4090
    4191== Installation ==
    42 1.  Upload the plugin files to the /wp-content/plugins/product-tabs-woocommerce directory or install the plugin directly from the WordPress Plugins screen.
    43 2.  Activate the plugin through the Plugins screen in WordPress.
    44 3.  Navigate to WooCommerce > Products > Product Tabs to access the page.
    45 4.  Click the “Add New Product Tab” button to create your first custom tab.
     92
     931. Upload the plugin files to the `/wp-content/plugins/product-tabs-for-woo` directory, or install the plugin through the WordPress plugins screen directly.
     942. Activate the plugin through the 'Plugins' screen in WordPress.
     953. Ensure WooCommerce is installed and activated (required).
     964. Navigate to WooCommerce > Products > Product Tabs to access the management page.
     975. Click "Add New Product Tab" to create your first custom tab.
     986. Configure display conditions and priority settings.
     997. Add your content using the block editor or classic editor.
     1008. Visit a product page to see your custom tabs in action.
    46101
    47102== Frequently Asked Questions ==
    48103
    49 = Does this plugin require WooCommerce? =
    50 Yes, this plugin requires WooCommerce to be installed and activated on your WordPress site.
    51 
    52104= Can I create multiple product tabs for different products? =
    53 Yes, you can create unlimited custom tabs and set specific display conditions for each tab based on products, categories, or tags.
     105Yes, you can create unlimited custom tabs and set specific display conditions for each tab. You can show tabs on all products, specific products, or based on categories, tags, and other advanced conditions (Pro).
    54106
    55107= Can I control the order of tabs? =
    56 Yes, you can set a priority number for each tab. Lower numbers appear first in the tab order.
     108Yes, you can set a priority number for each tab. Lower numbers appear first in the tab order. Pro users can also use the drag-and-drop reorder interface for visual tab management.
    57109
    58110= Can I use HTML in tab content? =
    59 Yes, the tab content editor supports HTML and rich text formatting.
     111Yes, the tab content editor supports HTML, rich text formatting, and WordPress blocks. You can use the block editor to create rich, structured content.
    60112
    61113= Is this plugin compatible with my theme? =
    62 This plugin is compatible with most WooCommerce themes. It uses standard WooCommerce hooks and filters for tab integration.
     114This plugin is compatible with most WooCommerce themes. It uses standard WooCommerce hooks and filters for tab integration, ensuring compatibility with the WooCommerce ecosystem.
    63115
    64116= Does this plugin affect my site's performance? =
    65 The plugin is optimized for performance and only loads tab content when needed. It uses WordPress caching mechanisms and efficient database queries.
     117The plugin is optimized for performance and only loads tab content when needed. It uses efficient database queries, WordPress caching mechanisms, and lazy loading where appropriate.
     118
     119= Can I hide default WooCommerce tabs? =
     120Yes, you can show or hide the default WooCommerce tabs (Description, Additional Information, Reviews) from the Settings page.
     121
     122= Can I rename default WooCommerce tabs? =
     123Yes, Pro users can rename the default WooCommerce tabs (Description, Additional Information, Reviews) and add icons to them.
     124
     125= Can I show different tabs to different user roles? =
     126Yes, Pro users can set user role-based display conditions, allowing different tabs to be shown to different user types, including guest users.
     127
     128= Can I create seasonal or time-limited tabs? =
     129Yes, Pro users can set date range conditions to display tabs only within specific date periods, perfect for seasonal promotions or limited-time offers.
     130
     131= Does the plugin work with variable products? =
     132Yes, the plugin works with all WooCommerce product types including Simple, Variable, Grouped, External, Downloadable, and Virtual products.
     133
     134= Can I use custom icons for tabs? =
     135Yes, Pro users can add Dashicons or custom CSS classes to tab titles for enhanced visual presentation.
    66136
    67137== Screenshots ==
    68138
    69 1. Product Tabs Management Screen
    70 2. Tab Settings Interface
    71 3. Display Conditions Settings
    72 4. Product Page with Custom Tabs
     1391. Product Tabs Management Screen - Overview of all custom tabs
     1402. Tab Settings Interface - Configure display conditions and priority
     1413. Display Conditions Settings - Advanced condition options (Pro)
     1424. Block Editor Integration - Rich content creation with blocks
     1435. Tab Reorder Interface - Drag and drop tab ordering (Pro)
     1446. Product Page with Custom Tabs - Tabs displayed on product page
     1457. Settings Page - Control default tabs and global settings
     1468. Advanced Conditions - Multiple condition types (Pro)
    73147
    74148== Changelog ==
    75149
     150= 1.0.2 - 29 Nov, 2025 =
     151* Updated: Tested up to WordPress 6.9
     152* Updated: WooCommerce compatibility tested up to 10.3.5
     153* Updated: Minimum PHP requirement to 7.4
     154* Added: Freemius SDK integration
     155* Added: Advanced tab conditions system with custom rules
     156* Added: New admin interface with improved UX
     157* Improved: Codebase restructured for better maintainability
     158* Improved: Security enhancements and capability checks
     159* Improved: Performance optimizations and caching
     160* Fixed: Various UI/UX issues
     161* Removed: Outdated code and deprecated functions
     162
    76163= 1.0.1 - 16 May, 2025 =
    77 * Updated: Tested up to WordPress 6.8.
     164* Updated: Tested up to WordPress 6.8
     165* Improved: WooCommerce compatibility declarations
    78166
    79167= 1.0.0 =
    80168* Initial release
     169* Basic tab creation and management
     170* Display conditions (All, Specific Products, Product Types)
     171* Priority system
     172* Block editor support
    81173
    82174== Upgrade Notice ==
    83 
    84 == Privacy Policy ==
    85 This plugin does not collect or store any personal data. It only uses WordPress and WooCommerce core functionality to display custom tabs on product pages.
    86 
    87 == Disclaimer == 
    88 This plugin is not affiliated with or endorsed by WooCommerce or Automattic. "WooCommerce" is a trademark of Automattic Inc. This plugin is an independent product developed to extend WooCommerce functionality.
Note: See TracChangeset for help on using the changeset viewer.