=== All The Parts Catalog ===
Contributors: alltheparts
Tags: partseurope, catalog, woocommerce, widget
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 8.0
Stable tag: 1.16.3
License: GPLv2 or later

Embeds the All The Parts catalog widget (PartsEurope parts) with WooCommerce cart integration.

== Description ==

Adds [partseurope] shortcode to embed the PartsEurope product catalog.
Works with WooCommerce for cart/checkout integration.

== Usage ==

[partseurope]              - The catalogue. Place it on one page; every /catalog/... address is served by it.
[partseurope cat="tires-and-tubes" title="Гуми"] - A store: one or more branches of Parts Europe's tree
                             (comma-separated paths), at the page's own address (/gumi/, /gumi/category/...)
[partseurope lang="en"]    - English interface
[partseurope_carousel cat="helmets-and-apparel/helmets" source="category"] - A row of articles
[partseurope_search]       - A search box that opens the catalogue's search
[partseurope_vehicle_finder] - Brand / model / year, opening the catalogue for that vehicle

Addresses (as Parts Europe has them): /catalog/, /catalog/category/<path>, /catalog/search?s=...,
/catalog/product/<slug>/<article>, /catalog/brands/, /catalog/brands/<brand>,
/catalog/product-stream/<promotion>[?c=<tile>], /catalog/tire-search, /catalog/chain-kits,
/catalog/catalogs, /catalog/catalogs/<catalogue>/<edition>[/<page>].

== Changelog ==

= 1.16.3 =
* IMP: product pictures are served from the catalogue's own store, in the size each place shows them: a listing card, a thumbnail or a phone loads a smaller copy, the enlarged view the full picture.
* NEW: a discreet "© Parts Europe" notice on the product picture, in the quick view and in the enlarged view, and Parts Europe named as the picture's copyright holder in the product's structured data - Parts Europe's terms ask for it wherever its pictures are shown.

= 1.16.2 =
* FIX: [partseurope_mega_menu] works in every copy on a page. A page that carries the menu twice (a header dropdown and a phone side panel) had a working menu only in the first: in the second, a tap on a group opened its page at once, or did nothing for the search tools, promotions and brands.

= 1.16.1 =
* FIX: [partseurope_mega_menu] takes the whole width of the place it is put in. In a header dropdown that sizes its content to fit, it had none: the groups stood one word per line and the tiles did not show.
* FIX: the menu keeps its own look under any theme: a theme's style for buttons (capitals, letter spacing, height) no longer reaches the group names.

= 1.16.0 =
* NEW: [partseurope_mega_menu] - the shop's category menu as Parts Europe's: its six top categories in its order, each opening its second level as tiles with Parts Europe's pictures, then the search tools (tire search, chain kits), the promotions and the brands and catalogues. Every link opens the catalogue at its own address. Built on the server from the catalogue and refreshed hourly.

= 1.15.0 =
* NEW: Parts Europe's printed catalogues at /catalog/catalogs: every catalogue Parts Europe lists, with its cover, pages and markets, filtered by keywords, market and brand. /catalog/catalogs/<catalogue>/<edition>[/<page>] reads one page by page, with the articles printed on the page beside it; the product page opens its catalogue at that page. Each carries its own title and canonical; an unknown catalogue or page answers 404.

= 1.14.0 =
* NEW: Parts Europe's tire search at /catalog/tire-search: by size (width, aspect ratio, construction, rim, load and speed index, brand), by the size printed on the tire, or the tires that fit your bike - over the tires listing, each field counting what the others leave.
* NEW: Parts Europe's chain kit wizard at /catalog/chain-kits: choose the vehicle, see its stock final drive and the kit, chains and front and rear sprockets that go with it.
* NEW: Parts Europe's Vehicle Search: brand, engine size, model or sales name and year in any order, or HSN and TSN, with keywords and a table of vehicles; opened from the vehicle card of the filters.

= 1.13.0 =
* NEW: Parts Europe's brands: /catalog/brands/ lists the house brands and every brand with its logo; /catalog/brands/<brand> is that brand's listing, with its categories and filters. An article links its brand's page. Each carries its own title and canonical; an unknown brand answers 404.
* NEW: Parts Europe's promotions: /catalog/product-stream/<promotion> shows its banner and its category tiles; each tile (?c=<tile>) lists what the promotion holds in that category.

= 1.12.0 =
* NEW: The catalogue lives at real addresses on this site, shaped as Parts Europe's: /catalog/category/<path>, /catalog/search?s=..., /catalog/product/<name>/<article>. Every address can be shared and bookmarked, and search engines can read it.
* NEW: Each address carries its own title, description and canonical (through Yoast SEO when it is active); an article also carries its schema.org Product. An address that names no article or category answers 404.
* NEW: Stores: [partseurope cat="<PE path>[,<PE path>...]" title="..."] makes any page a store of those branches, with the same addresses under the page's own (/gumi/category/...). An article opens at its one address under /catalog/.
* NEW: Cart rows link back to the article's own address.
* NEW: Administrator-only endpoints to snapshot pages and blocks, write a snapshot back and install an exact release (also in 1.10.2) - the way back from this release.
* REMOVED: The old per-store shortcodes ([partseurope_tires] and the like), the pe_* query parameters and the #/ links. Carousels take cat="<PE path>".

= 1.10.2 =
* NEW: Administrator-only endpoints to take an exact snapshot of pages and blocks, write one back, and install an exact release of this plugin - the way back from any later change. Nothing else differs from 1.10.1.

= 1.10.1 =
* FIX: Product pages tell search engines the price the shopper pays, VAT included.

= 1.10.0 =
* The one-off sync-plugin removal action from 1.9.1 is gone: it has done its job.
* Wording: every notice and the update feed name the plugin All The Parts Catalog.

= 1.9.1 =
* Adds a one-off admin action that removes the retired sync plugin safely: it deactivates it, deletes only its own settings and product markers by exact name, and deletes its folder without running its uninstall routine (that routine would have deleted unrelated product data).

= 1.9.0 =
* MOVE: The catalog platform now lives at catalog.alltheparts.eu. The widget, the catalog API and this plugin's updates load from there.
* NEW: The platform address is a setting: the pe_platform_host option, or PE_HOST in wp-config.php. A future move needs no plugin release.
* Renamed to All The Parts Catalog.

= 1.8.3 =
* FIX: On a Bulgarian storefront the coupon rejection now shows in Bulgarian. WooCommerce's Store API returns coupon errors in English on this site (a site-wide WC translation gap — even "coupon does not exist" comes back English), so the message our scoped coupons trigger ("not applicable to selected products") is localized via WC's own coupon-error filter. The broad fix for all WC messages is to install the WooCommerce Bulgarian translation (Dashboard → Updates → Update translations).
* NEW: Live admin warning — selecting "Fixed cart discount" while a PartsEurope scope is set now shows an inline warning that the scope will be ignored (fixed-cart is whole-cart by WooCommerce design). Use Percentage or Fixed product discount for a scoped discount.

= 1.8.2 =
* FIX: Removed the custom "no matching product" coupon message — WooCommerce already rejects a Percentage / Fixed-product coupon when no cart line is in scope, using its own storefront-localized message, so the custom one only duplicated it and leaked the admin's language onto the storefront. WC now manages that message.
* Panel note clarified: the PE scope applies to "Percentage discount" and "Fixed product discount" only. "Fixed cart discount" ignores the scope and discounts the whole order (it is a whole-cart type by WooCommerce design) — use Percentage or Fixed product for a scoped discount.

= 1.8.1 =
* FIX: The "PartsEurope scope" coupon panel now follows the WordPress admin language. Its labels were authored as Bulgarian source strings, so an English-language admin still saw Bulgarian in Marketing → Coupons. They now render English for an English admin and Bulgarian for a Bulgarian admin (via the user's WP locale). The customer-facing "no matching product" message follows the storefront locale too.

= 1.8.0 =
* NEW: Promo codes scoped to a PartsEurope category, sub-category, brand or specific SKUs. The coupon edit screen (Marketing → Coupons → "Usage restriction" tab) gains a "PartsEurope scope" panel with dropdowns fed from the live catalog. Empty = no restriction; several fields = intersection (e.g. brand + category). The coupon discounts only the matching cart lines, resolved server-side by SKU — a client-sent price or scope is never trusted.
* Native WooCommerce stays the backbone: code, percentage/fixed amount, usage limits (total + per-customer), expiry, min/max spend and free shipping all work as before. The scope is an extra layer on top.
* Works for "Percentage discount" and "Fixed product discount" (WooCommerce evaluates these per cart line); "Fixed cart discount" applies to the whole order and ignores the scope.
* Supports both the classic cart shortcode and the Cart/Checkout Blocks (Store API uses the same WC_Discounts path).
* Clean rejection: a coupon with no matching product in the cart is rejected with a clear message instead of applying a zero discount.
* Tier-agnostic architecture — the matching core is reused as-is by a future external Promo Manager (Tier B). Plan: plans/2026-06-13-promo-codes-discounts.md

= 1.7.0 =
* MAJOR: replaced per-SKU WooCommerce product creation with single "PE Part" placeholder + cart_item_data. Eliminates catalog pollution (formerly created 1 hidden WC_Product_Simple per unique SKU ever added to a cart, plus a sideloaded attachment per product — scaled poorly). Now: 1 placeholder product + live server-side price/weight lookup keyed by PE SKU.
* Worth-it workarounds bundled: shipping weight hook (woocommerce_cart_contents_weight) so Econt/Speedy etc still price cart correctly; 10-min transient cache on catalog-api price lookups with 3s HTTP timeout; idempotent placeholder creation; backwards-compat — legacy ensure-product endpoint still works if an older widget is loaded.
* Widget: addToCart now POSTs directly to /wc/store/v1/cart/add-item with { id: placeholderId, extensions: { pe: {sku,name,price,image_url} } } — one less HTTP roundtrip per click.
* Pattern docs: plans/2026-04-21-cart-placeholder-migration.md

= 1.6.1 =
* FIX: cart-only PE products no longer auto-assigned to default WC category ("Accessories"). Added wp_set_object_terms([], 'product_cat') after save + self-heal on existing products.

= 1.6.0 =
* purge-scan + purge-execute now match products in numeric-slug categories (01, 0101, 10, 48...) as well as _pe_* meta — catches sync-plugin products that lacked markers. Also deletes the numeric-slug categories themselves.

= 1.5.8 =
* NEW: REST endpoints /pe/v1/purge-scan (GET) + /pe/v1/purge-execute (POST, confirm-phrase required) to bulk-delete all PE products from WC catalog — covers both sync-plugin-created products and widget/cart-created ones. Also removes orphaned sideloaded attachments. Admin-only (manage_options).

= 1.5.7 =
* FIX: Product images now show in WooCommerce cart, mini-cart, checkout, and order emails. Previously we only stored the external PE image URL as meta, never creating a WP attachment — so WC's native image renderer fell back to the grey placeholder. Plugin now sideloads the image into the WP media library at ensure-product time and sets it as the product's featured image.
* Self-heal: products created by v1.5.6 and earlier that have no featured image get their image sideloaded on next ensure-product call (e.g. when anyone re-adds that SKU to cart).
* Added woocommerce_product_get_image filter as a classic-cart fallback in case sideload fails (uses the stored _pe_image_url meta directly).

= 1.5.6 =
* FIX (critical): Add-to-cart was broken for all products. pe_ensure_product() created WooCommerce products with status=private, which WC Store API rejects ("This item is not available for purchase"). Products now created as publish + catalog_visibility=hidden — they add to cart but stay out of native WC shop/category/search listings.
* FIX: Self-heal — existing private products from v1.5.5 and earlier are promoted to publish on next ensure-product call, so customers get working cart without manual migration.
* NEW: Cart URL + Checkout URL exposed via PEConfig; widget shows "Go to cart / Checkout" buttons after successful add, instead of auto-resetting the button.
* NEW: Plugin emits wc_fragment_refresh + added_to_cart jQuery events when widget's pe:cart-updated fires, so native WC cart icon in nav/header picks up the new item without a page reload.
* Guard: ensure-product returns 400 no_price if product has price &lt;= 0 instead of letting Store API fail cryptically later.

= 1.5.3 =
* FIX: pe_page_has_shortcode detects shortcodes inside Elementor _elementor_data so carousel on homepage (Elementor-built) works

= 1.5.1 =
* FIX: QuickView "View full details" → "Виж пълна информация" + proper catalog URL
* FIX: Description language indicator when PE ships EN/DE/FR only
* FIX: Search works for BG/DE/brand names now (uses search_tsv with unaccent)
* FIX: Analytics beacon CORS — no longer requests credentials
* Widget cache-control lowered to 60s during active development
* Carousel auto-detects catalog_url (finds page with [partseurope] shortcode)

= 1.5.0 =
* NEW: [partseurope_carousel] shortcode — embeddable product carousels with QuickView modal
* NEW: 6 category sources: manual (csv SKUs), category, brand, discount, new, fitment
* NEW: QuickView modal — image gallery, price, stock, add-to-cart, description preview, full-details link
* NEW: Keyboard nav in QuickView (Esc close, arrow keys cycle through carousel products)
* Widget-level: simplified filters (removed warehouse status/region/alerts from end-user UI, kept in API)
* Widget-level: time-framed availability labels ("1-3 дни" / "14-20 дни")
* Widget-level: L1 mega-category grouping (Helmets & Apparel / Parts & Accessories / Tires & Tubes / Tools & Chemicals / Merchandising / Bicycle)
* Widget-level: renamed 15 misleading category names per CATEGORY_ANALYSIS.md

= 1.4.1 =
* Renamed the plugin display name.
* Add "Check for updates" link in plugins list row

= 1.0.0 =
* Initial release
