Bước 1: Trong PHP

<?php 
// Thêm extra fields
class Extend_Attribute_Product extends WC_Product_Simple {
    public $fields; // Define new attribute, using for compare product
}

// 1. Thêm Nút So Sánh vào Sản Phẩm
add_action( 'woocommerce_after_shop_loop_item_title', 'woocommerce_custom_add_quick_cta', 4 );
function woocommerce_custom_add_quick_cta(){     
    $product_id = get_the_ID();
    $compare_products = isset($_SESSION['compare_products']) ? $_SESSION['compare_products'] : [];
    $wishlist = isset($_COOKIE['wishlist']) ? json_decode(stripslashes($_COOKIE['wishlist']), true) : [];
    
    $class_in_compare_list = in_array( $product_id, $compare_products ) ? 'in-compareList' : '';
    $class_in_wishlist_list = isset( $wishlist[$product_id] ) ? 'in-wishlist' : '';
?>
    <button class="add-to-compare <?php echo $class_in_compare_list; ?>" data-id="<?php echo $product_id; ?>">
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
            <path fill-rule="evenodd" d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5m14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5"/>
        </svg>
    </button>
    <?php
}

// 2. Đăng ký jquery
add_action('wp_enqueue_scripts', 'compare_scripts');
function compare_scripts() {
    wp_enqueue_script('compare-products', get_template_directory_uri() . '/js/compare.js', array('jquery'), null, true);
    wp_localize_script('compare-products', 'compare_ajax', array(
        'ajax_url' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce( 'ajax-nonce-action' )
    ));
}

// đảm bảo session_start() được gọi ở đầu file PHP, trước bất kỳ dòng HTML nào hoặc trước khi gọi các hàm xuất dữ liệu (như echo).
// Giải pháp: Thêm vào đầu file functions.php
add_action('init', function () {
    if (!session_id()) {
        session_start();
    }
});

// 3. Xử lý AJAX
add_action('wp_ajax_add_to_compare', 'handle_add_to_compare');
add_action('wp_ajax_nopriv_add_to_compare', 'handle_add_to_compare');
add_action('wp_ajax_remove_from_compare', 'handle_remove_from_compare');
add_action('wp_ajax_nopriv_remove_from_compare', 'handle_remove_from_compare');
add_action('wp_ajax_pgs_remove_compare_list', 'handle_remove_compare_list');
add_action('wp_ajax_nopriv_pgs_remove_compare_list', 'handle_remove_compare_list');

/**
 * 3.1 Handle ajax add compare product
 */
function handle_add_to_compare() {
    // phpcs:disable WordPress.Security.NonceVerification.Missing
    if ( !isset( $_POST['nonce'] ) ) {
        wp_send_json_error( 'missing_fields' );
        wp_die();
    }  

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    if( !wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'ajax-nonce-action' ) ){ 
        wp_send_json_error( 'bad_nonce' );
        wp_die();
    }  

    $product_id = intval($_POST['product_id']); // Lấy ID sản phẩm từ yêu cầu

    if (!isset($_SESSION['compare_products'])) {
        $_SESSION['compare_products'] = [];
    }

    // If the product is not in the list
    if (!in_array($product_id, $_SESSION['compare_products'])) {
        // Add new product
        $_SESSION['compare_products'][] = $product_id;

        // Limit comparison list to maximum 4 products
        if (count($_SESSION['compare_products']) > 4) {
            array_shift($_SESSION['compare_products']); // Delete the first (oldest) product
        }
    }

    wp_send_json_success($_SESSION['compare_products']);
}

/**
 * 3.2 Handle ajax remove compare product
 */
function handle_remove_from_compare() {
    // phpcs:disable WordPress.Security.NonceVerification.Missing
    if ( !isset( $_POST['nonce'] ) ) {
        wp_send_json_error( 'missing_fields' );
        wp_die();
    }  

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    if( !wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'ajax-nonce-action' ) ){ 
        wp_send_json_error( 'bad_nonce' );
        wp_die();
    }

    $product_id = intval($_POST['product_id']);
    if ( isset($_SESSION['compare_products']) && in_array($product_id, $_SESSION['compare_products'])) {

        $_SESSION['compare_products'] = array_diff($_SESSION['compare_products'], [$product_id]);
    }

    wp_send_json_success($_SESSION['compare_products']);
}


/**
 * 3.3 Handle ajax remove all product compare
 */
function handle_remove_compare_list() {
    // phpcs:disable WordPress.Security.NonceVerification.Missing
    if ( !isset( $_POST['nonce'] ) ) {
        wp_send_json_error( 'missing_fields' );
        wp_die();
    }  

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    if( !wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'ajax-nonce-action' ) ){ 
        wp_send_json_error( 'bad_nonce' );
        wp_die();
    }

    if ( isset($_SESSION['compare_products'])) {

        $_SESSION['compare_products'] = [];
    }

    wp_send_json_success([]);
}

?>

Bước 2: Xử lý jquery ajax

Trong file compare.js bạn hãy thêm mã sau

jQuery(document).ready(function ($) {
    // Set equal row
    function updateCompareProductsRowHeight() {
        const $rowTableRight = $('.compare-products-wrapper .right-table .table-body ul');
        const $rowTableLeft = $('.compare-products-wrapper .left-table .table-body ul');

        [...$rowTableRight].forEach((element, index) => {
            const rowHeight = $(element).height();
            $($rowTableLeft[index]).height(rowHeight);
        });
    }

    if ($('.compare-products-wrapper .right-table').length > 0) {
        updateCompareProductsRowHeight();

        $(window).resize(function () {
            updateCompareProductsRowHeight();
        });
    }

    // Add to compare
    $(document).on('click', '.add-to-compare', function (e) {
        e.preventDefault();

        const $this = $(this);
        const product_id = $this.data('id');

        $.ajax({
            url: compare_ajax.ajax_url,
            type: 'POST',
            data: {
                action: 'add_to_compare',
                product_id: product_id,
                nonce: compare_ajax.nonce,
            },
            beforeSend: function () {
                $this.attr('disabled', true);
            },
            success: function (response) {
                $this.removeAttr('disabled');
                $this.addClass('in-compareList');

                if (response.success) {
                    // bạn có thể show poup link trang compare, or thông báo ở đây
                }
                return;
            },
            error: function (error) {
                console.log(error);

                $this.removeAttr('disabled');
            },
        });
    });

    // Remove compare product
    $(document).on('click', '.remove-compare', function (e) {
        e.preventDefault();

        const $this = $(this);
        const product_id = $this.data('id');

        $.ajax({
            url: compare_ajax.ajax_url,
            type: 'POST',
            data: {
                action: 'remove_from_compare',
                product_id: product_id,
                nonce: compare_ajax.nonce, // Gửi nonce
            },
            beforeSend: function () {
                $this.attr('disabled', true);
            },
            success: function (response) {
                $this.removeAttr('disabled');

                console.log(response);

                if (response.success) {
                    window.location.reload();
                    return;
                }
            },
            error: function (error) {
                console.log(error);

                $this.removeAttr('disabled');
            },
        });
    });

    // Remove all compare product
    $('#clear-compare-list').on('click', function (e) {
        e.preventDefault();

        const $this = $(this);

        $.ajax({
            url: compare_ajax.ajax_url,
            type: 'POST',
            data: {
                action: 'remove_compare_list',
                nonce: compare_ajax.nonce,
            },
            beforeSend: function () {
                $this.css('pointer-events', 'none');
            },
            success: function (response) {
                $this.css('pointer-events', 'auto');

                if (response.success) {
                    window.location.reload();
                    return;
                }
            },
            error: function (error) {
                $this.css('pointer-events', 'auto');

                console.log(error);
            },
        });
    });
    
});

Bước 3: Tạo page template compare product và hiển thị sản phẩm compare

<?php
/*
Template Name: Compare products
Template Post Type: page
*/

// Only show when WooCommerce is active
if ( !class_exists( 'WooCommerce' ) ) {
    return '';
} 

get_header();

?>

<div class="page-title"><?php the_title(); ?></div>
<div class="fl-post-content">
    <?php
        $compare_products = isset($_SESSION['compare_products']) ? $_SESSION['compare_products'] : [];
    ?>
    <?php if ( !empty( $compare_products ) ) : ?>
        <?php 
            $taxonomies = get_object_taxonomies( 'product' );
            // $exclude_taxonomies = ['product_type', 'product_visibility', 'product_cat', 'product_tag', 'product_shipping_class', 'sale'];
            // $field_specification = array_diff( $taxonomies, $exclude_taxonomies );
            $field_specification = [
                'pa_availability' => 'Availability',
                'pa_brand' => 'Brand',
                'pa_brand-mint' => 'Brand/Mint',
                'pa_condition' => 'Condition',
                'pa_country' => 'Country',
                'pa_designation' => 'Designation',
                'pa_grade' => 'Grade',
                'pa_grade-service' => 'Grade Service',
                'pa_grading-company' => 'Grading company',
                'pa_metal-type' => 'Metal Type',
                'pa_ira-approved' => 'IRA approved',                
                'pa_metal-type' => 'Metal Type',
                'pa_price' => 'Denomination',
                'pa_product-line' => 'Product Line',
                'pa_series' => 'Series',
                'pa_weight' => 'Weight',
                'pa_sale' => 'Sale'
            ];

            $field_compare_default = [
                'remove-product' => '',
                'product-picture' => '',
                'product-name' => 'Name',
                'product-price' => 'Price',
                'product-short-desc' => 'Short description'
            ];  

            $fields_compare = array_merge( $field_compare_default, $field_specification );       

            $product_list = [];
            foreach( $compare_products as $product_id ){
                $product = new Extend_Attribute_Product( $product_id );
        if ( !$product || 'publish' !== $product->get_status() ) {
          continue;
        }

                $product->fields = array();

                // Custom attributes.
                foreach ( $fields_compare as $field => $name ) {

                    switch ( $field ) {
                        case 'product-name':
                            $product->fields[ $field ] = $product->get_title();
                            break;
                        case 'product-price':
                            $product->fields[ $field ] = $product->get_price_html();
                            break;
                        case 'product-picture':
                            $product->fields[ $field ] = absint( $product->get_image_id() );
                            break;
                        case 'product-short-desc':
                            $product->fields[ $field ] = $product->get_short_description();
                            break;
                        default:
                            if ( taxonomy_exists( $field ) ) {
                                $product->fields[ $field ] = array();
                                $terms                     = get_the_terms( $product_id, $field );
                                if ( ! empty( $terms ) ) {
                                    foreach ( $terms as $term ) {
                                        $term                        = sanitize_term( $term, $field );
                                        $product->fields[ $field ][] = $term->name;
                                    }
                                }
                                $product->fields[ $field ] = implode( ', ', $product->fields[ $field ] );
                            }
                            break;
                    }
                }  
                $product_list[ $product_id ] = $product;              
            }  
        ?>
        <a href="#" class="clear-list" id="clear-compare-list" rel="nofollow">Clear list</a>
        <div class="compare-products-wrapper d-flex">
            <div>
                <div class="table left-table">
                    <div class="table-body">
                        <?php foreach( $fields_compare as $field => $name ): ?>
                            <ul class="<?php echo esc_attr($field); ?>">
                                <li><?php echo esc_attr( $name ); ?></li>
                            </ul>
                        <?php endforeach; ?>    
                    </div>
                </div>
            </div>
            <div class="scrollbar-wrapper">
                <div class="table right-table">
                    <div class="table-body">
                        <?php foreach( $fields_compare as $field => $name ): ?>
                            <ul class="<?php echo esc_attr($field); ?>">
                                <?php foreach( $product_list as $product_id => $product ): ?>
                                    <li>
                                        <?php 
                                            switch ( $field ) {
                                                case 'remove-product':     
                                                    echo sprintf('<button type="button" class="remove-compare" data-id="%d">Remove</button>', $product_id );       
                                                    break;

                                                case 'product-picture':
                                                    echo sprintf('<a class="picture" href="%s">%s</a>', $product->get_permalink(), $product->get_image('thumbnail') );
                                                    break;

                                                case 'product-name':
                                                    echo sprintf('<a href="%s">%s</a>', $product->get_permalink(), $product->get_title() );
                                                    break;

                                                default:
                                                    echo empty( $product->fields[ $field ] ) ? '&nbsp;' : $product->fields[ $field ]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                                                    break;
                                            }                                        
                                        ?>
                                    </li>
                                <?php endforeach; ?>    
                            </ul>
                        <?php endforeach; ?>                          
                    </div>
                </div>
            </div>
        </div>
    <?php else: ?>        
        <div class="no-data">You have no items to compare.</div>
    <?php endif; ?>    
</div>

<?php get_footer(); ?>

Add style css

.compare-products-wrapper {
    .table {
        .table-body {
            ul {
                display: flex;
                li {
                    flex: 1;
                    padding: 10px;
                    border: 1px solid #dedede;
                    margin-left: -1px;
                    margin-bottom: -1px;
                    min-height: 56px;
                    img {
                        max-width: 120px;
                        max-height: 120px;
                        width: 100%;
                        height: 100%;
                        object-fit: cover;
                        display: block;
                        margin: 0 auto;
                    }
                }
            }
            ul.remove-product {
                li {
                    text-align: center;
                    button {
                        background: #f55d5d;
                        border: none;
                        outline: 0;
                        color: #ffff;
                        font-size: 15px;
                        padding: 2px 15px;
                        display: flex;
                        margin: 0 auto;
                    }
                }
            }
        }
    }
    .left-table {
        max-width: 120px;
        .table-body {
            ul {
                li {
                    display: flex;
                    align-items: center;
                }
            }
        }
    }
    .right-table {
        .table-body {
            ul {
                li {
                    display: flex;
                    align-items: center;
                }
            }
        }
    }
    .scrollbar-wrapper {
        flex: 1;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        direction: ltr;
    }
    @media (max-width: 768px) {
        .right-table {
            width: max-content;
            .table-body {
                ul {
                    li {
                        max-width: 180px;
                    }
                }
            }
        }
    }
}

compare-product

Bài viết liên quan

post-no-image

Update post meta ACF sử dụng Rest API WordPresss

post-no-image

Jquery ngăn không cho ô input type number nhập ký tự không hợp lệ

post-no-image

Loadmore product woocommerce infinity scroll

post-no-image

Add the Meta Box Upload Multiple Images and multiple metabox

post-no-image

Add the Meta Box Repeat

post-no-image

Kỹ thuật debounce trong javascript – Trì hoãn nhập từ khóa trong ô input