Skip to content

Infinite Loader WooCommerce#2

Description

@vapvarun

馃敶 HIGH PRIORITY TASKS (Function Logic Improvements)

LOGIC-001: Fix Unsafe Option Access Pattern

File: admin/class-infinite-loader-for-woocommerce-admin.php

Current Issue: Direct array access without validation

// Line 388-389 - UNSAFE
$infinite_loader_lm_custom_class = isset( $infinite_loader_button_setting['custom_class'] ) ? $infinite_loader_button_setting['custom_class'] : '';
$infinite_loader_lm_button_text  = isset( $infinite_loader_button_setting['button_text'] ) ? $infinite_loader_button_setting['button_text'] : '';

Replace with Safe Helper Method:

/**
 * Safely get option value with default fallback
 *
 * @param array  $options Option array
 * @param string $key     Option key
 * @param mixed  $default Default value
 * @return mixed Option value or default
 */
private function infinite_loader_get_option_value( $options, $key, $default = '' ) {
    if ( ! is_array( $options ) ) {
        return $default;
    }
    return isset( $options[ $key ] ) && ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
}

// Usage throughout the class:
$infinite_loader_lm_custom_class = $this->infinite_loader_get_option_value( $infinite_loader_button_setting, 'custom_class' );
$infinite_loader_lm_button_text  = $this->infinite_loader_get_option_value( $infinite_loader_button_setting, 'button_text', 'Load More' );

LOGIC-002: Fix Empty Settings Check Logic

File: admin/class-infinite-loader-for-woocommerce-admin.php

Lines 407-409 - Current Logic Issue:

public function infinite_loader_for_woocommerce_button_style() {
    $infinite_loader_load_more_button_style = '';
    $infinite_loader_button_setting         = get_option( 'infinite_loader_admin_button_option', array() );

    if( empty( $infinite_loader_button_setting ) ) {
        return;
    }
    // Missing return statement causes undefined behavior

Fix Return Logic:

public function infinite_loader_for_woocommerce_button_style() {
    $infinite_loader_load_more_button_style = '';
    $infinite_loader_button_setting         = get_option( 'infinite_loader_admin_button_option', array() );

    if ( empty( $infinite_loader_button_setting ) ) {
        return ''; // Return empty string instead of void
    }
    
    // Validate required fields exist
    $required_fields = array( 'text_font_size', 'text_color', 'background_color' );
    foreach ( $required_fields as $field ) {
        if ( ! isset( $infinite_loader_button_setting[ $field ] ) ) {
            error_log( "Infinite Loader: Missing required button setting: {$field}" );
            return '';
        }
    }
    
    // Continue with existing logic...
}

LOGIC-003: Fix Inconsistent Default Value Handling

File: admin/class-infinite-loader-for-woocommerce-admin.php

Lines 410-420 - Inconsistent defaults:

// Current inconsistent logic
$infinite_loader_lm_button_margin_top    = ( isset( $infinite_loader_button_setting['margin_top'] ) && ! empty( $infinite_loader_button_setting['margin_top'] ) ) ? $infinite_loader_button_setting['margin_top'] : '0';
$infinite_loader_lm_button_margin_right  = ( isset( $infinite_loader_button_setting['margin_right'] ) && ! empty( $infinite_loader_button_setting['margin_right'] ) ) ? $infinite_loader_button_setting['margin_right'] : '0';

Replace with Consistent Helper:

/**
 * Get button dimension value with validation
 *
 * @param array  $settings Button settings
 * @param string $key      Setting key
 * @param string $default  Default value
 * @return string Validated dimension value
 */
private function infinite_loader_get_dimension_value( $settings, $key, $default = '0' ) {
    $value = $this->infinite_loader_get_option_value( $settings, $key, $default );
    
    // Ensure numeric value
    $numeric_value = absint( $value );
    
    // Limit reasonable range (0-999px)
    return (string) max( 0, min( 999, $numeric_value ) );
}

// Replace all margin/padding/border assignments:
$infinite_loader_lm_button_margin_top    = $this->infinite_loader_get_dimension_value( $infinite_loader_button_setting, 'margin_top' );
$infinite_loader_lm_button_margin_right  = $this->infinite_loader_get_dimension_value( $infinite_loader_button_setting, 'margin_right' );
$infinite_loader_lm_button_margin_bottom = $this->infinite_loader_get_dimension_value( $infinite_loader_button_setting, 'margin_bottom' );
$infinite_loader_lm_button_margin_left   = $this->infinite_loader_get_dimension_value( $infinite_loader_button_setting, 'margin_left' );

LOGIC-004: Fix Style Generation Logic Issues

File: admin/class-infinite-loader-for-woocommerce-admin.php

Lines 424-445 - Refactor style building:

/**
 * Build CSS style string for button
 *
 * @param array $infinite_loader_button_setting Button settings
 * @return string Generated CSS styles
 */
private function infinite_loader_build_button_styles( $infinite_loader_button_setting ) {
    $styles = array();
    
    // Text styles
    $styles[] = sprintf( 
        'font-size: %spx;', 
        $this->infinite_loader_get_dimension_value( $infinite_loader_button_setting, 'text_font_size', '16' ) 
    );
    
    // Color styles with validation
    $text_color = $this->infinite_loader_get_option_value( $infinite_loader_button_setting, 'text_color', '#ffffff' );
    $bg_color   = $this->infinite_loader_get_option_value( $infinite_loader_button_setting, 'background_color', '#1d76da' );
    
    if ( $this->infinite_loader_is_valid_color( $text_color ) ) {
        $styles[] = sprintf( 'color: %s;', sanitize_hex_color( $text_color ) );
    }
    
    if ( $this->infinite_loader_is_valid_color( $bg_color ) ) {
        $styles[] = sprintf( 'background-color: %s;', sanitize_hex_color( $bg_color ) );
    }
    
    // Spacing styles
    $styles[] = $this->infinite_loader_build_spacing_styles( $infinite_loader_button_setting );
    
    // Border styles
    $styles[] = $this->infinite_loader_build_border_styles( $infinite_loader_button_setting );
    
    return implode( ' ', array_filter( $styles ) );
}

/**
 * Validate color format
 *
 * @param string $color Color value
 * @return bool True if valid color
 */
private function infinite_loader_is_valid_color( $color ) {
    return ! empty( $color ) && ( 
        sanitize_hex_color( $color ) || 
        preg_match( '/^rgb\(/', $color ) || 
        preg_match( '/^rgba\(/', $color )
    );
}

/**
 * Build spacing styles (padding/margin)
 *
 * @param array $settings Button settings
 * @return string CSS spacing styles
 */
private function infinite_loader_build_spacing_styles( $settings ) {
    $spacing_styles = array();
    $properties = array( 'padding', 'margin' );
    $directions = array( 'top', 'right', 'bottom', 'left' );
    
    foreach ( $properties as $property ) {
        foreach ( $directions as $direction ) {
            $key = $property . '_' . $direction;
            $value = $this->infinite_loader_get_dimension_value( $settings, $key );
            $spacing_styles[] = sprintf( '%s-%s: %spx;', $property, $direction, $value );
        }
    }
    
    return implode( ' ', $spacing_styles );
}

LOGIC-005: Fix JavaScript DOM Performance Issues

File: public/js/infinite_loader_products.js

Lines 15-30 - Fix DOM cache initialization:

// Replace existing domCache with performance improvements
var infinite_loader_dom_cache = {
    products: null,
    pagination: null,
    result_count: null,
    window: null,
    document: null,
    body: null,
    cache_timestamp: 0,
    cache_duration: 5000, // 5 seconds cache
    
    infinite_loader_init_cache: function() {
        const now = Date.now();
        if (now - this.cache_timestamp < this.cache_duration && this.products) {
            return; // Cache still valid
        }
        
        this.products = $(infinite_loader_product_data.products);
        this.pagination = $(infinite_loader_product_data.pagination);
        this.result_count = $('.woocommerce-result-count');
        this.window = $(window);
        this.document = $(document);
        this.body = $('body');
        this.cache_timestamp = now;
    },
    
    infinite_loader_get_products: function() {
        this.infinite_loader_init_cache();
        return this.products;
    },
    
    infinite_loader_get_pagination: function() {
        this.infinite_loader_init_cache();
        return this.pagination;
    },
    
    infinite_loader_refresh_cache: function() {
        this.cache_timestamp = 0;
        this.infinite_loader_init_cache();
    }
};

// Replace all domCache references with infinite_loader_dom_cache

LOGIC-006: Fix AJAX Error Handling Logic

File: public/js/infinite_loader_products.js

Lines 150+ - Improve error handling:

// Replace existing AJAX call
infinite_loader_ajax_instance = $.ajax({
    method: "GET", 
    url: next_page, 
    timeout: 30000, // Add timeout
    beforeSend: function (xhr) {
        xhr.setRequestHeader('X-Braapfdisable', '1');
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    }, 
    success: function (data) {
        infinite_loader_process_ajax_response(data, next_page, replace);
    },
    error: function(xhr, status, error) {
        infinite_loader_handle_ajax_error(xhr, status, error, next_page, replace);
    },
    complete: function() {
        infinite_loader_ajax_instance = false;
    }
});

/**
 * Handle AJAX errors with retry mechanism
 */
function infinite_loader_handle_ajax_error(xhr, status, error, next_page, replace) {
    console.error('Infinite Loader AJAX Error:', {
        status: xhr.status,
        statusText: xhr.statusText,
        error: error,
        url: next_page
    });
    
    // Determine error type
    let error_message = infinite_loader_product_data.error_message || 'Unable to load more products.';
    let retry_available = true;
    
    if (xhr.status === 404) {
        error_message = infinite_loader_product_data.not_found_message || 'No more products available.';
        retry_available = false;
    } else if (xhr.status === 0 && status === 'timeout') {
        error_message = infinite_loader_product_data.timeout_message || 'Request timed out. Please check your connection.';
    }
    
    // Show error UI
    infinite_loader_show_error_message(error_message, retry_available, next_page, replace);
    
    // Clean up loading state
    infinite_loader_end_ajax_loading();
}

/**
 * Show user-friendly error message
 */
function infinite_loader_show_error_message(message, show_retry, next_page, replace) {
    let error_html = '<div class="infinite-loader-error" role="alert">' +
        '<p>' + message + '</p>';
    
    if (show_retry) {
        error_html += '<button class="infinite-loader-retry button" data-url="' + next_page + '" data-replace="' + replace + '">' +
            (infinite_loader_product_data.retry_text || 'Retry') + '</button>';
    }
    
    error_html += '</div>';
    
    infinite_loader_dom_cache.infinite_loader_get_products().after(error_html);
    
    // Auto-remove error after 10 seconds
    setTimeout(function() {
        $('.infinite-loader-error').fadeOut(500, function() {
            $(this).remove();
        });
    }, 10000);
}

// Add retry click handler
$(document).on('click', '.infinite-loader-retry', function() {
    const $button = $(this);
    const url = $button.data('url');
    const replace = $button.data('replace');
    
    $('.infinite-loader-error').remove();
    infinite_loader_load_next_page(replace, url);
});

LOGIC-007: Fix Pagination Logic Issues

File: public/js/infinite_loader_products.js

Lines 200+ - Fix pagination replacement logic:

/**
 * Replace pagination with improved error handling
 */
function infinite_loader_pagination_replace_partial($data, replace) {
    try {
        const $pagination = infinite_loader_dom_cache.infinite_loader_get_pagination();
        const $new_pagination = $data.find(infinite_loader_product_data.pagination);
        
        if (!$pagination.length) {
            console.warn('Infinite Loader: Original pagination not found');
            return false;
        }
        
        if (!$new_pagination.length) {
            console.warn('Infinite Loader: New pagination not found in response');
            return false;
        }
        
        const $prev_page = infinite_loader_jquery_get_prev_page();
        const $new_prev_page = infinite_loader_jquery_get_prev_page($data);
        const $next_page = infinite_loader_jquery_get_next_page();
        const $new_next_page = infinite_loader_jquery_get_next_page($data);
        
        let pagination_updated = false;
        
        // Handle complex pagination structures
        if (($pagination.find(infinite_loader_product_data.next_page).length || 
             $pagination.find(infinite_loader_product_data.prev_page).length) && 
             $pagination.find('.current').length) {
            
            pagination_updated = infinite_loader_handle_complex_pagination($pagination, $new_pagination, replace);
        }
        
        // Fallback to simple replacement
        if (!pagination_updated) {
            infinite_loader_handle_simple_pagination($pagination, $new_pagination, $prev_page, $new_prev_page, $next_page, $new_next_page, replace);
        }
        
        return true;
        
    } catch (error) {
        console.error('Infinite Loader: Pagination replacement failed:', error);
        return false;
    }
}

/**
 * Handle complex pagination with current page indicators
 */
function infinite_loader_handle_complex_pagination($pagination, $new_pagination, replace) {
    try {
        const $current = replace == 2 ? $pagination.find('.current').last() : $pagination.find('.current').first();
        const $new_current = replace == 2 ? $new_pagination.find('.current').last() : $new_pagination.find('.current').first();
        
        if (!$current.length || !$new_current.length) {
            return false;
        }
        
        const $current_block = infinite_loader_get_current_page_last_block($current);
        const $new_current_block = infinite_loader_get_current_page_last_block($new_current);
        
        if ($current_block && $new_current_block) {
            if (replace == 2) {
                $current_block.prevAll().remove();
                $current_block.before($new_current_block.prevAll().clone());
                $current_block.before($new_current_block.clone());
            } else {
                $current_block.nextAll().remove();
                $current_block.after($new_current_block.clone());
                $current_block.after($new_current_block.nextAll().clone());
            }
            return true;
        }
        
        return false;
        
    } catch (error) {
        console.error('Infinite Loader: Complex pagination handling failed:', error);
        return false;
    }
}

LOGIC-008: Fix Settings Initialization Race Condition

File: includes/class-infinite-loader-for-woocommerce-activator.php

Lines 25+ - Fix activation logic:

public static function activate() {
    // Check if WooCommerce is active before setting defaults
    if ( ! class_exists( 'WooCommerce' ) ) {
        deactivate_plugins( plugin_basename( __FILE__ ) );
        wp_die( 
            esc_html__( 'Infinite Loader for WooCommerce requires WooCommerce to be installed and activated.', 'infinite-loader-for-woocommerce' ),
            esc_html__( 'Plugin Activation Error', 'infinite-loader-for-woocommerce' ),
            array( 'back_link' => true )
        );
    }

    // Use atomic operations for option creation
    $infinite_loader_default_options = self::infinite_loader_get_default_options();
    
    foreach ( $infinite_loader_default_options as $option_name => $default_values ) {
        $existing_option = get_option( $option_name );
        
        if ( false === $existing_option ) {
            // Option doesn't exist, create it
            add_option( $option_name, $default_values );
        } else {
            // Option exists, merge with defaults for new keys
            $merged_options = array_merge( $default_values, $existing_option );
            update_option( $option_name, $merged_options );
        }
    }
    
    // Set activation flag for welcome redirect
    set_transient( 'infinite_loader_activation_redirect', true, 30 );
}

/**
 * Get default option values
 *
 * @return array Default options
 */
private static function infinite_loader_get_default_options() {
    return array(
        'infinite_loader_admin_general_option' => array(
            'product_loading_type' => 'pagination',
            'product_per_page'     => '8',
            'loading_image'        => 'fa-spinner',
            'rotate_image'         => 'yes',
            'do_not_update_url'    => 'no',
            'enable_font_awesome'  => 'yes',
        ),
        'infinite_loader_admin_button_option' => array(
            'button_text'                  => 'Load More',
            'background_color'             => '#1d76da',
            'background_color_mouse_hover' => '#0e4da0',
            'border_color'                 => '#1d76da',
            'text_color'                   => '#ffffff',
            'text_color_mouse_hover'       => '#ffffff',
            'text_font_size'               => '16',
            'padding_top'                  => '13',
            'padding_right'                => '30',
            'padding_bottom'               => '13',
            'padding_left'                 => '30',
            'border_radius_top'            => '50',
            'border_radius_right'          => '50',
            'border_radius_bottom'         => '50',
            'border_radius_left'           => '50',
            'border_top'                   => '1',
            'border_right'                 => '1',
            'border_bottom'                => '1',
            'border_left'                  => '1',
        ),
        'infinite_loader_admin_previous_button_option' => array(
            'button_text'                  => 'Load Previous',
            'background_color'             => '#1d76da',
            'background_color_mouse_hover' => '#0e4da0',
            'border_color'                 => '#1d76da',
            'text_color'                   => '#ffffff',
            'text_color_mouse_hover'       => '#ffffff',
            'text_font_size'               => '16',
            'padding_top'                  => '13',
            'padding_right'                => '30',
            'padding_bottom'               => '13',
            'padding_left'                 => '30',
            'margin_bottom'                => '20',
            'border_radius_top'            => '50',
            'border_radius_right'          => '50',
            'border_radius_bottom'         => '50',
            'border_radius_left'           => '50',
            'border_top'                   => '1',
            'border_right'                 => '1',
            'border_bottom'                => '1',
            'border_left'                  => '1',
        ),
    );
}

LOGIC-009: Fix Product Count Logic Issues

File: public/class-infinite-loader-for-woocommerce-public.php

Lines 250+ - Fix result count logic:

/**
 * Display Woocommerce count with improved logic
 *
 * @param string $text Original text
 * @return string Modified text
 */
public function infinite_loader_products_count_additional( $text ) {
    remove_filter( 'ngettext', array( $this, 'infinite_loader_products_count_additional' ), 1, 9999 );
    remove_filter( 'ngettext_with_context', array( $this, 'infinite_loader_products_count_additional' ), 1, 9999 );
    
    // Get product count data with fallback
    $infinite_loader_count_data = $this->infinite_loader_get_product_count_data();
    
    if ( ! $infinite_loader_count_data ) {
        return $text;
    }
    
    extract( $infinite_loader_count_data );
    
    echo '<span class="infinite_loader_product_count" style="display: none;" data-text="';
    
    // Use proper WordPress internationalization
    if ( 1 === $total ) {
        echo esc_html__( 'Showing the single result', 'infinite-loader-for-woocommerce' );
    } elseif ( $total <= $per_page || -1 === $per_page ) {
        printf( 
            /* translators: %d: total number of results */
            esc_html( _n( 'Showing %d result', 'Showing %d results', $total, 'infinite-loader-for-woocommerce' ) ), 
            number_format_i18n( $total ) 
        );
    } else {
        printf( 
            /* translators: 1: first result number 2: last result number 3: total results */
            esc_html__( 'Showing %1$s&ndash;%2$s of %3$s results', 'infinite-loader-for-woocommerce' ), 
            number_format_i18n( $first ), 
            number_format_i18n( $last ), 
            number_format_i18n( $total ) 
        );
    }
    
    printf( 
        '" data-start="%d" data-end="%d" data-laststart="%d" data-lastend="%d"></span>',
        esc_attr( $first ),
        esc_attr( $last ),
        esc_attr( $first ),
        esc_attr( $last )
    );
    
    return $text;
}

/**
 * Get product count data with proper error handling
 *
 * @return array|false Product count data or false on error
 */
private function infinite_loader_get_product_count_data() {
    try {
        if ( class_exists( 'WC_Query' ) && method_exists( 'WC_Query', 'product_query' ) && function_exists( 'wc_get_loop_prop' ) ) {
            $total    = wc_get_loop_prop( 'total' );
            $per_page = wc_get_loop_prop( 'per_page' );
            $paged    = wc_get_loop_prop( 'current_page' );
            
            // Validate WooCommerce data
            if ( ! is_numeric( $total ) || ! is_numeric( $per_page ) || ! is_numeric( $paged ) ) {
                throw new Exception( 'Invalid WooCommerce loop properties' );
            }
            
        } else {
            global $wp_query;
            
            if ( ! isset( $wp_query ) || ! is_object( $wp_query ) ) {
                throw new Exception( 'WP_Query not available' );
            }
            
            $paged    = max( 1, $wp_query->get( 'paged' ) );
            $per_page = $wp_query->get( 'posts_per_page' );
            $total    = $wp_query->found_posts;
        }
        
        // Calculate first and last with bounds checking
        $first = max( 1, ( $per_page * $paged ) - $per_page + 1 );
        $last  = min( $total, $per_page * $paged );
        
        // Ensure logical consistency
        if ( $first > $last || $last > $total ) {
            throw new Exception( 'Inconsistent pagination calculation' );
        }
        
        return compact( 'total', 'per_page', 'paged', 'first', 'last' );
        
    } catch ( Exception $e ) {
        error_log( 'Infinite Loader: Product count calculation failed - ' . $e->getMessage() );
        return false;
    }
}

LOGIC-010: Fix Asset Loading Logic

File: public/class-infinite-loader-for-woocommerce-public.php

Lines 50+ - Improve conditional loading:

/**
 * Register the stylesheets for the public-facing side of the site.
 *
 * @since    1.0.0
 */
public function enqueue_styles() {
    // Only load on relevant pages
    if ( ! $this->infinite_loader_should_load_assets() ) {
        return;
    }
    
    $infinite_loader_asset_config = $this->infinite_loader_get_asset_config();
    
    wp_enqueue_style( 
        $this->plugin_name, 
        plugin_dir_url( __FILE__ ) . 'css' . $infinite_loader_asset_config['css_path'] . '/infinite-loader-for-woocommerce-public' . $infinite_loader_asset_config['css_extension'], 
        array(), 
        $this->version, 
        'all' 
    ); 
}

/**
 * Register the JavaScript for the public-facing side of the site.
 *
 * @since    1.0.0
 */
public function enqueue_scripts() {
    if ( ! $this->infinite_loader_should_load_assets() ) {
        return;
    }
    
    $infinite_loader_asset_config = $this->infinite_loader_get_asset_config();
    
    wp_enqueue_script( 
        'infinite_loader_products', 
        plugin_dir_url( __FILE__ ) . 'js' . $infinite_loader_asset_config['js_path'] . '/infinite-loader-for-woocommerce-public' . $infinite_loader_asset_config['js_extension'], 
        array( 'jquery' ), 
        $this->version, 
        true // Load in footer for better performance
    );
}

/**
 * Check if assets should be loaded on current page
 *
 * @return bool True if assets should be loaded
 */
private function infinite_loader_should_load_assets() {
    // Load on shop pages and product archives
    return is_shop() || is_product_category() || is_product_tag() || is_product_taxonomy();
}

/**
 * Get asset configuration for current environment
 *
 * @return array Asset paths and extensions
 */
private function infinite_loader_get_asset_config() {
    $is_debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
    $is_rtl   = is_rtl();
    
    return array(
        'css_extension' => $is_debug ? ( $is_rtl ? '.rtl.css' : '.css' ) : ( $is_rtl ? '.rtl.css' : '.min.css' ),
        'css_path'      => $is_debug ? ( $is_rtl ? '/rtl' : '' ) : ( $is_rtl ? '/rtl' : '/min' ),
        'js_extension'  => $is_debug ? '.js' : '.min.js',
        'js_path'       => $is_debug ? '' : '/min',
    );
}

These improvements maintain all existing function prefixes while fixing critical logic issues, improving error handling, and enhancing performance without breaking existing functionality.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions