feat: implement WooCommerce AJAX product filtering

This commit is contained in:
XShop
2026-09-13 20:31:20 +03:30
parent f5c283af25
commit b78e332966
15 changed files with 1543 additions and 150 deletions
+11
View File
@@ -34,4 +34,15 @@ All notable changes to XShop follow [Keep a Changelog](https://keepachangelog.co
- Performance: debounce, bounded, stale cancel, dedupe, `Cache-Control`, minimal payload, documented in `PERFORMANCE.md`.
- Runtime: endpoint Persian/English/SKU/no-result/1-char/empty/xss/limit, Woo on/off fallback to posts, desktop/mobile/RTL/dark, no stale overwrite, `debug.log` clean.
- Docs: `ARCHITECTURE.md:7` `UI-SPEC.md:3,8` `SECURITY.md:11` `PERFORMANCE.md:8` `TESTING.md:M3` `RUNTIME-QA.md:M3`.
- **M4 — WooCommerce AJAX Product Filtering (REST-first, shared ProductQuery)**:
- Query: `xshop-core/includes/Query/ProductQuery.php:1` — `sanitize_search_param` `map_orderby` `build_product_query_args` (search/category/pa_* attributes/price/stock/sale/rating/orderby/page/per_page → `tax_query` + `meta_query` + `orderby` + `Cache-Control`), `format_product` `execute_query`, no raw SQL, no N+1, HPOS-safe.
- Endpoint `GET /wp-json/xshop/v1/products` (`REST/Products.php:1`, `Core/Plugin.php`) — public, `search` max 100, `category` slug/ID, dynamic `pa_*` (only `taxonomy_exists`), `min/max_price` 0..99M + `min<=max` 400, `stock all/instock/outofstock`, `on_sale 0/1` via `_sale_price OR _min_variation_sale_price`, `rating 0..5`, `orderby` allow-list, `page 1..100` `per_page 1..24` default 12, response `{items:[id,title,url,image,price_html,type,in_stock], pagination:{page,per_page,total,total_pages}}` + `public, max-age=30`, M3 refactored to use `Query`.
- UI: `template-parts/components/product-filters.php:1` — `data-xshop-filters` + `chips` + `form method=get` no-JS fallback (category radio, `pa_*` checkbox, price number, stock radio, sale checkbox, rating radio, orderby select, `Apply/Clear all`, hidden `s`), `woocommerce/archive-product.php:1` — `xshop-shop` layout `sidebar 280px|1fr` + `drawer dialog` + `overlay` + `toolbar` + `grid` + `chips` + `loading/error`.
- JS: `assets/js/filter.js:1` (8KB) — `parseStateFromURL` `stateToParams` `stateToEndpoint` `renderProductCard` (reuses `product-card` structure), `renderChips` + `Clear all`, `URLSearchParams` + `history.pushState` + `popstate` back/forward, `debounce 300ms` for price, `AbortController` + `seq` stale guard, `click outside` `Escape` drawer `focus trap` + `aria-expanded/controls` `role=dialog` `aria-modal`.
- Style: `assets/css/woo.css:1` — `.xshop-shop__layout` responsive, `.xshop-chip`, `.xshop-filter` card, `.xshop-drawer` fixed, RTL via `inset-inline`, dark via tokens.
- Asset: `inc/setup/assets.php:1` — `filter.js` front-end with DOM guard (`data-xshop-filters`), versioned, `woo.css` global intentional.
- Server fallback: `inc/woocommerce/setup.php:1` `pre_get_posts` `xshop_wc_filter_main_query` — `ProductQuery::build_product_query_args` → `tax_query/meta_query` for no-JS `?stock=instock` etc.
- Security: strict sanitization/validation, bounded, `taxonomy_exists` allow-list, `post_status publish` only, `post__in` for sale via Woo lookup, no private data, `permission_callback __return_true` public read-only, documented.
- Performance: bounded `per_page 24`, `no_found_rows` `fields ids` for search, `Cache-Control`, debounce, stale cancel, dedupe, minimal payload.
- Runtime: direct REST `category/price/stock/sale/rating/price_asc/pagination/search+filter` all PASS, server `?stock=instock` PASS, browser E2E (Playwright) category/sale/price/stock/sort/pagination/rapid/back/forward/drawer/search+filter all PASS (minor duplicate form fix), `debug.log` clean.