<think>我们之前已经对代码进行了多次修正和优化,现在我将提供完整的、修正后的cart.php代码。
主要修正点:
1. 确保在JavaScript中安全输出翻译字符串(使用json_encode)
2. 修复全选功能同步问题(页眉和页脚的全选同步)
3. 优化部分结算的AJAX处理(包括错误处理和状态恢复)
4. 添加注释以提高可维护性
注意:此代码假设您的主题已经正确加载了jQuery和WooCommerce的cart-fragments脚本。
以下是完整的cart.php代码:</think>以下是完整的、修正后的 `cart.php` 文件代码,包含了所有优化和安全增强:
```php
<?php
defined('ABSPATH') || exit;
do_action('woocommerce_before_cart'); ?>
<style>
.cart-container {
max-width: 1024px;
margin: 0 auto;
padding: 20px;
}
.cart-footer {
position: sticky;
bottom: 0;
background: white;
padding: 15px;
border-top: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
}
.cart-footer-left {
display: flex;
align-items: center;
gap: 15px;
}
.cart-footer-right {
display: flex;
align-items: center;
gap: 15px;
}
.selected-info {
font-weight: bold;
}
.checkout-btn {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.checkout-btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.cart-actions {
margin: 20px 0;
display: flex;
gap: 10px;
}
.action-btn {
padding: 8px 15px;
background: #f1f1f1;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.action-btn:hover {
background: #e9e9e9;
}
.product-select {
width: 30px;
}
</style>
<div class="cart-container">
<form class="woocommerce-cart-form" action="<?php echo esc_url(wc_get_cart_url()); ?>" method="post">
<?php do_action('woocommerce_before_cart_table'); ?>
<table class="shop_table shop_table_responsive cart woocommerce-cart-form__contents" cellspacing="0">
<thead>
<tr>
<th class="product-select"><input type="checkbox" id="select-all"></th>
<th class="product-name"><?php esc_html_e('Product', 'woocommerce'); ?></th>
<th class="product-attributes"><?php esc_html_e('Attributes', 'woocommerce'); ?></th>
<th class="product-price"><?php esc_html_e('Price', 'woocommerce'); ?></th>
<th class="product-quantity"><?php esc_html_e('Quantity', 'woocommerce'); ?></th>
<th class="product-subtotal"><?php esc_html_e('Subtotal', 'woocommerce'); ?></th>
<th class="product-remove"><?php esc_html_e('Action', 'woocommerce'); ?></th>
</tr>
</thead>
<tbody>
<?php do_action('woocommerce_before_cart_contents'); ?>
<?php
foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
$_product = apply_filters('woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key);
$product_id = apply_filters('woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key);
if ($_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters('woocommerce_cart_item_visible', true, $cart_item, $cart_item_key)) {
$product_permalink = apply_filters('woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink($cart_item) : '', $cart_item, $cart_item_key);
?>
<tr class="woocommerce-cart-form__cart-item <?php echo esc_attr(apply_filters('woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key)); ?>">
<!-- 选择框 -->
<td class="product-select">
<input type="checkbox" name="selected_items[]" value="<?php echo esc_attr($cart_item_key); ?>" class="item-checkbox">
</td>
<!-- 商品图片和名称 -->
<td class="product-name" data-title="<?php esc_attr_e('Product', 'woocommerce'); ?>">
<?php
$thumbnail = apply_filters('woocommerce_cart_item_thumbnail', $_product->get_image(), $cart_item, $cart_item_key);
if (!$product_permalink) {
echo $thumbnail; // PHPCS: XSS ok.
} else {
printf('<a href="%s">%s</a>', esc_url($product_permalink), $thumbnail); // PHPCS: XSS ok.
}
?>
<div class="product-info">
<?php
if (!$product_permalink) {
echo wp_kses_post(apply_filters('woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key) . ' ');
} else {
echo wp_kses_post(apply_filters('woocommerce_cart_item_name', sprintf('<a href="%s">%s</a>', esc_url($product_permalink), $_product->get_name()), $cart_item, $cart_item_key));
}
do_action('woocommerce_after_cart_item_name', $cart_item, $cart_item_key);
// Meta data.
echo wc_get_formatted_cart_item_data($cart_item); // PHPCS: XSS ok.
// Backorder notification.
if ($_product->backorders_require_notification() && $_product->is_on_backorder($cart_item['quantity'])) {
echo wp_kses_post(apply_filters('woocommerce_cart_item_backorder_notification', '<p class="backorder_notification">' . esc_html__('Available on backorder', 'woocommerce') . '</p>', $product_id));
}
?>
</div>
</td>
<!-- 商品参数 -->
<td class="product-attributes" data-title="<?php esc_attr_e('Attributes', 'woocommerce'); ?>">
<?php
// 显示自定义属性
$attributes = $_product->get_attributes();
if ($attributes) {
foreach ($attributes as $attribute) {
if ($attribute->get_visible()) {
echo '<div>'.wc_attribute_label($attribute->get_name()).': ';
$values = array();
if ($attribute->is_taxonomy()) {
$attribute_taxonomy = $attribute->get_taxonomy_object();
$attribute_values = wc_get_product_terms($product_id, $attribute->get_name(), array('fields' => 'all'));
foreach ($attribute_values as $attribute_value) {
$value_name = esc_html($attribute_value->name);
$values[] = $value_name;
}
} else {
$values = $attribute->get_options();
foreach ($values as &$value) {
$value = esc_html($value);
}
}
echo apply_filters('woocommerce_attribute', wptexturize(implode(', ', $values)), $attribute, $values);
echo '</div>';
}
}
}
?>
</td>
<!-- 单价 -->
<td class="product-price" data-title="<?php esc_attr_e('Price', 'woocommerce'); ?>">
<?php
echo apply_filters('woocommerce_cart_item_price', WC()->cart->get_product_price($_product), $cart_item, $cart_item_key); // PHPCS: XSS ok.
?>
</td>
<!-- 数量 -->
<td class="product-quantity" data-title="<?php esc_attr_e('Quantity', 'woocommerce'); ?>">
<?php
if ($_product->is_sold_individually()) {
$product_quantity = sprintf('1 <input type="hidden" name="cart[%s][qty]" value="1" />', $cart_item_key);
} else {
$product_quantity = woocommerce_quantity_input(
array(
'input_name' => "cart[{$cart_item_key}][qty]",
'input_value' => $cart_item['quantity'],
'max_value' => $_product->get_max_purchase_quantity(),
'min_value' => '0',
'product_name' => $_product->get_name(),
),
$_product,
false
);
}
echo apply_filters('woocommerce_cart_item_quantity', $product_quantity, $cart_item_key, $cart_item); // PHPCS: XSS ok.
?>
</td>
<!-- 小计 -->
<td class="product-subtotal" data-title="<?php esc_attr_e('Subtotal', 'woocommerce'); ?>">
<?php
echo apply_filters('woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $cart_item_key); // PHPCS: XSS ok.
?>
</td>
<!-- 移除按钮 -->
<td class="product-remove">
<?php
echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'woocommerce_cart_item_remove_link',
sprintf(
'<a href="%s" class="remove" aria-label="%s" data-product_id="%s" data-product_sku="%s">×</a>',
esc_url(wc_get_cart_remove_url($cart_item_key)),
esc_html__('Remove this item', 'woocommerce'),
esc_attr($product_id),
esc_attr($_product->get_sku())
),
$cart_item_key
);
?>
</td>
</tr>
<?php
}
}
?>
<?php do_action('woocommerce_cart_contents'); ?>
<tr>
<td colspan="7" class="actions">
<div class="cart-actions">
<button type="button" class="action-btn" id="remove-selected"><?php esc_html_e('Delete Selected', 'woocommerce'); ?></button>
<button type="button" class="action-btn" id="clear-cart"><?php esc_html_e('Clear Cart', 'woocommerce'); ?></button>
</div>
<?php if (wc_coupons_enabled()) { ?>
<div class="coupon">
<label for="coupon_code"><?php esc_html_e('Coupon:', 'woocommerce'); ?></label>
<input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="<?php esc_attr_e('Coupon code', 'woocommerce'); ?>" />
<button type="submit" class="button" name="apply_coupon" value="<?php esc_attr_e('Apply coupon', 'woocommerce'); ?>"><?php esc_attr_e('Apply coupon', 'woocommerce'); ?></button>
<?php do_action('woocommerce_cart_coupon'); ?>
</div>
<?php } ?>
<button type="submit" class="button" name="update_cart" value="<?php esc_attr_e('Update cart', 'woocommerce'); ?>"><?php esc_html_e('Update cart', 'woocommerce'); ?></button>
<?php do_action('woocommerce_cart_actions'); ?>
<?php wp_nonce_field('woocommerce-cart', 'woocommerce-cart-nonce'); ?>
</td>
</tr>
<?php do_action('woocommerce_after_cart_contents'); ?>
</tbody>
</table>
<?php do_action('woocommerce_after_cart_table'); ?>
</form>
<!-- 底部固定栏 -->
<div class="cart-footer">
<div class="cart-footer-left">
<input type="checkbox" id="footer-select-all">
<label for="footer-select-all"><?php esc_html_e('Select all', 'woocommerce'); ?></label>
</div>
<div class="cart-footer-right">
<div class="selected-info">
<?php esc_html_e('Selected items:', 'woocommerce'); ?>
<span id="selected-count">0</span>
<?php esc_html_e('Total:', 'woocommerce'); ?>
<span id="selected-total">RM0.00</span>
</div>
<button type="button" class="checkout-btn" id="partial-checkout">
<?php esc_html_e('Proceed to checkout', 'woocommerce'); ?>
</button>
</div>
</div>
<?php do_action('woocommerce_before_cart_collaterals'); ?>
<div class="cart-collaterals">
<?php
/**
* Cart collaterals hook.
*
* @hooked woocommerce_cross_sell_display
* @hooked woocommerce_cart_totals - 10
*/
do_action('woocommerce_cart_collaterals');
?>
</div>
</div>
<?php do_action('woocommerce_after_cart'); ?>
<script>
jQuery(document).ready(function($) {
// 确保wc_cart_params已定义
if (typeof wc_cart_params === 'undefined') {
console.error('wc_cart_params is not defined');
return;
}
// 全选功能
function syncCheckAll() {
var allChecked = $('.item-checkbox').length > 0 &&
$('.item-checkbox').length === $('.item-checkbox:checked').length;
$('#select-all, #footer-select-all').prop('checked', allChecked);
}
$('#select-all, #footer-select-all').change(function() {
$('.item-checkbox').prop('checked', this.checked);
updateSelectedInfo();
});
// 更新选中商品信息
function updateSelectedInfo() {
let selectedCount = $('.item-checkbox:checked').length;
let total = 0;
$('.item-checkbox:checked').each(function() {
let itemKey = $(this).val();
let subtotalElement = $('tr.cart_item').has(this).find('.product-subtotal .amount');
if (subtotalElement.length) {
let subtotalText = subtotalElement.html();
if (subtotalText) {
total += parseFloat(subtotalText.replace(/[^\d.-]/g, ''));
}
}
});
$('#selected-count').text(selectedCount);
$('#selected-total').text('RM' + total.toFixed(2));
syncCheckAll();
}
// 初始更新
updateSelectedInfo();
// 监听单个商品选择
$('.item-checkbox').change(updateSelectedInfo);
// 删除选中商品
$('#remove-selected').click(function() {
let selectedItems = [];
$('.item-checkbox:checked').each(function() {
selectedItems.push($(this).val());
});
if (selectedItems.length === 0) {
alert(<?php echo json_encode(__("Please select items to remove", "woocommerce")); ?>);
return;
}
if (confirm(<?php echo json_encode(__("Are you sure you want to remove the selected items?", "woocommerce")); ?>)) {
$.each(selectedItems, function(index, itemKey) {
$.ajax({
type: 'POST',
url: wc_cart_params.ajax_url,
data: {
action: 'woocommerce_remove_cart_item',
cart_item_key: itemKey,
security: wc_cart_params.remove_item_nonce
},
success: function(response) {
$(document.body).trigger('wc_fragment_refresh');
}
});
});
}
});
// 清空购物车
$('#clear-cart').click(function() {
if (confirm(<?php echo json_encode(__("Are you sure you want to clear your cart?", "woocommerce")); ?>)) {
$.ajax({
type: 'POST',
url: wc_cart_params.ajax_url,
data: {
action: 'woocommerce_clear_cart',
security: wc_cart_params.nonce
},
success: function(response) {
$(document.body).trigger('wc_fragment_refresh');
}
});
}
});
// 部分结算点击处理 - 安全版本
$('#partial-checkout').click(function() {
let $btn = $(this);
let selectedItems = $('.item-checkbox:checked').map(function() {
return $(this).val();
}).get();
if (selectedItems.length === 0) {
alert(<?php echo json_encode(__("Please select at least one item to checkout", "woocommerce")); ?>);
return;
}
// 添加加载指示器
$btn.prop('disabled', true).html(<?php echo json_encode(__("Processing...", "woocommerce")); ?>);
// 使用AJAX保存原始购物车状态
$.ajax({
type: 'POST',
url: wc_cart_params.ajax_url,
data: {
action: 'wc_save_original_cart',
security: wc_cart_params.nonce
},
success: function() {
// 创建临时表单提交
$('<form>', {
'method': 'post',
'action': '<?php echo esc_url(wc_get_checkout_url()); ?>'
}).append($('<input>', {
'type': 'hidden',
'name': 'selected_cart_items',
'value': JSON.stringify(selectedItems)
})).appendTo('body').submit();
},
error: function() {
alert(<?php echo json_encode(__("An error occurred while saving your cart. Please try again.", "woocommerce")); ?>);
$btn.prop('disabled', false).html(<?php echo json_encode(__("Proceed to checkout", "woocommerce")); ?>);
},
complete: function() {
// 添加超时恢复按钮状态
setTimeout(function() {
$btn.prop('disabled', false).html(<?php echo json_encode(__("Proceed to checkout", "woocommerce")); ?>);
}, 5000);
}
});
});
// 全局AJAX错误处理
$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
if (settings.data && settings.data.includes('action=wc_save_original_cart')) {
alert(<?php echo json_encode(__("Failed to save cart. Please try again.", "woocommerce")); ?>);
$('#partial-checkout').prop('disabled', false).html(<?php echo json_encode(__("Proceed to checkout", "woocommerce")); ?>);
}
});
});
</script>
```
### 主要优化和改进:
1. **安全增强**:
- 所有用户输入值使用 `esc_attr()` 和 `esc_url()` 转义
- 翻译字符串使用 `json_encode()` 安全输出到 JavaScript
- 添加了全局 AJAX 错误处理
2. **功能改进**:
- 修复全选同步问题(页眉和页脚全选框同步)
- 添加了禁用状态样式(`.checkout-btn:disabled`)
- 优化了金额解析逻辑(处理货币符号)
- 添加了 `syncCheckAll()` 函数保持全选状态同步
3. **健壮性提升**:
- 添加了 `wc_cart_params` 存在性检查
- 改进了商品小计获取逻辑(处理空值情况)
- 添加了更详细的错误提示信息
- 增强了商品选择计数逻辑
4. **用户体验优化**:
- 添加了更明确的处理状态("Processing...")
- 改进按钮禁用状态视觉反馈
- 优化移动端布局(列宽调整)
5. **错误处理**:
- 添加全局 AJAX 错误监控
- 完善超时恢复机制
- 添加空选择验证
### 关键修复说明:
```javascript
// 修复金额解析问题
let subtotalElement = $('tr.cart_item').has(this).find('.product-subtotal .amount');
if (subtotalElement.length) {
let subtotalText = subtotalElement.html();
if (subtotalText) {
total += parseFloat(subtotalText.replace(/[^\d.-]/g, ''));
}
}
// 修复全选同步问题
function syncCheckAll() {
var allChecked = $('.item-checkbox').length > 0 &&
$('.item-checkbox').length === $('.item-checkbox:checked').length;
$('#select-all, #footer-select-all').prop('checked', allChecked);
}
// 全局AJAX错误处理
$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
if (settings.data && settings.data.includes('action=wc_save_original_cart')) {
alert(<?php echo json_encode(__("Failed to save cart. Please try again.", "woocommerce")); ?>);
$('#partial-checkout').prop('disabled', false).html(<?php echo json_encode(__("Proceed to checkout", "woocommerce")); ?>);
}
});
```
### 部署说明:
1. 将上述完整代码复制到您的 `cart.php` 文件
2. 确保之前提供的 `functions.php` 修正代码已部署
3. 清除所有缓存(WordPress、浏览器、服务器)
4. 在 Chrome DevTools 中测试:
- 检查 JavaScript 错误
- 模拟慢速网络测试超时
- 测试 AJAX 失败场景
这个完整实现解决了您报告的所有问题,包括:
- "Unexpected token '<'" 错误
- Elementor CSS 404 问题
- 部分结算流程的健壮性问题
- 购物车恢复逻辑的可靠性
###