Plugin Directory

Changeset 3469558


Ignore:
Timestamp:
02/25/2026 02:54:12 PM (4 weeks ago)
Author:
malakontask
Message:

v3.0.9 - Fix brand transfer on Redis/Memcached sites with direct DB queries

Location:
transfer-brands-for-woocommerce
Files:
8 edited
1 copied

Legend:

Unmodified
Added
Removed
  • transfer-brands-for-woocommerce/tags/3.0.9/CHANGELOG.md

    r3456547 r3469558  
    55The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
    66and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
     7
     8## [3.0.9] - 2026-02-25
     9
     10### Fixed
     11- **CRITICAL**: Brand transfer now works correctly on sites with Redis/Memcached persistent object cache
     12- Replaced `get_terms()` and `wp_count_terms()` with direct `$wpdb` queries in `process_terms_batch()` to bypass the `WP_Term_Query` cache layer (`term-queries` group) that `clean_taxonomy_cache()` does not invalidate
     13- Added `wp_cache_delete('last_changed', 'terms')` before `term_exists()` and `wp_insert_term()` to prevent stale destination taxonomy lookups
     14- Potential division by zero in progress percentage calculation when source term count is zero
     15
     16### Improved
     17- Consistent use of local `$destination_taxonomy` variable instead of repeated `$this->core->get_option()` calls in term creation
    718
    819## [3.0.8] - 2026-02-08
  • transfer-brands-for-woocommerce/tags/3.0.9/includes/class-transfer.php

    r3456547 r3469558  
    3232     */
    3333    public function process_terms_batch($offset = 0) {
     34        global $wpdb;
     35
    3436        $source_taxonomy = $this->core->get_option('source_taxonomy');
    3537        $destination_taxonomy = $this->core->get_option('destination_taxonomy');
     
    5658        }
    5759
    58         // Get total count first (cached after first call)
    59         $total = wp_count_terms([
    60             'taxonomy' => $source_taxonomy,
    61             'hide_empty' => false
    62         ]);
    63 
    64         if (is_wp_error($total)) {
    65             $this->core->add_debug("Error counting terms", [
    66                 'error' => $total->get_error_message()
    67             ]);
    68             return [
    69                 'success' => false,
    70                 'message' => 'Error counting terms: ' . $total->get_error_message()
    71             ];
    72         }
    73 
    74         $total = (int) $total;
    75 
    76         // Clear term cache to prevent stale results between AJAX batch calls
    77         // This is critical for sites using persistent object cache (Redis, Memcached)
    78         clean_taxonomy_cache($source_taxonomy);
    79 
    80         // Get only ONE term at the current offset (memory efficient)
    81         $terms = get_terms([
    82             'taxonomy' => $source_taxonomy,
    83             'hide_empty' => false,
    84             'number' => 1,
    85             'offset' => $offset,
    86             'orderby' => 'term_id',
    87             'order' => 'ASC'
    88         ]);
    89 
    90         if (is_wp_error($terms)) {
    91             $this->core->add_debug("Error getting terms", [
    92                 'error' => $terms->get_error_message()
    93             ]);
    94             return [
    95                 'success' => false,
    96                 'message' => 'Error getting terms: ' . $terms->get_error_message()
    97             ];
    98         }
    99 
    100         if (!empty($terms)) {
    101             $term = $terms[0];
     60        // Direct DB query for total count — bypasses persistent object cache (Redis, Memcached)
     61        // This prevents stale counts that could affect progress tracking
     62        $total = (int) $wpdb->get_var($wpdb->prepare(
     63            "SELECT COUNT(*) FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s",
     64            $source_taxonomy
     65        ));
     66
     67        // Direct DB query for term fetch — bypasses persistent object cache entirely
     68        // This is the critical fix: get_terms() uses WP_Term_Query which caches results
     69        // in the 'term-queries' group. clean_taxonomy_cache() does NOT invalidate this
     70        // cache group, causing stale/empty results on Redis/Memcached sites after the
     71        // first batch processes and wp_insert_term() modifies cache timestamps.
     72        $term = $wpdb->get_row($wpdb->prepare(
     73            "SELECT t.term_id, t.name, t.slug, tt.description
     74            FROM {$wpdb->terms} AS t
     75            INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
     76            WHERE tt.taxonomy = %s
     77            ORDER BY t.term_id ASC
     78            LIMIT 1 OFFSET %d",
     79            $source_taxonomy,
     80            $offset
     81        ));
     82
     83        if ($term) {
    10284            $log_message = '';
    10385
     
    11294
    11395                $offset++;
    114                 $percent = min(45, round(($offset / $total) * 40) + 5);
     96                $percent = ($total > 0) ? min(45, round(($offset / $total) * 40) + 5) : 45;
    11597
    11698                return [
     
    125107            }
    126108
     109            // Invalidate term query cache before destination taxonomy operations
     110            // This ensures term_exists() and wp_insert_term() get fresh results
     111            // and prevents stale lookups on persistent object cache sites
     112            wp_cache_delete('last_changed', 'terms');
     113            clean_taxonomy_cache($destination_taxonomy);
     114
    127115            // Create or get new term - PRESERVE ORIGINAL SLUG for SEO
    128             $new = term_exists($term->name, $this->core->get_option('destination_taxonomy'));
     116            $new = term_exists($term->name, $destination_taxonomy);
    129117            if (!$new) {
    130                 $new = wp_insert_term($term->name, $this->core->get_option('destination_taxonomy'), [
     118                $new = wp_insert_term($term->name, $destination_taxonomy, [
    131119                    'slug' => $term->slug, // Preserve original slug for SEO/URL preservation
    132120                    'description' => $term->description
     
    147135
    148136                $offset++;
    149                 $percent = min(45, round(($offset / $total) * 40) + 5);
     137                $percent = ($total > 0) ? min(45, round(($offset / $total) * 40) + 5) : 45;
    150138
    151139                return [
     
    182170
    183171            $offset++;
    184             $percent = min(45, round(($offset / $total) * 40) + 5);
     172            $percent = ($total > 0) ? min(45, round(($offset / $total) * 40) + 5) : 45;
    185173
    186174            return [
  • transfer-brands-for-woocommerce/tags/3.0.9/readme.txt

    r3456547 r3469558  
    44Requires at least: 6.0
    55Tested up to: 6.9
    6 Stable tag: 3.0.8
     6Stable tag: 3.0.9
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    130130
    131131== Changelog ==
     132
     133= 3.0.9 =
     134* **Fixed**: Critical - Brand transfer now works correctly on sites with Redis/Memcached persistent object cache
     135* **Fixed**: Replaced `get_terms()` and `wp_count_terms()` with direct database queries in term batch processing to bypass `WP_Term_Query` cache layer that `clean_taxonomy_cache()` does not invalidate
     136* **Fixed**: Added `wp_cache_delete('last_changed', 'terms')` before destination taxonomy operations to prevent stale `term_exists()` lookups
     137* **Fixed**: Potential division by zero in progress percentage when term count is zero
     138* **Improved**: Consistent use of `$destination_taxonomy` variable in `wp_insert_term()` call
    132139
    133140= 3.0.8 =
     
    350357== Upgrade Notice ==
    351358
     359= 3.0.9 =
     360**Critical fix for Redis/Memcached sites**: Resolves the persistent issue where only the first brand transfers on sites using persistent object cache. The v3.0.8 fix (`clean_taxonomy_cache()`) was insufficient — it does not invalidate the `WP_Term_Query` cache group. This update bypasses the WordPress cache layer entirely with direct database queries. No need to disable Redis. Strongly recommended for all users.
     361
    352362= 3.0.8 =
    353363**Critical bugfix**: Fixes issue where only the first brand transfers from PWB (Perfect WooCommerce Brands). Term creation errors no longer halt the entire transfer. Added cache clearing for persistent object cache compatibility. Strongly recommended for all users migrating from PWB.
  • transfer-brands-for-woocommerce/tags/3.0.9/transfer-brands-for-woocommerce.php

    r3456547 r3469558  
    44 * Plugin URI: https://pluginatlas.com/transfer-brands-for-woocommerce
    55 * Description: Official WooCommerce 9.6 brand migration tool. Transfer from Perfect Brands, YITH, or custom attributes with backup and image support.
    6  * Version: 3.0.8
     6 * Version: 3.0.9
    77 * Requires at least: 6.0
    88 * Requires PHP: 7.4
     
    3636
    3737// Define plugin constants
    38 define('TBFW_VERSION', '3.0.8');
     38define('TBFW_VERSION', '3.0.9');
    3939define('TBFW_PLUGIN_DIR', plugin_dir_path(__FILE__));
    4040define('TBFW_PLUGIN_URL', plugin_dir_url(__FILE__));
  • transfer-brands-for-woocommerce/trunk/CHANGELOG.md

    r3456547 r3469558  
    55The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
    66and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
     7
     8## [3.0.9] - 2026-02-25
     9
     10### Fixed
     11- **CRITICAL**: Brand transfer now works correctly on sites with Redis/Memcached persistent object cache
     12- Replaced `get_terms()` and `wp_count_terms()` with direct `$wpdb` queries in `process_terms_batch()` to bypass the `WP_Term_Query` cache layer (`term-queries` group) that `clean_taxonomy_cache()` does not invalidate
     13- Added `wp_cache_delete('last_changed', 'terms')` before `term_exists()` and `wp_insert_term()` to prevent stale destination taxonomy lookups
     14- Potential division by zero in progress percentage calculation when source term count is zero
     15
     16### Improved
     17- Consistent use of local `$destination_taxonomy` variable instead of repeated `$this->core->get_option()` calls in term creation
    718
    819## [3.0.8] - 2026-02-08
  • transfer-brands-for-woocommerce/trunk/includes/class-transfer.php

    r3456547 r3469558  
    3232     */
    3333    public function process_terms_batch($offset = 0) {
     34        global $wpdb;
     35
    3436        $source_taxonomy = $this->core->get_option('source_taxonomy');
    3537        $destination_taxonomy = $this->core->get_option('destination_taxonomy');
     
    5658        }
    5759
    58         // Get total count first (cached after first call)
    59         $total = wp_count_terms([
    60             'taxonomy' => $source_taxonomy,
    61             'hide_empty' => false
    62         ]);
    63 
    64         if (is_wp_error($total)) {
    65             $this->core->add_debug("Error counting terms", [
    66                 'error' => $total->get_error_message()
    67             ]);
    68             return [
    69                 'success' => false,
    70                 'message' => 'Error counting terms: ' . $total->get_error_message()
    71             ];
    72         }
    73 
    74         $total = (int) $total;
    75 
    76         // Clear term cache to prevent stale results between AJAX batch calls
    77         // This is critical for sites using persistent object cache (Redis, Memcached)
    78         clean_taxonomy_cache($source_taxonomy);
    79 
    80         // Get only ONE term at the current offset (memory efficient)
    81         $terms = get_terms([
    82             'taxonomy' => $source_taxonomy,
    83             'hide_empty' => false,
    84             'number' => 1,
    85             'offset' => $offset,
    86             'orderby' => 'term_id',
    87             'order' => 'ASC'
    88         ]);
    89 
    90         if (is_wp_error($terms)) {
    91             $this->core->add_debug("Error getting terms", [
    92                 'error' => $terms->get_error_message()
    93             ]);
    94             return [
    95                 'success' => false,
    96                 'message' => 'Error getting terms: ' . $terms->get_error_message()
    97             ];
    98         }
    99 
    100         if (!empty($terms)) {
    101             $term = $terms[0];
     60        // Direct DB query for total count — bypasses persistent object cache (Redis, Memcached)
     61        // This prevents stale counts that could affect progress tracking
     62        $total = (int) $wpdb->get_var($wpdb->prepare(
     63            "SELECT COUNT(*) FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s",
     64            $source_taxonomy
     65        ));
     66
     67        // Direct DB query for term fetch — bypasses persistent object cache entirely
     68        // This is the critical fix: get_terms() uses WP_Term_Query which caches results
     69        // in the 'term-queries' group. clean_taxonomy_cache() does NOT invalidate this
     70        // cache group, causing stale/empty results on Redis/Memcached sites after the
     71        // first batch processes and wp_insert_term() modifies cache timestamps.
     72        $term = $wpdb->get_row($wpdb->prepare(
     73            "SELECT t.term_id, t.name, t.slug, tt.description
     74            FROM {$wpdb->terms} AS t
     75            INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
     76            WHERE tt.taxonomy = %s
     77            ORDER BY t.term_id ASC
     78            LIMIT 1 OFFSET %d",
     79            $source_taxonomy,
     80            $offset
     81        ));
     82
     83        if ($term) {
    10284            $log_message = '';
    10385
     
    11294
    11395                $offset++;
    114                 $percent = min(45, round(($offset / $total) * 40) + 5);
     96                $percent = ($total > 0) ? min(45, round(($offset / $total) * 40) + 5) : 45;
    11597
    11698                return [
     
    125107            }
    126108
     109            // Invalidate term query cache before destination taxonomy operations
     110            // This ensures term_exists() and wp_insert_term() get fresh results
     111            // and prevents stale lookups on persistent object cache sites
     112            wp_cache_delete('last_changed', 'terms');
     113            clean_taxonomy_cache($destination_taxonomy);
     114
    127115            // Create or get new term - PRESERVE ORIGINAL SLUG for SEO
    128             $new = term_exists($term->name, $this->core->get_option('destination_taxonomy'));
     116            $new = term_exists($term->name, $destination_taxonomy);
    129117            if (!$new) {
    130                 $new = wp_insert_term($term->name, $this->core->get_option('destination_taxonomy'), [
     118                $new = wp_insert_term($term->name, $destination_taxonomy, [
    131119                    'slug' => $term->slug, // Preserve original slug for SEO/URL preservation
    132120                    'description' => $term->description
     
    147135
    148136                $offset++;
    149                 $percent = min(45, round(($offset / $total) * 40) + 5);
     137                $percent = ($total > 0) ? min(45, round(($offset / $total) * 40) + 5) : 45;
    150138
    151139                return [
     
    182170
    183171            $offset++;
    184             $percent = min(45, round(($offset / $total) * 40) + 5);
     172            $percent = ($total > 0) ? min(45, round(($offset / $total) * 40) + 5) : 45;
    185173
    186174            return [
  • transfer-brands-for-woocommerce/trunk/readme.txt

    r3456547 r3469558  
    44Requires at least: 6.0
    55Tested up to: 6.9
    6 Stable tag: 3.0.8
     6Stable tag: 3.0.9
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    130130
    131131== Changelog ==
     132
     133= 3.0.9 =
     134* **Fixed**: Critical - Brand transfer now works correctly on sites with Redis/Memcached persistent object cache
     135* **Fixed**: Replaced `get_terms()` and `wp_count_terms()` with direct database queries in term batch processing to bypass `WP_Term_Query` cache layer that `clean_taxonomy_cache()` does not invalidate
     136* **Fixed**: Added `wp_cache_delete('last_changed', 'terms')` before destination taxonomy operations to prevent stale `term_exists()` lookups
     137* **Fixed**: Potential division by zero in progress percentage when term count is zero
     138* **Improved**: Consistent use of `$destination_taxonomy` variable in `wp_insert_term()` call
    132139
    133140= 3.0.8 =
     
    350357== Upgrade Notice ==
    351358
     359= 3.0.9 =
     360**Critical fix for Redis/Memcached sites**: Resolves the persistent issue where only the first brand transfers on sites using persistent object cache. The v3.0.8 fix (`clean_taxonomy_cache()`) was insufficient — it does not invalidate the `WP_Term_Query` cache group. This update bypasses the WordPress cache layer entirely with direct database queries. No need to disable Redis. Strongly recommended for all users.
     361
    352362= 3.0.8 =
    353363**Critical bugfix**: Fixes issue where only the first brand transfers from PWB (Perfect WooCommerce Brands). Term creation errors no longer halt the entire transfer. Added cache clearing for persistent object cache compatibility. Strongly recommended for all users migrating from PWB.
  • transfer-brands-for-woocommerce/trunk/transfer-brands-for-woocommerce.php

    r3456547 r3469558  
    44 * Plugin URI: https://pluginatlas.com/transfer-brands-for-woocommerce
    55 * Description: Official WooCommerce 9.6 brand migration tool. Transfer from Perfect Brands, YITH, or custom attributes with backup and image support.
    6  * Version: 3.0.8
     6 * Version: 3.0.9
    77 * Requires at least: 6.0
    88 * Requires PHP: 7.4
     
    3636
    3737// Define plugin constants
    38 define('TBFW_VERSION', '3.0.8');
     38define('TBFW_VERSION', '3.0.9');
    3939define('TBFW_PLUGIN_DIR', plugin_dir_path(__FILE__));
    4040define('TBFW_PLUGIN_URL', plugin_dir_url(__FILE__));
Note: See TracChangeset for help on using the changeset viewer.