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 ] ) ? ' ' : $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; } } } } } }