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;
}
}
}
}
}
}
