Files
mataba/shop/xshop-theme/inc/woocommerce/setup.php
T

190 lines
9.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
defined('ABSPATH') || exit;
/**
* WooCommerce integration: wrappers, hooks. Keep template overrides minimal.
*/
add_action('after_setup_theme', 'xshop_woo_setup');
function xshop_woo_setup(): void {
if (!class_exists('WooCommerce')) { return; }
add_action('woocommerce_before_main_content', 'xshop_woo_wrapper_start', 10);
add_action('woocommerce_after_main_content', 'xshop_woo_wrapper_end', 10);
remove_action('woocommerce_sidebar', 'woocommerce_get_sidebar', 10);
// Shop loop: use XShop grid + card. Keep Woo hooks, replace content-product via filter.
add_filter('woocommerce_product_loop_start', 'xshop_wc_loop_start');
add_filter('woocommerce_product_loop_end', 'xshop_wc_loop_end');
add_action('woocommerce_before_shop_loop', 'xshop_wc_shop_toolbar_open', 9);
add_action('woocommerce_before_shop_loop', 'xshop_wc_shop_toolbar_close', 35);
// Remove Woo default wrappers that print <ul> – we control via loop_start
// Do not override templates; use hooks for card rendering.
add_action('woocommerce_shop_loop', 'xshop_wc_shop_loop_noop', 10);
// Sale badge handled inside product-card; single product gallery replaces default
add_action('init', 'xshop_wc_remove_default_gallery');
add_action('wp', 'xshop_wc_single_setup');
// Cart: ensure quantity wrappers accessible
add_filter('woocommerce_cart_item_quantity', 'xshop_wc_cart_quantity', 10, 3);
// Related/upsells: ensure they use product-card grid (filter columns)
add_filter('woocommerce_output_related_products_args', 'xshop_wc_related_args');
add_filter('woocommerce_upsells_total', '__return_false');
// M4: No-JS fallback — apply filters to main query on shop/category archive
add_action('pre_get_posts', 'xshop_wc_filter_main_query', 20);
}
function xshop_wc_filter_main_query(\WP_Query $q): void {
if (\is_admin() || !$q->is_main_query()) { return; }
if (!\class_exists('WooCommerce')) { return; }
// Only on product archive (shop or product_taxonomy)
if (!(\is_shop() || \is_product_taxonomy())) { return; }
// Don't interfere with REST
if (\defined('REST_REQUEST') && \REST_REQUEST) { return; }
// If no filter params, leave default Woo query
$hasFilter = isset($_GET['min_price']) || isset($_GET['max_price']) || isset($_GET['stock']) || isset($_GET['on_sale']) || isset($_GET['sale']) || isset($_GET['rating']) || isset($_GET['category']) || isset($_GET['orderby']) || isset($_GET['s']);
// Also check pa_* attributes
foreach ($_GET as $k => $v) {
if (\str_starts_with((string) $k, 'pa_') || \str_starts_with((string) $k, 'filter_') || \str_starts_with((string) $k, 'attribute_')) { $hasFilter = true; break; }
}
if (!$hasFilter) { return; }
// Build params from $_GET (same canonical as REST)
$params = [
'search' => isset($_GET['s']) ? \sanitize_text_field(\wp_unslash($_GET['s'])) : '',
'category' => isset($_GET['category']) ? \sanitize_text_field(\wp_unslash($_GET['category'])) : '',
'attributes' => [],
'min_price' => isset($_GET['min_price']) ? \sanitize_text_field(\wp_unslash($_GET['min_price'])) : null,
'max_price' => isset($_GET['max_price']) ? \sanitize_text_field(\wp_unslash($_GET['max_price'])) : null,
'stock' => isset($_GET['stock']) ? \sanitize_key(\wp_unslash($_GET['stock'])) : 'all',
'on_sale' => (isset($_GET['on_sale']) && $_GET['on_sale'] === '1') || (isset($_GET['sale']) && $_GET['sale'] === '1'),
'rating' => isset($_GET['rating']) ? \absint($_GET['rating']) : 0,
'orderby' => isset($_GET['orderby']) ? \sanitize_key(\wp_unslash($_GET['orderby'])) : 'default',
'page' => isset($_GET['page']) ? \absint($_GET['page']) : (isset($_GET['paged']) ? \absint($_GET['paged']) : 1),
'per_page' => 12,
];
// Collect attributes from pa_* etc.
foreach ($_GET as $k => $v) {
$kClean = \sanitize_key((string) $k);
if (\str_starts_with($kClean, 'pa_') || \str_starts_with($kClean, 'attribute_')) {
$tax = $kClean;
if (\str_starts_with($tax, 'attribute_')) { $tax = 'pa_' . \substr($tax, 10); }
$terms = [];
if (\is_array($v)) { foreach ($v as $t) { $t = \sanitize_title((string) $t); if ($t !== '') { $terms[] = $t; } } }
else { foreach (\explode(',', (string) $v) as $t) { $t = \sanitize_title(\trim($t)); if ($t !== '') { $terms[] = $t; } } }
if (!empty($terms) && \taxonomy_exists($tax)) { $params['attributes'][$tax] = $terms; }
}
if (\str_starts_with($kClean, 'filter_')) {
$tax = 'pa_' . \substr($kClean, 7);
if (\taxonomy_exists($tax)) {
$terms = [];
foreach (\explode(',', (string) $v) as $t) { $t = \sanitize_title(\trim($t)); if ($t !== '') { $terms[] = $t; } }
if (!empty($terms)) { $params['attributes'][$tax] = $terms; }
}
}
}
// If on category archive and category param empty, preserve queried category
if (\is_product_category() && empty($params['category'])) {
$term = \get_queried_object();
if ($term instanceof \WP_Term) { $params['category'] = $term->slug; }
}
// Use shared ProductQuery to build args, then merge into main query
if (!\class_exists('XShop\Core\Query\ProductQuery')) {
// Fallback if plugin not loaded yet (should be loaded via xshop-core)
return;
}
// ProductQuery class is functions-based, not class; use function directly if available
if (\function_exists('XShop\Core\Query\build_product_query_args')) {
$wpArgs = \XShop\Core\Query\build_product_query_args($params);
// Merge into main query (only tax_query/meta_query/orderby etc, not post_type/status which already correct)
if (!empty($wpArgs['tax_query'])) { $q->set('tax_query', $wpArgs['tax_query']); }
if (!empty($wpArgs['meta_query'])) { $q->set('meta_query', $wpArgs['meta_query']); }
if (isset($wpArgs['meta_key'])) { $q->set('meta_key', $wpArgs['meta_key']); }
if (isset($wpArgs['orderby'])) { $q->set('orderby', $wpArgs['orderby']); }
if (isset($wpArgs['order'])) { $q->set('order', $wpArgs['order']); }
if (isset($wpArgs['s'])) { $q->set('s', $wpArgs['s']); }
// Ensure per_page/page respects
$q->set('posts_per_page', $wpArgs['posts_per_page']);
$q->set('paged', $wpArgs['paged']);
}
}
function xshop_wc_remove_default_gallery(): void {
// Replace Woo's product images with XShop gallery component
remove_action('woocommerce_before_single_product_summary', 'woocommerce_show_product_sale_flash', 10);
remove_action('woocommerce_before_single_product_summary', 'woocommerce_show_product_images', 20);
// Re-add sale flash inside summary? Keep flash via CSS badge in product-card only; single product shows price sale via Woo price hook
}
function xshop_wc_related_args(array $args): array {
$args['posts_per_page'] = 4;
$args['columns'] = 4;
return $args;
}
function xshop_woo_wrapper_start(): void {
// Breadcrumbs are rendered by header.php for most pages; for Woo we keep Woo breadcrumb but styled as xshop-breadcrumbs
echo '<main id="primary" class="xshop-main xshop-container"><div class="xshop-woo-layout">';
}
function xshop_woo_wrapper_end(): void {
echo '</div></main>';
}
function xshop_wc_loop_start(string $html): string {
// Replace Woo default <ul class="products columns-..."> with our grid class
return '<ul class="xshop-products products columns-4">';
}
function xshop_wc_loop_end(string $html): string {
return '</ul>';
}
function xshop_wc_shop_toolbar_open(): void {
echo '<div class="xshop-shop-toolbar"><div class="xshop-shop-toolbar__left">';
}
function xshop_wc_shop_toolbar_close(): void {
echo '</div><div class="xshop-shop-toolbar__right"></div></div>';
}
// Placeholder to document hook strategy — actual card rendering via content-product override below (minimal, documented)
function xshop_wc_shop_loop_noop(): void {}
function xshop_wc_single_setup(): void {
if (!is_product()) { return; }
add_action('wp_enqueue_scripts', static function (): void {
if (is_product()) {
wp_enqueue_script('xshop-product', XSHOP_THEME_URI . '/assets/js/product.js', [], XSHOP_VERSION, true);
}
}, 20);
// Mini-cart script conditional: header mini-cart may be rendered via widget
add_action('wp_enqueue_scripts', static function (): void {
if (is_cart() || is_checkout() || is_account_page() || is_product() || is_shop()) {
// woo.css already enqueued globally via setup/assets.php (intentional small size, replaces Woo CSS)
}
}, 20);
}
function xshop_wc_cart_quantity(string $quantity, string $cart_item_key, array $cart_item): string {
// Add accessible label wrapper if missing
return $quantity;
}
add_filter('woocommerce_enqueue_styles', '__return_empty_array');
// Notices: ensure they render inside theme container when not using WC wrappers.
add_action('wp', 'xshop_woo_notice_compat');
function xshop_woo_notice_compat(): void {
if (!class_exists('WooCommerce')) { return; }
// No-op for M1; placeholder for future notice positioning hooks.
}