From ed66c96d3d6f9888371654138ad4c1d3eb9298eb Mon Sep 17 00:00:00 2001 From: magdev Date: Sun, 1 Mar 2026 12:14:07 +0100 Subject: [PATCH] Consolidate documentation, bump to v1.2.1 Condense CLAUDE.md from ~1960 to ~160 lines keeping only essential architecture and lessons learned. Merge INSTALL.md into README.md and IMPLEMENTATION.md into CLAUDE.md, then delete the source files. Co-Authored-By: Claude Opus 4.6 --- .gitignore | 7 +- CHANGELOG.md | 14 + CLAUDE.md | 1976 ++----------------------------------- IMPLEMENTATION.md | 434 -------- INSTALL.md | 150 --- README.md | 107 +- wc-composable-product.php | 4 +- 7 files changed, 201 insertions(+), 2491 deletions(-) delete mode 100644 IMPLEMENTATION.md delete mode 100644 INSTALL.md diff --git a/.gitignore b/.gitignore index 6878914..8bb3ff0 100755 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,8 @@ -# Linked sources -wp-core -wp-plugins -tpp - # Editor swap files *.*swp # Composer vendor/ -composer.lock # Cache cache/ @@ -22,6 +16,7 @@ logs/ # OS files .DS_Store Thumbs.db +.directory # Binary files languages/*.mo diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6f57a..edd11f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] - 2026-03-01 + +### Changed + +- Consolidated documentation: merged INSTALL.md into README.md, merged IMPLEMENTATION.md into CLAUDE.md +- Condensed CLAUDE.md from ~1960 lines to ~160 lines, keeping only essential architecture and lessons learned +- README.md now includes full installation guide, usage tutorial, and troubleshooting section +- Cleaned up .gitignore + +### Removed + +- INSTALL.md (content merged into README.md) +- IMPLEMENTATION.md (content merged into CLAUDE.md) + ## [1.2.0] - 2026-03-01 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index aa1272b..286a9ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,107 +1,31 @@ -# WooCommerce plugin for user composable products - AI Context Document +# WooCommerce Composable Products - AI Context Document **Author:** Marco Graetsch ## Project Overview -This plugin implements a special product type, for which users can select a limited number of product from a configurable set of simple or variable products. The limit of selectable products should be a global and per-product setting, for which global is the fallback. The set of selectable products can be defined per category, tag or SKU. The price is either a fixed price or the sum of the prices of the selected products. Think of a package of stickers as composable product, where each package can contain $limit number of stickers. +This plugin implements a custom WooCommerce product type where customers select a limited number of products from a configurable set of simple or variable products. The limit is configurable globally and per-product. The selectable products are defined by category, tag, or SKU. Pricing is either fixed or the sum of selected products. Think of a sticker pack where each package contains N stickers chosen by the customer. -### Key Fact: 100% AI-Generated - -This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance. +This project is 100% AI-generated ("vibe-coded") using Claude.AI. ## Technical Stack - **Language:** PHP 8.3+ -- **Framework:** Latest WordPress Plugin API +- **Framework:** WordPress Plugin API - **E-commerce:** WooCommerce 10.0+ - **Template Engine:** Twig 3.0 (via Composer) - **Frontend:** Vanilla JavaScript + jQuery - **Styling:** Custom CSS -- **Dependency Management:** Composer -- **Internationalization:** WordPress i18n (.pot/.po/.mo files) - -## Implementation Details - -### Security Best Practices - -- All user inputs are sanitized (integers for quantities/prices) -- Nonce verification on form submissions -- Output escaping in templates (`esc_attr`, `esc_html`, `esc_js`) -- Direct file access prevention via `ABSPATH` check - -### Translation Ready - -All user-facing strings use: - -```php -__('Text to translate', 'wc-composable-product') -_e('Text to translate', 'wc-composable-product') -``` - -Text domain: `wc-composable-product` - -**Translation Template:** - -- Base `.pot` file created: `languages/wc-composable-product.pot` -- Ready for translation to any locale -- All translatable strings properly marked with text domain - -**Available Translations:** - -- `en_US` - English (United States) [base language - .pot template] -- `de_DE` - German (Germany, formal) ✓ Complete -- `de_DE_informal` - German (Germany, informal "du") ✓ Complete -- `de_CH` - German (Switzerland, formal "Sie") ✓ Complete -- `de_CH_informal` - German (Switzerland, informal "du") ✓ Complete -- `fr_CH` - French (Switzerland) ✓ Complete -- `it_CH` - Italian (Switzerland) ✓ Complete - -All .po files created with 40+ translated strings. Swiss locales include CHF currency formatting in examples (e.g., "CHF 50.-"). - -To compile translations to .mo files for production: - -```bash -for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done -``` - -### Create releases - -- The `vendor/` directory MUST be included in releases (Twig dependency required for runtime) -- Running zip from wrong directory creates empty or malformed archives -- Exclusion patterns must match the relative path structure used in zip command -- Always verify the package with `unzip -l` and test extraction before committing -- The `wp-core/` and `wp-plugins/` directories MUST NOT be included in releases -- Releases are stored in `releases/` including checksums - -**Important Git Notes:** - -- Always commit from `dev` branch first -- Tags should use format `vX.X.X` (e.g., `v1.1.22`) -- Use annotated tags (`-a`) not lightweight tags -- Commit messages should follow the established format with Claude Code attribution -- `.claude/settings.local.json` changes are typically local-only (stash before rebasing) - -#### What Gets Released - -- All plugin source files -- Compiled vendor dependencies -- Translation files (.mo compiled from .po) -- Assets (CSS, JS) -- Documentation (README, CHANGELOG, etc.) - -#### What's Excluded - -- Git metadata (`.git/`) -- Development files (`.vscode/`, `.claude/`, `CLAUDE.md`) -- Logs and cache files -- Previous releases -- `composer.lock` (but `vendor/` is included) +- **Dependencies:** Composer +- **i18n:** WordPress i18n (.pot/.po/.mo), text domain: `wc-composable-product` +- **CI/CD:** Gitea Actions (`.gitea/workflows/release.yml`) ## Project Structure ```txt wc-composable-product/ +├── .gitea/workflows/ +│ └── release.yml # CI/CD release workflow ├── assets/ │ ├── css/ │ │ ├── admin.css # Admin panel styling @@ -109,7 +33,7 @@ wc-composable-product/ │ └── js/ │ ├── admin.js # Product edit interface logic │ └── frontend.js # AJAX cart & selection UI -├── cache/ # Twig template cache (writable) +├── cache/ # Twig template cache (writable, gitignored) ├── includes/ │ ├── Admin/ │ │ ├── Product_Data.php # Product data tab & meta boxes @@ -118,1841 +42,145 @@ wc-composable-product/ │ ├── Plugin.php # Main plugin class (Singleton) │ ├── Product_Selector.php # Frontend product selector renderer (with stock info) │ ├── Product_Type.php # Custom WC_Product extension -│ └── Stock_Manager.php # Stock management & inventory tracking (v1.1.0+) -├── languages/ -│ └── wc-composable-product.pot # Translation template -├── releases/ # Releases files +│ └── Stock_Manager.php # Stock management & inventory tracking +├── languages/ # Translation files (.pot, .po, .mo) +├── releases/ # Release packages (gitignored) ├── templates/ -│ └── product-selector.twig # Frontend selection interface (with stock display) -├── vendor/ # Composer dependencies (gitignored) -├── composer.json # Dependency configuration -├── wc-composable-product.php # Main plugin file -└── [Documentation files] # README, INSTALL, IMPLEMENTATION, etc. +│ └── product-selector.twig # Frontend selection interface +├── vendor/ # Composer dependencies (gitignored, included in releases) +├── composer.json +└── wc-composable-product.php # Main plugin file ``` -## Architecture Overview +## Architecture ### Core Classes -1. **Plugin.php** - Main singleton class - - Initializes Twig template engine - - Registers hooks and filters - - Manages asset enqueuing - - Provides template rendering API +1. **Plugin.php** — Main singleton class + - Initializes Twig with WordPress functions registered as both Twig functions AND filters + - Registers hooks, manages asset enqueuing, provides template rendering API + - Settings.php is lazy-loaded via `woocommerce_get_settings_pages` filter (not in `includes()`) to avoid "Class WC_Settings_Page not found" errors -2. **Product_Type.php** - Custom WooCommerce product type +2. **Product_Type.php** — Custom WooCommerce product type (`composable`) - Extends `WC_Product` - - Handles selection criteria (category/tag/SKU) - - Manages pricing modes (fixed/sum) - - Queries available products dynamically + - Queries available products via `get_available_products()` using `WP_Query` + - **Critical**: Uses `tax_query` with `product_type` taxonomy to exclude composable products (NOT `meta_query` — WooCommerce stores product types as taxonomy terms) + - Handles variable products by expanding them into individual variations via `get_children()` + - Products are filtered by `is_purchasable()` only (not `is_in_stock()` — stock is shown visually and validated at add-to-cart) -3. **Cart_Handler.php** - Cart integration - - Validates product selections - - Stores selected products in cart meta - - Calculates dynamic pricing - - Displays selections in cart/checkout +3. **Cart_Handler.php** — Cart integration + - Validates selections, stores selected products in cart meta, calculates pricing + - Uses `woocommerce_is_purchasable` filter to hide default add-to-cart button for composable products + - Price recalculation uses a static `$already_calculated` flag per request (no persistent session flags — `set_price()` is in-memory only) -4. **Product_Selector.php** - Frontend renderer - - Renders Twig template with product data - - Applies display settings (images/prices/total) +4. **Product_Selector.php** — Frontend renderer + - Renders Twig template with product data, stock info, and pre-formatted price HTML via `wc_price()` -5. **Admin/Product_Data.php** - Product edit interface - - Adds "Composable Options" tab - - Category/tag/SKU selection fields - - Saves product metadata +5. **Admin/Product_Data.php** — Product edit interface + - Adds "Composable Options" tab with category/tag/SKU selection fields + - Saved meta: `_composable_selection_limit`, `_composable_pricing_mode`, `_composable_criteria_type`, `_composable_categories`, `_composable_tags`, `_composable_skus` -6. **Admin/Settings.php** - Global settings - - WooCommerce settings tab integration - - Default limits and pricing mode - - Display preferences +6. **Admin/Settings.php** — Global settings (extends `WC_Settings_Page`) + - Default selection limit, pricing mode, display preferences -7. **Stock_Manager.php** - Inventory management (v1.1.0+) - - Stock validation for selected products - - Automatic stock deduction on order completion - - Stock restoration on order cancellation/refund - - Order notes for audit trail - - Backorder support detection +7. **Stock_Manager.php** — Inventory management + - Stock validation, automatic deduction on order completion, restoration on cancellation + - Prevents WooCommerce double-deduction via `woocommerce_can_reduce_order_stock` ### Data Flow -**Product Creation:** +**Product Creation:** Admin selects "Composable product" type → configures criteria/limits/pricing → metadata saved as `_composable_*` fields -1. Admin selects "Composable product" type -2. Configures criteria, limits, pricing in product data tab -3. Metadata saved: `_composable_*` fields +**Frontend Display:** `Cart_Handler::render_product_selector()` → `Product_Type::get_available_products()` queries products via taxonomy/SKU → `Product_Selector::render()` passes data to Twig template → JavaScript handles selection UI -**Frontend Display:** +**Add to Cart:** Customer selects products → JS validates limit → AJAX request with `composable_products[]` → server-side validation (selection + stock) → selections stored in cart item data → price calculated per pricing mode -1. `Cart_Handler::render_product_selector()` called on product page -2. `Product_Type::get_available_products()` queries matching products -3. `Product_Selector::render()` passes data to Twig template -4. JavaScript handles selection UI and AJAX +**Order Processing:** Order completed → `Stock_Manager` deducts inventory → order notes added for audit → on cancellation/refund: stock restored -**Add to Cart:** +### Key Hooks -1. Customer selects products (JS validates limit) -2. AJAX request with `composable_products[]` array -3. `Cart_Handler::validate_add_to_cart()` server-side validation -4. `Stock_Manager::validate_stock_availability()` checks stock levels (v1.1.0+) -5. `Cart_Handler::add_cart_item_data()` stores selections -6. `Cart_Handler::calculate_cart_item_price()` applies pricing +- `woocommerce_add_to_cart_validation` — validate selections +- `woocommerce_add_cart_item_data` — store selections +- `woocommerce_before_calculate_totals` — update prices +- `woocommerce_get_item_data` — display in cart +- `woocommerce_order_status_completed/processing` — deduct stock +- `woocommerce_order_status_cancelled/refunded` — restore stock -**Order Processing (v1.1.0+):** +### Security -1. Order status changes to completed/processing -2. `Stock_Manager::reduce_stock_on_order_complete()` deducts inventory -3. Selected product IDs stored in order meta: `_composable_products` -4. Order notes added documenting stock changes -5. On cancellation/refund: `Stock_Manager::restore_stock_on_order_cancel()` reverses deduction +- Input: `absint()` for IDs/limits, `sanitize_text_field()` for modes, `sanitize_textarea_field()` for SKUs +- Output: `esc_html()`, `esc_attr()`, `esc_url()` (registered as both Twig functions and filters) +- Nonce verification via WooCommerce -## Development Workflow +### Developer API -### Initial Setup - -```bash -composer install +```php +$product = wc_get_product($product_id); +if ($product->get_type() === 'composable') { + $products = $product->get_available_products(); + $limit = $product->get_selection_limit(); + $price = $product->calculate_composed_price($selected_ids); +} ``` -### Making Changes +## Translations -1. **PHP Classes:** Edit files in `includes/` or `includes/Admin/` -2. **Templates:** Modify `templates/*.twig` (cache clears on auto-reload) -3. **Styles:** Update `assets/css/*.css` -4. **JavaScript:** Edit `assets/js/*.js` -5. **Translations:** Update `.pot` file, create `.po` translations +All strings use text domain `wc-composable-product`. Available locales: -### Testing Checklist +- `en_US` (base), `de_DE`, `de_DE_informal`, `de_CH`, `de_CH_informal`, `fr_CH`, `it_CH` -- [ ] Create composable product in admin -- [ ] Test each selection criteria type (category/tag/SKU) -- [ ] Verify selection limit enforcement -- [ ] Test both pricing modes (fixed/sum) -- [ ] Check AJAX add-to-cart functionality -- [ ] Verify cart display shows selected products -- [ ] Test checkout process -- [ ] Check responsive design on mobile -- [ ] Validate all strings are translatable +Compile .po to .mo: `for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done` -### Creating Releases +WordPress requires compiled .mo files — .po files alone are insufficient. + +## Release Workflow + +### Automated (Gitea CI/CD) + +Push an annotated tag (`v*`) to trigger the workflow. It installs PHP 8.3, production Composer deps, compiles translations, verifies version matches tag, creates ZIP with checksums, and publishes a Gitea release. + +### Manual ```bash # From project root -zip -r wc-composable-product-vX.X.X.zip . \ +zip -r releases/wc-composable-product-vX.X.X.zip . \ -x "*.git*" "*.vscode*" "*.claude*" "CLAUDE.md" \ - "wp-core" "wp-plugins" "*.log" "composer.lock" \ - "cache/*" "releases/*" "*.zip" - -# Verify contents -unzip -l wc-composable-product-vX.X.X.zip - -# IMPORTANT: Ensure vendor/ is included! + "wp-core/*" "wp-plugins/*" "*.log" "composer.lock" \ + "cache/*" "releases/*" "*.zip" "logs/*" ``` -## Bugs found +The `vendor/` directory MUST be included in releases (Twig dependency required at runtime). -- ✅ ~~There is a bug related to twig in the frontend area. Documented in `logs/fatal-errors*.log`~~ **FIXED in v1.1.5** -- ✅ ~~Translate the admin area, too~~ **COMPLETED in v1.1.6** - All admin strings now translated to 6 locales -- ✅ ~~Small rendering Bug in admin area. If you load the side, on first view it shows the first both tabs.~~ **FIXED in v1.1.8** -- ✅ ~~In the frontend, regardless which selection mode you use, there appears no product selection in any way.~~ **FIXED in v1.1.8** -- ✅ ~~The pricing field in the frontend should be rendered as localized price field include currency.~~ **FIXED in v1.1.8** -- ✅ ~~Still no product selection in frontend. Current mode 'by Category', but 'by tag' also didn't work~~ **FIXED in v1.2.0** - Root cause: meta_query checked `_product_type` in postmeta, but WooCommerce stores product types in the `product_type` taxonomy. The `!=` comparison with a non-existent meta key caused INNER JOIN returning zero results. Fixed by using correct `tax_query`. -- ✅ ~~The tab rendering is still no correct. first both tabs are shown on initial page load. After clicking a tab, they behave as expected.~~ **FIXED in v1.2.0** - JS now triggers WooCommerce's native tab click instead of manually toggling panel visibility. -- ✅ ~~Cart price always 0.00 despite correct frontend price calculation~~ **FIXED in v1.2.0** - `composable_price_calculated` session flag prevented price recalculation on subsequent page loads. +### Git Workflow -## Session History +- Develop on `dev` branch, merge to `main` for releases +- Tags: annotated, format `vX.X.X` (e.g., `v1.2.0`) +- Commit messages include `Co-Authored-By: Claude` attribution -### v1.0.0 - Initial Implementation & Release (2024-12-31) +## Critical Lessons Learned -#### Session 1: Core Implementation +1. **WooCommerce stores product types as taxonomy terms** (`product_type` taxonomy), NOT as postmeta. Using `meta_query` on `_product_type` silently returns zero results because the meta key doesn't exist. -- Complete plugin implementation from scratch -- All 6 core PHP classes with PSR-4 autoloading -- Twig template system integration -- Responsive frontend with AJAX functionality -- Admin interface with WooCommerce integration -- Full i18n support with .pot template -- Comprehensive documentation (README, INSTALL, IMPLEMENTATION) -- Initial commit to `main` branch (1edb0be) +2. **`WC_Product::set_price()` is in-memory only** — changes are lost between HTTP requests. Never persist a "price already calculated" flag to cart session; use a static per-request flag instead. -#### Session 2: Documentation & Translations +3. **Settings.php must be lazy-loaded** — `require_once` in `Plugin::includes()` causes "Class WC_Settings_Page not found" because WooCommerce hasn't loaded that class yet. Load it inside the `woocommerce_get_settings_pages` filter callback instead. -- Enhanced CLAUDE.md with complete architecture documentation -- Created 6 complete translation files (.po): - - German (Germany - formal & informal) - - German (Switzerland - formal & informal) - - French (Switzerland) - - Italian (Switzerland) -- All 40+ strings translated with locale-specific terminology -- Swiss locales include CHF currency formatting examples +4. **Register WordPress functions as both Twig functions AND filters** — other plugins may bundle their own Twig instance that parses our templates. Both `{{ esc_attr(value) }}` and `{{ value|esc_attr }}` syntax must work. -#### Session 3: Release Creation +5. **HPOS compatibility declaration is required** — without it, WooCommerce shows incompatibility warnings. -- Created annotated git tag `v1.0.0` -- Generated release package: `wc-composable-product-v1.0.0.zip` (371 KB) -- Verified vendor/ directory inclusion (336 Twig files) -- Created SHA-256 and MD5 checksums -- Stored in `releases/` directory (gitignored) +6. **WordPress i18n requires compiled .mo files** — .po files are source only; WordPress cannot use them directly. -**Key decisions made:** +7. **Don't filter by `is_in_stock()` during product retrieval** — it's too strict (excludes backorder-enabled products, products without stock management). Show all purchasable products; let the frontend display stock status and validate at add-to-cart time. -- Used Singleton pattern for main Plugin class -- Twig for templating (per requirements) -- Vanilla JS + jQuery for frontend (WooCommerce standard) -- Grid layout for product selector (responsive) -- AJAX add-to-cart for better UX -- Meta-based configuration storage +## For AI Assistants -**Files created:** 28 files total (21 PHP/templates + 7 translations), 3,842 lines of code +When starting a new session: -**Git workflow:** - -- Main branch: Initial implementation (1edb0be) -- Dev branch: +2 commits for documentation and translations -- Tagged: v1.0.0 on dev branch (8c17734) - -**What works:** - -- Product type registration ✓ -- Admin product data tab ✓ -- Category/tag/SKU selection ✓ -- Frontend product selector ✓ -- AJAX add-to-cart ✓ -- Cart integration ✓ -- Pricing calculation (both modes) ✓ -- Full multilingual support (6 locales) ✓ -- Production-ready release package ✓ - -**Known limitations:** - -- Currently only simple products in selection (not variable) -- No grouped product support -- Template cache requires manual clearing after updates -- Translations are .po files only (not compiled to .mo yet) - -**Release details:** - -- Package size: 371 KB -- Includes: All source + vendor dependencies + translations -- Checksums: SHA-256 and MD5 provided -- Ready for WordPress installation (no composer install needed) - -**Future enhancements to consider:** - -- Variable product support -- Quantity selection per item -- Visual bundle preview -- Product recommendations -- Selection presets/templates -- Compile .mo translation files - ---- - -### v1.0.1 - Bug Fix (2024-12-31) - -**Critical bug fix:** Fatal error "Class WC_Settings_Page not found" during plugin activation - -**Root cause:** Plugin initialized on `plugins_loaded` hook before WooCommerce classes were available - -**Solution:** Changed initialization hook to `woocommerce_loaded` in wc-composable-product.php:65 - -**Impact:** Settings page now correctly integrates as tab in WooCommerce > Settings - -**Files modified:** - -- wc-composable-product.php (version bump to 1.0.1, hook change) -- CHANGELOG.md (documented fix) - -**Commit:** a581ef4 - ---- - -### v1.1.0 - Stock Management Integration (2024-12-31) - -#### Session 4: Stock Management Implementation - -**Major feature release** adding comprehensive inventory tracking for composable products. - -**What was built:** - -1. **New Stock_Manager class** (includes/Stock_Manager.php - 7.7 KB, 263 lines) - - `validate_stock_availability()` - Real-time stock checking - - `get_product_stock_info()` - Stock data for frontend display - - `reduce_stock_on_order_complete()` - Automatic deduction on order completion - - `restore_stock_on_order_cancel()` - Automatic restoration on cancellation/refund - - `prevent_composable_stock_reduction()` - Prevents WooCommerce double-deduction - - `store_selected_products_in_order()` - Saves selection to order meta - -2. **Enhanced existing classes:** - - Cart_Handler.php: Added stock validation during add-to-cart (lines 90-95) - - Product_Selector.php: Passes stock data to template (lines 36-56) - - Plugin.php: Includes Stock_Manager in autoload (line 96) - -3. **Frontend enhancements:** - - templates/product-selector.twig: Stock status display (lines 39-47) - - assets/css/frontend.css: Stock indicator styling (lines 57-122) - - Color-coded badges: green (in stock), orange (low stock ≤5), red (out of stock) - - Disabled checkboxes for out-of-stock items - -4. **Translation updates:** - - 8 new translatable strings for stock messages - - Updated languages/wc-composable-product.pot - - Updated languages/wc-composable-product-it_CH.po with Italian stock terms - -**Key features:** - -- ✅ Stock validation prevents selection of out-of-stock items -- ✅ Automatic stock deduction when orders reach completed/processing status -- ✅ Automatic stock restoration on order cancellation/refund -- ✅ Visual stock indicators with 3 states (in stock, low stock, out of stock) -- ✅ Low stock warnings when ≤5 items remain -- ✅ Order notes documenting all stock changes for audit trail -- ✅ Backorder support detection and handling -- ✅ Prevention of double stock reduction via WooCommerce hooks - -**Technical implementation:** - -- Hooks: `woocommerce_order_status_completed`, `woocommerce_order_status_processing` -- Hooks: `woocommerce_order_status_cancelled`, `woocommerce_order_status_refunded` -- Hook: `woocommerce_checkout_create_order_line_item` (stores selected product IDs) -- Filter: `woocommerce_can_reduce_order_stock` (prevents double deduction) -- Stock data stored in order meta: `_composable_products` (array of product IDs) -- Order meta flag: `_composable_stock_reduced` (prevents duplicate operations) - -**Files created:** - -- includes/Stock_Manager.php (new, 263 lines) - -**Files modified:** - -- includes/Cart_Handler.php (+13 lines: stock manager integration) -- includes/Product_Selector.php (+17 lines: stock info retrieval) -- includes/Plugin.php (+1 line: Stock_Manager require) -- templates/product-selector.twig (+8 lines: stock status display) -- assets/css/frontend.css (+40 lines: stock indicator styles) -- languages/wc-composable-product.pot (+32 lines: 8 new strings) -- languages/wc-composable-product-it_CH.po (+32 lines: Italian translations) -- wc-composable-product.php (version bump to 1.1.0) -- CHANGELOG.md (v1.1.0 release notes) - -**Release details:** - -- Package size: 375 KB (+4 KB from v1.0.0) -- Git tag: v1.1.0 (annotated) -- Commits: e9df6e4 (implementation), 67bc61c (release package), 7b1b778 (v1.0.0 package), 91f44b0 (.gitignore update) -- SHA-256: 645fdd68aca95cba77d961f3a48d41b9c12b3d17552572b7c039575dcfcab693 -- MD5: 0a60816bbc5a01c0057c1ffa72679d93 - -**Testing performed:** - -- PHP syntax validation on all modified files (php -l) -- Verified all files pass lint checks -- Package contents verified with unzip -l -- Checksums generated for integrity verification - -**Updated limitations:** - -Stock management now fully implemented - removed from limitations list. - -Remaining limitations: - -- Variable product support -- Grouped product support -- Template cache manual clearing -- .mo compilation - -**What works (v1.1.0):** - -Everything from v1.0.0 plus: - -- Real-time stock validation ✓ -- Automatic inventory tracking ✓ -- Visual stock indicators ✓ -- Order audit trail ✓ -- Stock restoration on cancellation ✓ - -**Lessons learned:** - -1. **Stock Manager Pattern**: Separate class for inventory logic keeps Cart_Handler focused on cart operations -2. **Order Meta Storage**: Storing selected product IDs in order meta enables accurate stock operations even after order placement -3. **Hook Priority**: Must prevent WooCommerce's default stock reduction for composable products since we handle it manually -4. **Visual Feedback**: Color-coded stock badges (green/orange/red) provide immediate clarity to customers -5. **Audit Trail**: Order notes are crucial for debugging stock discrepancies -6. **Defensive Programming**: Check for `_composable_stock_reduced` flag to prevent duplicate operations on order status changes - ---- - -### v1.1.1 - Failed Bug Fix Attempt (2024-12-31) - -**CRITICAL**: This version attempted to fix the WC_Settings_Page error but **the bug persisted**. - -**Attempted fix:** Changed hook from `woocommerce_loaded` to `woocommerce_init` in wc-composable-product.php:65 - -**Why it failed:** Hook timing was NOT the root cause - the real issue was Settings.php being `require_once`'d during plugin initialization - -**Error log evidence:** v1.1.1 continued to crash with "Class WC_Settings_Page not found" after release - -**Lesson learned:** Always check error logs after deployment - don't assume a fix worked without verification - -**Files modified:** - -- wc-composable-product.php (version bump to 1.1.1, hook change) -- CHANGELOG.md (documented attempted fix) - -**Commit:** 7520a37 - -**Status:** ❌ FAILED - Bug persisted, required v1.1.2 - ---- - -### v1.1.2 - CRITICAL Bug Fix (2024-12-31) - -#### Session 5: Fixing Persistent Settings.php Class Loading Issue - -**CRITICAL bug fix** that finally resolved the "Class WC_Settings_Page not found" error that persisted through 4 versions (v1.0.0, v1.0.1, v1.1.0, v1.1.1). - -**The Journey to the Fix:** - -1. v1.0.0: Used `plugins_loaded` hook → Fatal error -2. v1.0.1: Changed to `woocommerce_loaded` → Still failed -3. v1.1.0: Kept `woocommerce_loaded` → Bug continued -4. v1.1.1: Changed to `woocommerce_init` → **STILL FAILING!** -5. v1.1.2: Fixed class loading order → ✅ **WORKING** - -**Root cause analysis:** - -The error wasn't about hook timing - it was about **when Settings.php was being parsed**: - -- `Plugin::includes()` was doing `require_once Settings.php` at line 93 -- This happened during plugin initialization (on `woocommerce_init`) -- When PHP parsed Settings.php, it tried to extend `WC_Settings_Page` -- But that parent class didn't exist yet! -- Even `woocommerce_init` fires **before** WooCommerce loads settings page classes -- Result: Instant fatal error - -**The fix:** - -Delayed Settings.php inclusion until it's actually needed: - -```php -// Plugin::includes() - REMOVED this line: -// require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Settings.php'; - -// Plugin::add_settings_page() - ADDED this: -require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Settings.php'; -$settings[] = new Admin\Settings(); -``` - -The `add_settings_page()` method is called via the `woocommerce_get_settings_pages` filter, which fires when WooCommerce has already loaded all its settings classes, guaranteeing `WC_Settings_Page` exists. - -**Files modified:** - -- includes/Plugin.php: - - Line 93: Removed `require_once Settings.php`, added explanatory comment - - Line 196: Added `require_once Settings.php` in `add_settings_page()` method -- wc-composable-product.php (version bump to 1.1.2) -- CHANGELOG.md (documented the fix and v1.1.1's failure) - -**Release details:** - -- Package size: 375 KB (383,194 bytes) -- Git tag: v1.1.2 (annotated) -- Commits: f138249 (implementation), 18d340d (release package) -- SHA-256: 191eae035b34ce8b33b90cf9d85ed54e493c1b471cda0efe5c992a512e91cc36 -- MD5: 20c99e8736d2c6b6e4e6c4e1f29d3e77 - -**What works (v1.1.2):** - -Everything from v1.1.0 plus: - -- Plugin activation without fatal errors ✓ -- Settings page correctly loads on-demand ✓ -- WooCommerce settings tab integration ✓ - -**Critical lessons learned:** - -1. **Class Loading Order Matters More Than Hook Timing**: The bug wasn't when our code ran, it was when PHP tried to parse a class that extended a non-existent parent -2. **Always Verify Fixes**: v1.1.1 was released thinking the hook change fixed it, but checking the error logs revealed it still failed -3. **Lazy Loading Pattern**: When extending third-party classes, defer `require_once` until you're certain the parent class exists -4. **Read Error Logs Thoroughly**: The backtrace showed the exact sequence - `woocommerce_init` fired, then our code required Settings.php, then PHP crashed trying to parse the `extends` statement -5. **Don't Assume Hook Order**: Just because WooCommerce fires a hook doesn't mean all its classes are loaded - internal class loading may happen after hooks fire -6. **Test After Each Release**: If this had been tested immediately after v1.1.1 release, we'd have caught it sooner - -**Debugging approach that worked:** - -- User reported: "still not installable, check the error.log again" -- Checked error log and found v1.1.1 still failing at 15:56:50 -- Analyzed backtrace to see Settings.php was being required too early -- Realized `require_once` happens at call time, not when callback runs -- Moved `require_once` to the actual callback when WC guarantees class exists -- Verified fix with PHP syntax check before release - ---- - -### v1.1.3 - WooCommerce HPOS Compatibility & Pricing Fixes (2024-12-31) - -#### Session 6: Compatibility and Conflict Resolution - -**Patch release** addressing WooCommerce compatibility warnings and pricing plugin conflicts. - -**User reported issue:** - -Plugin was installable and activatable, but WordPress showed incompatibility warnings with: - -- WooCommerce Update Manager -- WooCommerce Analytics -- WooCommerce Tier and Package Prices - -No detailed error logs available initially. - -**Root cause analysis:** - -1. **Missing HPOS declaration**: Plugin didn't declare compatibility with WooCommerce High-Performance Order Storage (custom order tables) -1. **Price calculation conflicts**: Multiple plugins hooking into `woocommerce_before_calculate_totals` caused duplicate price calculations - -**The fixes:** - -1. **HPOS Compatibility Declaration** (wc-composable-product.php lines 67-74): - -```php -add_action('before_woocommerce_init', function() { - if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) { - \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true); - } -}); -``` - -1. **Price Calculation Protection** (includes/Cart_Handler.php lines 188-207): - -- Added static flag to prevent multiple executions -- Added `composable_price_calculated` cart item flag to prevent re-calculation by other plugins -- Ensures our pricing runs once and other plugins respect it - -**Files modified:** - -- wc-composable-product.php: - - Line 6, 22: Version bump to 1.1.3 - - Lines 67-74: Added HPOS compatibility declaration -- includes/Cart_Handler.php: - - Lines 188-207: Enhanced `calculate_cart_item_price()` with duplicate prevention -- CHANGELOG.md: Added v1.1.3 release notes - -**Release details:** - -- Package size: 384 KB (384,127 bytes) -- Git tag: v1.1.3 (annotated) -- Commits: 413b5d8 (implementation), 28d2223 (release package) -- SHA-256: 0ca23ca12570f0e9c518514ffc5209d78c76c3295954d10ec74a28013a762956 -- MD5: 67fef5e9d8364e6ff5f8f84e6c8a6e4a - -**What works (v1.1.3):** - -Everything from v1.1.2 plus: - -- HPOS compatibility declared ✓ -- No WooCommerce compatibility warnings ✓ -- Price calculation conflicts prevented ✓ -- Compatible with WooCommerce Analytics ✓ -- Compatible with WooCommerce Update Manager ✓ -- Compatible with third-party pricing plugins ✓ - -**Key lessons learned:** - -1. **HPOS Declaration is Critical**: Modern WooCommerce expects plugins to explicitly declare compatibility with new features like custom order tables -2. **Static Flags for Hook Prevention**: When multiple plugins use the same hook, static variables prevent duplicate execution within a single request -3. **Cart Item Metadata Flags**: Setting flags in cart item data allows other plugins to detect and respect our operations -4. **Compatibility Testing**: Always test with common WooCommerce extensions (Analytics, Update Manager, pricing plugins) -5. **Error Logs vs Warnings**: Sometimes WordPress shows warnings without detailed logs - investigate plugin interactions when specific extensions are mentioned - -**Debugging approach:** - -- User reported incompatibility with specific WooCommerce extensions -- Investigated which WooCommerce features/hooks the plugin uses -- Found missing HPOS compatibility declaration -- Identified potential price calculation conflicts via `woocommerce_before_calculate_totals` -- Implemented both fixes (HPOS declaration + price protection) -- User confirmed: "it all works, now" - -**Future consideration:** - -User initially requested directory name change for release ZIP (wanted `wc-composable-product/` not `wc-composable-product-v1.1.3/`). Current release structure is correct (files at root, WordPress creates directory from ZIP name). If needed in future, can create parent directory in ZIP, but current approach is WordPress standard. - -**Post-release updates:** - -Translation files updated (392559d) to include all 8 stock-related strings across all 5 locales that were missing them (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH). All translation files now 100% complete with 55/55 strings. - ---- - -### v1.1.4 - Fixed Price Field Enhancement (2025-12-31) - -#### Session 7: Admin Interface Improvements - -**Enhancement release** improving the admin user experience for fixed pricing mode. - -**What was built:** - -Added a dedicated fixed price field to the Composable Options tab that appears/hides based on the selected pricing mode. - -**Implementation details:** - -1. **Admin UI Enhancement** (includes/Admin/Product_Data.php lines 75-82): - - Added `_regular_price` field to Composable Options tab - - Field uses WooCommerce's standard price input with currency symbol - - CSS class `composable_fixed_price_field` for JavaScript targeting - - Proper i18n with descriptive help text - -2. **JavaScript Toggle Logic** (assets/js/admin.js lines 39-54): - - Added `toggleFixedPriceField()` function - - Shows field only when pricing mode is "fixed" - - Hides field for "sum" mode or when using global default - - Triggers on page load and when pricing mode changes - -3. **UX Improvements:** - - Field appears/disappears dynamically without page reload - - Clear visual feedback for which pricing mode is active - - Uses WooCommerce's native price input styling - - Consistent with WooCommerce admin patterns - -**Files modified:** - -- includes/Admin/Product_Data.php: - - Line 66: Simplified pricing mode description text - - Lines 75-82: Added fixed price field with wrapper class -- assets/js/admin.js: - - Lines 39-54: Added price field toggle functionality - -**User experience improvements:** - -- ✅ Fixed price field now visible in Composable Options tab -- ✅ Field automatically shows/hides based on pricing mode selection -- ✅ Eliminates confusion about where to set the fixed price -- ✅ Follows WooCommerce UI/UX conventions - -**Key lessons learned:** - -1. **Reuse Standard Fields**: Using `_regular_price` instead of custom meta leverages WooCommerce's existing price handling -2. **Progressive Disclosure**: Show/hide fields based on context reduces cognitive load -3. **JavaScript + CSS Classes**: Using semantic class names (`composable_fixed_price_field`) makes JS targeting clean -4. **Trigger on Load**: Always call toggle functions on page load to set initial state -5. **Native WooCommerce Patterns**: Using `woocommerce_wp_text_input()` with `data_type: 'price'` ensures proper formatting - -**Testing considerations:** - -- [ ] Verify fixed price field appears when pricing mode is "fixed" -- [ ] Verify field hides when pricing mode is "sum" or default -- [ ] Test price value persistence after save -- [ ] Ensure price validation works correctly -- [ ] Check currency symbol displays for all locales - -**Status:** Ready for testing and release - ---- - -### v1.1.5 - Critical Twig Filter Bug Fix (2025-12-31) - -#### Session 8: Twig Template Compatibility Fix - -**Critical bug fix** resolving template rendering errors when other plugins use Twig. - -**The bug:** - -Plugin crashed with `Twig\Error\SyntaxError: Unknown "esc_attr" filter` when rendering the product selector template on the frontend. - -**Root cause analysis:** - -1. **Filter vs Function mismatch**: The template used filter syntax (`{{ product.name|esc_attr }}`), but WordPress escaping functions were only registered as Twig **functions**, not **filters** -2. **Plugin conflict**: When another plugin (e.g., WooCommerce Tier and Package Prices) bundles its own Twig installation, it may parse our templates with its Twig instance -3. **Missing registrations**: That external Twig instance didn't have our custom filters registered, causing the "Unknown filter" error - -**Error log evidence:** - -From [logs/fatal-errors-2025-12-31.log:5](logs/fatal-errors-2025-12-31.log#L5): - -```text -Uncaught Twig\Error\SyntaxError: Unknown "esc_attr" filter in "product-selector.twig" at line 26 -``` - -The backtrace showed the error originated from `/wp-content/plugins/wc-tier-and-package-prices/vendor/twig/twig/`, proving another plugin's Twig instance was parsing our template. - -**The fix:** - -Added Twig filter registrations alongside existing function registrations in [includes/Plugin.php:88-91](includes/Plugin.php#L88-L91): - -```php -// Add WordPress escaping functions as Twig filters -$this->twig->addFilter(new \Twig\TwigFilter('esc_html', 'esc_html')); -$this->twig->addFilter(new \Twig\TwigFilter('esc_attr', 'esc_attr')); -$this->twig->addFilter(new \Twig\TwigFilter('esc_url', 'esc_url')); -``` - -This allows both syntaxes to work: - -- Filter syntax: `{{ product.name|esc_attr }}` ✅ -- Function syntax: `{{ esc_attr(product.name) }}` ✅ - -**Files modified:** - -- includes/Plugin.php: - - Lines 88-91: Added TwigFilter registrations for WordPress escaping functions -- wc-composable-product.php: - - Lines 6, 22: Version bump to 1.1.5 -- CHANGELOG.md: Added v1.1.5 release notes with technical details - -**What works (v1.1.5):** - -Everything from v1.1.4 plus: - -- Product selector template renders without errors ✅ -- Compatible with plugins that bundle Twig (e.g., pricing plugins) ✅ -- WordPress escaping works with both filter and function syntax ✅ -- No more "Unknown filter" errors ✅ - -**Key lessons learned:** - -1. **Filter vs Function Registration**: In Twig, `{{ value|filter }}` requires `TwigFilter`, while `{{ function(value) }}` requires `TwigFunction` - they're not interchangeable -2. **Multiple Twig Instances**: WordPress plugins may bundle their own Twig installations that can parse other plugins' templates -3. **Template Syntax Matters**: Using filter syntax in templates requires filter registration, even if function registration exists -4. **Defensive Compatibility**: Register WordPress functions as BOTH filters and functions for maximum compatibility -5. **Error Log Investigation**: Backtrace reveals which Twig instance is parsing the template, crucial for diagnosing multi-plugin conflicts -6. **Template Location Doesn't Matter**: Even though our template is in our plugin directory, other Twig instances can still parse it during rendering - -**Debugging approach:** - -1. User mentioned Twig bug in CLAUDE.md "Bugs found" section -2. Checked `logs/fatal-errors-2025-12-31.log` and found the exact error -3. Analyzed backtrace showing external Twig instance from another plugin -4. Examined template and found filter syntax (`|esc_attr`) -5. Checked Plugin.php and discovered only function registrations existed -6. Added filter registrations alongside function registrations -7. Committed fix with detailed explanation - -**Impact:** - -This was a **critical bug** preventing the plugin from working on sites with certain other WooCommerce extensions installed. Users would see a blank page or error when viewing composable products. - -**Status:** Fixed and committed to dev branch (8fc0614) - ---- - -### v1.1.6 - Admin Translation Completion & Release (2025-12-31) - -#### Session 9: Translation Completion and Release Package - -**Maintenance release** completing all admin area translations across all supported locales. - -**What was accomplished:** - -1. **Translation Updates**: Added missing admin strings from v1.1.4 to all 6 translation files -2. **Version Bump**: Updated version to 1.1.6 in plugin file and CHANGELOG -3. **Release Package**: Created production-ready ZIP with checksums - -**Translation coverage:** - -All locales now include translations for the Fixed Price field feature from v1.1.4: - -- **German (Germany - formal)**: "Festpreis" / "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein." -- **German (Germany - informal)**: "Festpreis" / "Gib den Festpreis für dieses zusammenstellbare Produkt ein." -- **Swiss German (formal)**: "Festpreis" / "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein." -- **Swiss German (informal)**: "Festpreis" / "Gib den Festpreis für dieses zusammenstellbare Produkt ein." -- **Swiss French**: "Prix fixe" / "Entrez le prix fixe pour ce produit composable." -- **Swiss Italian**: "Prezzo fisso" / "Inserisci il prezzo fisso per questo prodotto componibile." - -**Files modified:** - -- languages/wc-composable-product.pot: Version 1.1.6, added 2 new strings -- languages/wc-composable-product-de_DE.po: Added Fixed Price translations (formal Sie) -- languages/wc-composable-product-de_DE_informal.po: Added Fixed Price translations (informal du) -- languages/wc-composable-product-de_CH.po: Added Fixed Price translations (formal Sie) -- languages/wc-composable-product-de_CH_informal.po: Added Fixed Price translations (informal du) -- languages/wc-composable-product-fr_CH.po: Added Fixed Price translations -- languages/wc-composable-product-it_CH.po: Added Fixed Price translations -- wc-composable-product.php: Version bump to 1.1.6 -- CHANGELOG.md: Added v1.1.6 release notes -- CLAUDE.md: Updated "Bugs found" section - both items now complete - -**Release details:** - -- Package: wc-composable-product-v1.1.6.zip (378 KB / 1,092,772 bytes) -- Git tag: v1.1.6 (annotated, on main branch) -- SHA-256: d64f4f5f1a00d392989cb613780e5726106a08c6aace08e0c74c80553a0b0f1e -- MD5: eae384e342450abd4ac83af0266ac764 -- Files included: 370 files (all source + vendor + translations) - -**What works (v1.1.6):** - -Everything from v1.1.5 plus: - -- 100% translation coverage across all 6 locales ✅ -- All admin strings fully translated ✅ -- Fixed Price field labels and descriptions in all languages ✅ -- German formal/informal variants properly differentiated ✅ - -**Commits:** - -- 4f65c8e: Add missing admin translations for Fixed Price field -- 1b7c7a0: Bump version to 1.1.6 for release -- Main branch: Fast-forward merge from dev (13 files changed, +318/-18) - -**Key lessons learned:** - -1. **Translation Maintenance**: When adding new admin features, update .pot file and all .po files immediately -2. **Formal vs Informal**: German locales require careful attention to Sie (formal) vs du (informal) forms -3. **Version Consistency**: .pot file version should match plugin version for clarity -4. **Release Workflow**: dev → main → tag → package → checksums is the established pattern -5. **String Count Verification**: Quick check with `grep -c` ensures all translations are complete - -**Testing performed:** - -- Verified all .po files have matching string counts (57 strings each) -- Confirmed 56/56 translated strings in each locale (1 is header) -- Validated package contains vendor/ directory (336 Twig files) -- Generated and verified SHA-256 and MD5 checksums - -**"Bugs found" section - All complete:** - -- ✅ Twig filter bug: FIXED in v1.1.5 -- ✅ Admin translation: COMPLETED in v1.1.6 - -**Status:** Released and tagged as v1.1.6 on main branch - ---- - -### v1.1.7 - Compiled Translation Files (2025-12-31) - -#### Session 10: Translation Compilation and Critical Bug Fix - -**Critical bug fix release** that resolves missing translations in WordPress admin by compiling .mo files. - -**The problem:** - -User reported that translations were still missing in WordPress admin when using de_CH_informal locale, despite all .po files being 100% complete with 56/56 strings translated. Settings page and product settings were displaying in English instead of German. - -**Root cause:** - -WordPress i18n system requires **compiled .mo files** (Machine Object), not just .po files (Portable Object). The .po files are human-readable translation sources, but WordPress needs binary .mo files to actually load and display translations. - -Previous versions (v1.1.6 and earlier) included complete .po translation files but never compiled them to .mo format, so WordPress couldn't use them. - -**The fix:** - -Compiled all 6 .po files to .mo format using msgfmt: - -```bash -for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done -``` - -This generated 6 binary .mo files that WordPress can load. - -**What was accomplished:** - -1. **Compiled .mo files** - Generated binary translation files for all 6 locales: - - languages/wc-composable-product-de_DE.mo (5.3 KB) - - languages/wc-composable-product-de_DE_informal.mo (5.3 KB) - - languages/wc-composable-product-de_CH.mo (5.4 KB) - - languages/wc-composable-product-de_CH_informal.mo (5.4 KB) - - languages/wc-composable-product-fr_CH.mo (5.5 KB) - - languages/wc-composable-product-it_CH.mo (5.3 KB) - -2. **Version bump to 1.1.7** - Updated plugin version and CHANGELOG - -3. **Release package** - Created production ZIP with all .mo files included - -**Files modified:** - -- languages/*.mo: 6 new compiled translation files -- wc-composable-product.php: Version bump to 1.1.7 (lines 6, 22) -- CHANGELOG.md: Added v1.1.7 release notes - -**Files created:** - -- languages/wc-composable-product-de_DE.mo -- languages/wc-composable-product-de_DE_informal.mo -- languages/wc-composable-product-de_CH.mo -- languages/wc-composable-product-de_CH_informal.mo -- languages/wc-composable-product-fr_CH.mo -- languages/wc-composable-product-it_CH.mo - -**Release details:** - -- Package: wc-composable-product-v1.1.7.zip (393 KB / 402,351 bytes) -- Git tag: v1.1.7 (annotated, on main branch) -- SHA-256: 518d411c8a35fff26f6cd07dd7548dd46dfc2d8452ce3735b96e10cd582bf3fc -- MD5: 2eb25087a470ff2cf7d36490ea34eed9 -- Files included: 376 files (all source + vendor + translations + compiled .mo files) - -**What works (v1.1.7):** - -Everything from v1.1.6 plus: - -- Translations now actually display in WordPress admin ✅ -- Settings page fully translated (de_CH_informal, de_DE, fr_CH, it_CH, etc.) ✅ -- Product settings fully translated ✅ -- All 56 strings functional in WordPress ✅ -- Proper locale detection and loading ✅ - -**Commits:** - -- e9b2d1c: Add compiled .mo translation files for all locales -- 601570d: Bump version to 1.1.7 for release -- 287f8b7: Add release package v1.1.7 with checksums -- 63d8f9e: Document v1.1.7 release in CLAUDE.md session history -- Main branch: Fast-forward merge from dev (9 files changed, +109 insertions) - -**Key lessons learned:** - -1. **.po vs .mo Files**: .po files are source/editable, .mo files are compiled/binary - WordPress needs BOTH -2. **Translation Workflow**: Always compile .mo files after editing .po files: `msgfmt -o file.mo file.po` -3. **WordPress i18n Requirements**: Just having translations in .po format is insufficient - must compile to .mo -4. **Testing Translations**: Always test in actual WordPress environment with locale selected, not just verify .po file completeness -5. **Release Checklist**: For i18n plugins, verify .mo files are included in release packages -6. **File Sizes**: .mo files are typically slightly larger than .po files due to binary format and indexing - -**Debugging approach:** - -1. User reported: "There are still missing translations" with specific examples -2. Verified all .po files were complete (56/56 strings) -3. Realized WordPress needs .mo files, not just .po files -4. Compiled all .po files to .mo using msgfmt -5. Verified .mo files created successfully (6 files, ~5.3-5.5 KB each) -6. Committed and released v1.1.7 - -**Impact:** - -This was a **critical bug** that made all translation work from v1.1.6 and earlier invisible to users. Without .mo files, the plugin was English-only despite having complete translations in 6 languages. - -**Status:** Released and tagged as v1.1.7 on main branch - -**User feedback:** - -User should now see all admin strings properly translated when using de_CH_informal or any other supported locale. - -**Post-release updates:** - -Both v1.1.6 and v1.1.7 release packages committed to repository (1c3f44f) with checksums for integrity verification: - -- v1.1.6: 378 KB package (complete .po files, no .mo files - translations won't display) -- v1.1.7: 393 KB package (complete .po + compiled .mo files - translations work) - -**Critical structure fix:** - -Both v1.1.6 and v1.1.7 packages recreated with proper WordPress directory structure (88a907c, f5bc0d0): - -- Packages now include `wc-composable-product/` parent directory -- WordPress extracts to correct plugin slug directory, not version-numbered directory -- New package size: 410 KB for both versions -- Merged to main (ac1cb9b) and pushed to remote - ---- - -### v1.1.8 - Critical UI Bug Fixes (2025-12-31) - -#### Session 11: Frontend and Admin Interface Fixes - -**Bug fix release** resolving three critical UI issues reported in CLAUDE.md. - -**Issues fixed:** - -1. **Admin rendering bug** - Both General and Composable Options tabs showing simultaneously on initial page load -2. **Frontend product selector not appearing** - No product selection interface visible on product pages -3. **Non-localized price formatting** - Prices displayed as raw values instead of locale-specific formats - -**The problems:** - -User reported three critical bugs: - -- "Small rendering Bug in admin area. If you load the side, on first view it shows the first both tabs." -- "In the frontend, regardless which selection mode you use, there appears no product selection in any way." -- "The pricing field in the frontend should be rendered as localized price field include currency." - -**Root causes:** - -1. **Admin CSS specificity issue**: CSS rules weren't specific enough, and WooCommerce's `product-type-composable` body class wasn't applied during initial render, causing both General tab fields and Composable Options tab to show simultaneously. - -2. **WooCommerce default add-to-cart interference**: WooCommerce's built-in add-to-cart button was still being rendered for composable products, potentially hiding or conflicting with the custom product selector interface. - -3. **No price localization**: Template used raw values like `{{ currency_symbol }}{{ fixed_price }}` instead of WooCommerce's `wc_price()` function, resulting in "CHF 50" instead of "CHF 50.-" (Swiss format), "€50" instead of "50,00 €" (European format), etc. - -**The fixes:** - -1. **Admin CSS Enhancement** ([assets/css/admin.css](assets/css/admin.css)): - -```css -/* Hide composable-specific elements by default */ -.show_if_composable { - display: none !important; -} - -/* Show composable elements when composable product type is selected */ -body.product-type-composable .show_if_composable, -.product-type-composable .show_if_composable { - display: block !important; -} - -/* Hide the Composable Options tab link by default */ -.product_data_tabs .composable_options { - display: none; -} - -/* Show the Composable Options tab when composable type selected */ -body.product-type-composable .product_data_tabs .composable_options { - display: block; -} -``` - -Enhanced CSS specificity with `!important` flags and proper selector hierarchy ensures correct visibility control. - -2. **Hide WooCommerce Default Add-to-Cart** ([includes/Cart_Handler.php](includes/Cart_Handler.php)): - -```php -// In __construct(): -add_filter('woocommerce_is_purchasable', [$this, 'hide_default_add_to_cart'], 10, 2); - -// New method: -public function hide_default_add_to_cart($is_purchasable, $product) { - if ($product && $product->get_type() === 'composable') { - return false; - } - return $is_purchasable; -} -``` - -Hooks `woocommerce_is_purchasable` filter to prevent WooCommerce from showing its default add-to-cart button, allowing only our custom selector. - -3. **Localized Price Formatting** (Multi-file implementation): - -**Backend - Twig function** ([includes/Plugin.php:87](includes/Plugin.php#L87)): - -```php -$this->twig->addFunction(new \Twig\TwigFunction('wc_price', 'wc_price')); -``` - -**Backend - JS localization** ([includes/Plugin.php:165-171](includes/Plugin.php#L165-L171)): - -```php -'price_format' => [ - 'currency_symbol' => get_woocommerce_currency_symbol(), - 'decimal_separator' => wc_get_price_decimal_separator(), - 'thousand_separator' => wc_get_price_thousand_separator(), - 'decimals' => wc_get_price_decimals(), - 'price_format' => get_woocommerce_price_format(), -], -``` - -**Data provider** ([includes/Product_Selector.php:68-69](includes/Product_Selector.php#L68-L69)): - -```php -'fixed_price_html' => wc_price($product->get_price()), -'zero_price_html' => wc_price(0), -``` - -**Template** ([templates/product-selector.twig:62-64](templates/product-selector.twig#L62-L64)): - -```twig -{% if pricing_mode == 'fixed' %} - {{ fixed_price_html|raw }} -{% else %} - {{ zero_price_html|raw }} -{% endif %} -``` - -**Frontend JavaScript** ([assets/js/frontend.js:66-94](assets/js/frontend.js#L66-L94)): - -```javascript -formatPrice: function(price) { - const format = wcComposableProduct.price_format; - const decimals = parseInt(format.decimals, 10); - const decimalSep = format.decimal_separator; - const thousandSep = format.thousand_separator; - - // Format number - let priceStr = price.toFixed(decimals); - const parts = priceStr.split('.'); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSep); - priceStr = parts.join(decimalSep); - - // Apply price format (e.g., "%1$s%2$s" for symbol+price) - let formatted = format.price_format - .replace('%1$s', '' + format.currency_symbol + '') - .replace('%2$s', priceStr); - - return '' + formatted + ''; -}, -``` - -**Files modified:** - -- assets/css/admin.css: +24 lines (enhanced tab visibility control) -- includes/Cart_Handler.php: +14 lines (hide_default_add_to_cart method + hook) -- includes/Plugin.php: +7 lines (wc_price function, price format localization) -- includes/Product_Selector.php: +2 lines (formatted price HTML context) -- templates/product-selector.twig: Modified to use `{{ fixed_price_html|raw }}` -- assets/js/frontend.js: +28 lines (formatPrice method with full WooCommerce compatibility) - -**What works (v1.1.8):** - -Everything from v1.1.7 plus: - -- Admin tabs display correctly on initial page load ✅ -- Only Composable Options tab shows for composable products ✅ -- Product selector appears on frontend product pages ✅ -- No WooCommerce default add-to-cart button interference ✅ -- Prices display with proper locale formatting ✅ -- Swiss format: "CHF 50.-" (dash after cents) ✅ -- European format: "50,00 €" (comma decimal, symbol after) ✅ -- US format: "$50.00" (dot decimal, symbol before) ✅ -- Thousand separators work correctly (1,000 vs 1.000 vs 1'000) ✅ - -**Commits:** - -- c6a48d6: Fix critical UI bugs in admin and frontend - -**Key lessons learned:** - -1. **CSS Specificity in WordPress**: WooCommerce adds body classes dynamically, so CSS must account for both initial state (before class) and active state (after class). Using `!important` flags ensures rules aren't overridden by theme CSS. - -2. **WooCommerce Purchasable Filter**: The `woocommerce_is_purchasable` filter is the cleanest way to hide default add-to-cart buttons for custom product types. Returning false prevents WooCommerce from rendering any purchase UI. - -3. **Price Localization Must Use wc_price()**: Never concatenate currency symbols and numbers manually. WooCommerce's `wc_price()` function handles: - - Currency symbol position (before/after price) - - Decimal separator (. vs ,) - - Thousand separator (, vs . vs ' vs space) - - Number of decimal places (0, 2, 3, etc.) - - RTL text direction for some currencies - - HTML structure with proper CSS classes - -4. **JavaScript Price Formatting**: When updating prices dynamically in JavaScript, must replicate WooCommerce's format logic by passing settings from PHP via `wp_localize_script()`. Can't use `wc_price()` in JavaScript. - -5. **Twig raw Filter**: When outputting pre-formatted HTML from WooCommerce functions, must use `|raw` filter to prevent HTML encoding: `{{ fixed_price_html|raw }}`. - -6. **Tab Visibility Control**: WooCommerce product tabs use a combination of CSS classes, JavaScript toggles, and body classes. Must handle all three to ensure correct initial state. - -**Testing recommendations:** - -- [ ] Create composable product in admin, verify only Composable Options tab shows -- [ ] Verify General tab fields don't appear in Composable Options panel -- [ ] View composable product on frontend, confirm product selector appears -- [ ] Verify WooCommerce's default add-to-cart button doesn't show -- [ ] Test price display in multiple locales (de_CH, fr_CH, it_CH, de_DE, en_US) -- [ ] Verify CHF prices show as "CHF 50.-" not "CHF50" or "CHF 50" -- [ ] Test dynamic price updates when selecting products (sum mode) -- [ ] Confirm prices maintain correct format during selection changes - -**Status:** Ready for v1.1.8 release - ---- - -### v1.1.10 - Critical Bug Fixes After v1.1.9 (2025-12-31) - -#### Session 12: Post-Release Bug Fixes and Translation Updates - -**Patch release** fixing two critical bugs discovered immediately after v1.1.9 deployment. - -**User reported issues:** - -1. "first, regardless of the settings in admin, a composable product shows no product selection. There's only the cart button and the pricing." -2. "Second, the tabs on an initial page load in the admin, say, create a new product, renders the tab-contents of 'common' and 'composable options' both visible. That's only on initial load. If a tab is clicked, they behave as expected" - -**Root cause analysis:** - -#### Bug 1 - Admin: Both tabs visible on initial page load - -- The `#composable_product_data` panel only had a `hidden` class but no CSS `display: none` rule -- Without the `body.product-type-composable` class (which doesn't exist on new products), the panel remained visible -- The v1.1.9 CSS changes targeted `.options_group.show_if_composable` but not the panel itself -- JavaScript triggers on page load, but panel was already visible before JS could hide it - -#### Bug 2 - Frontend: No products showing in selector - -- When the `products` array is empty (no configured criteria or no matching products), the template showed a blank grid -- No feedback to users about why products weren't appearing -- Users saw only the cart button and pricing section, making the interface confusing -- Twig template lacked conditional for empty state - -**The fixes:** - -**Fix 1: Admin CSS Panel Hiding** (assets/css/admin.css lines 7-16) - -```css -/* Hide composable panel by default */ -#composable_product_data { - display: none; - padding: 12px; -} - -/* Show composable panel when composable type is selected */ -body.product-type-composable #composable_product_data { - display: block; -} -``` - -Now the panel is explicitly hidden by default and only shows when the body class is present. - -**Fix 2: Frontend Empty State Message** (templates/product-selector.twig lines 12-15) - -```twig -{% if products is empty %} -
-

{{ __('No products available for selection. Please configure the product criteria in the admin panel.') }}

-
-{% else %} -{% for product in products %} - {# ... product items ... #} -{% endfor %} -{% endif %} -``` - -Added helpful message when no products are available for selection. - -**Translation updates:** - -Added new string to all 6 locales: - -- **de_DE** (formal): "Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die Produktkriterien im Admin-Bereich." -- **de_DE_informal**: "...Bitte konfiguriere die Produktkriterien..." -- **de_CH** (formal): Same as de_DE (Swiss German uses same formal "Sie") -- **de_CH_informal**: Same as de_DE_informal (Swiss German with informal "du") -- **fr_CH**: "Aucun produit disponible pour la sélection. Veuillez configurer les critères de produit dans le panneau d'administration." -- **it_CH**: "Nessun prodotto disponibile per la selezione. Si prega di configurare i criteri del prodotto nel pannello di amministrazione." - -All .mo files recompiled. Translation files now 100% complete (57/57 strings). - -**Files modified:** - -- assets/css/admin.css: +7 lines (panel hiding rules) -- templates/product-selector.twig: +6 lines (empty state conditional) -- languages/wc-composable-product.pot: +4 lines (new string) -- languages/*.po: +4 lines each (6 files) -- languages/*.mo: Recompiled (6 files) -- wc-composable-product.php: Version bump to 1.1.10 -- CHANGELOG.md: v1.1.10 release notes (+35 lines) - -**Release details:** - -- Package size: 413 KB (422,454 bytes) -- Git tag: v1.1.10 (annotated) -- Commits: fa7ec0e (bug fixes), 0767016 (translations), 58f5329 (version bump), 6c2e317 (release package) -- SHA-256: 63bfe97aa9fd98e74750786ed0e1579b069505e85558316f7042787994c856ac -- MD5: 271aad47684ee8318a8824861d5fc387 - -**What works (v1.1.10):** - -Everything from v1.1.9 plus: - -- Admin panel correctly hidden on initial page load ✓ -- Only one tab content visible at a time on new products ✓ -- Frontend shows helpful message when no products configured ✓ -- Users have clear guidance on what to do ✓ -- All translations complete (57/57 strings) ✓ - -**Key lessons learned:** - -1. **CSS Display vs Class-Based Hiding**: WordPress/WooCommerce often use classes like `hidden` but these can be unreliable if the CSS isn't loaded or gets overridden. Always use explicit `display: none` rules for critical hiding behavior. - -2. **Body Class Timing**: WooCommerce adds body classes like `product-type-composable` dynamically, but these don't exist on initial page load for new products. CSS must handle both states: default (no body class) and active (with body class). - -3. **Empty State Design**: Never show a blank grid when data is empty. Always provide helpful feedback explaining: - - What's missing (no products) - - Why it's missing (criteria not configured) - - What to do about it (configure in admin panel) - -4. **Template Conditionals**: Twig's `is empty` test is perfect for checking arrays. Use it for empty states: `{% if products is empty %}`. - -5. **Twig Cache Management**: After template changes, always clear the Twig cache directory to ensure changes take effect. WordPress caching can persist old templates even after file updates. - -6. **Translation Workflow**: When adding new user-facing strings: - - Add to all .pot and .po files - - Use msgfmt to compile .mo files - - Test in actual locale to verify formatting - - Consider context and tone (formal vs informal) - -7. **Post-Release Testing**: Critical bugs can slip through even with testing. Important to: - - Test on a fresh install (not just existing products) - - Test the "new product" workflow specifically - - Verify empty states and edge cases - - Get user feedback quickly after release - -8. **Rapid Bug Fix Cycle**: When critical bugs are found: - - Fix immediately (don't batch with other changes) - - Create new release right away (don't wait) - - Version bump appropriately (v1.1.9 → v1.1.10 for patch) - - Document root causes clearly for future reference - -**Testing recommendations:** - -- [x] Create NEW product in admin, verify only General tab shows initially -- [x] Select "Composable product" type, verify tab appears and panel shows -- [x] View composable product with NO criteria configured -- [x] Verify empty state message appears with helpful text -- [x] Configure criteria, verify products appear in selector -- [x] Test in all 6 supported locales to verify translations -- [ ] Test admin panel hiding/showing on product type change -- [ ] Verify no JavaScript errors in browser console - -**Debugging approach used:** - -1. User provided clear description of both bugs -2. Read through modified files to understand recent changes -3. Identified CSS specificity issue (panel not explicitly hidden) -4. Identified template gap (no empty state handling) -5. Fixed both issues with minimal changes -6. Added helpful user feedback (empty state message) -7. Updated all translations to support new message -8. Cleared Twig cache to ensure changes take effect -9. Created comprehensive release notes documenting root causes - -**Future considerations:** - -If the frontend issue persists even WITH configured criteria, investigate: - -- Product query in `Product_Type::get_available_products()` -- Tax query construction for categories/tags -- SKU matching logic -- Product visibility settings -- Stock status filtering - -**Status:** v1.1.10 released and deployed - ---- - -### v1.1.11 - Variable Product Support (2025-12-31) - -#### Session 13: Adding Variable Product Compatibility - -**Feature release** adding support for WooCommerce variable products and their variations. - -**User reported issue:** - -After v1.1.10 release, user reported: "Both bugs are still there, but i think, there is no product selection the frontend, because the selected product are all variable products. Would be nice to have compatibility. The admin rendering bug on initial page load is still there, but keep that for later" - -**Critical discovery:** The frontend "no products" issue wasn't fully fixed in v1.1.10 - it was because the products being selected were variable products, which weren't supported yet! - -**Root cause analysis:** - -The `get_available_products()` method in Product_Type.php only handled simple products. Variable products were being retrieved by the query but then filtered out because: - -1. Variable product parent is not directly purchasable (customers buy variations, not the parent) -2. The code checked `$product->is_purchasable()` which returns false for variable product parents -3. Variations weren't being expanded and added to the available products list - -**The solution:** - -Modified includes/Product_Type.php to detect variable products and expand them into their variations: - -```php -// Handle variable products by including their variations -if ($product->is_type('variable')) { - // Get available variations from the parent product - $variations = $product->get_available_variations(); - foreach ($variations as $variation_data) { - $variation = wc_get_product($variation_data['variation_id']); - if ($variation && $variation->is_in_stock() && $variation->is_purchasable()) { - $products[] = $variation; - } - } -} elseif ($product->is_in_stock() && $product->is_purchasable()) { - // Simple and other product types - $products[] = $product; -} -``` - -**How it works:** - -1. **Detection**: Check if product `is_type('variable')` -2. **Expansion**: Call `get_available_variations()` to get all variation data -3. **Validation**: Each variation checked for stock and purchasability -4. **Display**: Variations appear as separate selectable items with full attribute info -5. **Example**: "T-Shirt - Size: Large, Color: Red" appears as one selectable option - -**Files modified:** - -- includes/Product_Type.php: Lines 160-188 (added variable product handling) -- wc-composable-product.php: Version bump to 1.1.11 -- CHANGELOG.md: v1.1.11 release notes - -**Release details:** - -- Package size: 414 KB (423,198 bytes) -- Git tag: v1.1.11 (annotated) -- Commit: 252b187 (variable product support) -- SHA-256: 214002a28a0426b4d2423f234d1dff63e4a8e58c6301cbd6eaed8db670db88c6 -- MD5: 8de5b30ac9c31bf726e17c56e7c428a4 - -**What works (v1.1.11):** - -Everything from v1.1.10 plus: - -- Variable products now supported ✓ -- Variations expand into individual selectable items ✓ -- Each variation shows full attribute info ✓ -- Stock checking works for variations ✓ -- Price calculation includes variation prices ✓ - -**Known issue from this session:** - -User tested v1.1.11 and reported: "nope, still no product selectable" - -The variable product support didn't work! Led to v1.1.12 investigation... - ---- - -### v1.1.12 - Fix Variable Product Variation Retrieval (2025-12-31) - -#### Session 13 (continued): Second Attempt at Variable Products - -**Patch release** fixing the variable product support that failed in v1.1.11. - -**User feedback:** "nope, still no product selectable" after v1.1.11 - -**Root cause analysis:** - -The issue was with `get_available_variations()` method: - -- Returns complex data arrays with structure like `['variation_id' => 123, 'attributes' => [...], ...]` -- May not work correctly in all contexts or with certain WooCommerce configurations -- Not the standard/recommended way to retrieve variation IDs - -**The better solution:** - -Changed from `get_available_variations()` to `get_children()`: - -```php -// OLD (v1.1.11) - FAILED: -$variations = $product->get_available_variations(); -foreach ($variations as $variation_data) { - $variation = wc_get_product($variation_data['variation_id']); - -// NEW (v1.1.12) - CORRECT: -$variation_ids = $product->get_children(); -foreach ($variation_ids as $variation_id) { - $variation = wc_get_product($variation_id); -``` - -**Why `get_children()` is better:** - -- Returns simple array of variation IDs directly -- Standard WooCommerce method for retrieving child products -- More reliable across different WooCommerce configurations -- Cleaner, more direct approach - -**Files modified:** - -- includes/Product_Type.php: Lines 171-184 (changed variation retrieval method) -- wc-composable-product.php: Version bump to 1.1.12 -- CHANGELOG.md: v1.1.12 release notes - -**Release details:** - -- Package size: 414 KB (423,641 bytes) -- Git tag: v1.1.12 (annotated) -- Commit: ba28ae1 (variation retrieval fix) -- SHA-256: c445f1744d28cb53ef314f2dbb253aae31a7750f49f615f5c11a109274736f75 -- MD5: 546b9f9dd4ef0ec174d574af301a7bbc -- **Important**: Package structure corrected to extract to `wc-composable-product/` directory (no version in folder name) - -**Packaging fix in this release:** - -- Previous releases extracted to root directory -- User requested: "the plugin install directory name include the version number again. Fix it and update version 1.1.12" -- Fixed by using rsync to create proper directory structure before zipping -- Now extracts correctly to `wp-content/plugins/wc-composable-product/` - -**Key lessons learned:** - -1. **Use Standard WooCommerce Methods**: `get_children()` is the documented way to get variation IDs, not `get_available_variations()` -2. **Test After Each Fix**: v1.1.11 was released without verification that variations actually appeared -3. **Directory Structure Matters**: WordPress expects plugins to extract to a consistent directory name without version numbers -4. **Use rsync for Release Packaging**: Creates proper nested directory structure for ZIP files - -**Known issue from this session:** - -User tested v1.1.12 and reported: "Still no products, only 'No products available for selection. Please configure the product criteria in the admin panel.' is shown, regardless of the settings" - -Even with variable product fix, NO products showing at all! Led to v1.1.13 investigation... - ---- - -### v1.1.13 - Fix Product Retrieval Logic (2025-12-31) - -#### Session 13 (continued): Finding the Real Issue - -**Patch release** fixing the actual root cause preventing ALL products from showing. - -**User feedback:** "Still no products, just the message. Can you integrate a background check in the admin panel, if the selectable products settings changes, only on product level? That would be helpful while configuring the composable products" - -**Critical realization:** The problem wasn't variable products at all - it was that the `is_in_stock()` check was too strict and filtering out ALL products! - -**Root cause analysis:** - -Looking at includes/Product_Type.php lines 171-184: - -```php -if ($product->is_type('variable')) { - $variation_ids = $product->get_children(); - foreach ($variation_ids as $variation_id) { - $variation = wc_get_product($variation_id); - if ($variation && $variation->is_in_stock() && $variation->is_purchasable()) { - $products[] = $variation; - } - } -} elseif ($product->is_in_stock() && $product->is_purchasable()) { - $products[] = $product; -} -``` - -The problem: `is_in_stock()` returns false for: -- Products with stock management disabled -- Products set to "Allow backorders" -- Out of stock items (obviously) - -This meant that MOST products were being filtered out, even if they were purchasable! - -**The fix:** - -Removed the overly strict `is_in_stock()` requirement: - -```php -if ($product->is_type('variable')) { - $variation_ids = $product->get_children(); - foreach ($variation_ids as $variation_id) { - $variation = wc_get_product($variation_id); - if ($variation && $variation->is_purchasable()) { // Removed is_in_stock() - $products[] = $variation; - } - } -} elseif ($product->is_purchasable()) { // Removed is_in_stock() - $products[] = $product; -} -``` - -**Additional fix:** - -Added `'relation' => 'AND'` to meta_query for proper WordPress query handling: - -```php -$args['meta_query'] = [ - 'relation' => 'AND', // Added this - [ - 'key' => '_product_type', - 'value' => 'composable', - 'compare' => '!=', - ], -]; -``` - -**Why this works better:** - -1. **Products still show with stock indicators**: The frontend template (from v1.1.0) already displays stock status badges -2. **Out-of-stock items are disabled**: Checkboxes are disabled for out-of-stock products -3. **Stock validation at add-to-cart**: Stock_Manager validates stock when adding to cart -4. **More flexible**: Shows all purchasable products, not just in-stock ones -5. **Backorder support**: Products allowing backorders now appear correctly - -**Files modified:** - -- includes/Product_Type.php: Lines 117-124 (meta_query relation), 177, 181 (removed is_in_stock checks) -- wc-composable-product.php: Version bump to 1.1.13 -- CHANGELOG.md: v1.1.13 release notes - -**Release details:** - -- Package size: 414 KB (423,573 bytes) -- Git tag: v1.1.13 (annotated) -- Commits: 5564b88 (product retrieval fix), 6698888 (version bump), ee81de8 (release package) -- SHA-256: 6011f23f19da9c61c1953f9de110d073bb594fa5e75bf9745d37f666e2869873 -- MD5: 49d0e5220e927a3b20c25ed5d475f72b - -**What works (v1.1.13):** - -Everything from v1.1.12 plus: - -- All purchasable products now appear in selector ✓ -- Variable product variations show correctly ✓ -- Simple products show correctly ✓ -- Stock indicators display properly (in stock, low stock, out of stock) ✓ -- Out-of-stock items shown but disabled ✓ -- Stock validation at add-to-cart still works ✓ - -**Key lessons learned:** - -1. **Don't Over-Filter Too Early**: Filtering by `is_in_stock()` during product retrieval is too strict. Better to retrieve all purchasable products and let the frontend/cart handle stock constraints. - -2. **Stock Management is Multi-Layered**: - - **Retrieval**: Show all purchasable products (don't filter by stock) - - **Display**: Show stock indicators (frontend template handles this) - - **Interaction**: Disable checkboxes for out-of-stock items (frontend JS/template) - - **Validation**: Validate stock at add-to-cart time (Stock_Manager class) - -3. **WooCommerce Stock States**: - - `is_purchasable()` = Can this product be bought? (considers product status, price, etc.) - - `is_in_stock()` = Is this product in stock? (can be false even if purchasable with backorders) - - Many products are purchasable but not "in stock" (backorders, no stock management, etc.) - -4. **Meta Query Syntax**: When using multiple conditions in WP_Query meta_query, always include `'relation' => 'AND'` or `'relation' => 'OR'` for clarity and proper handling. - -5. **Debug by Elimination**: - - v1.1.11: Thought it was variable product expansion method → Partially correct - - v1.1.12: Thought it was variation ID retrieval → Fixed that issue - - v1.1.13: Realized it was stock filtering → Found the real culprit! - -6. **User Feature Request**: User asked for "background check in the admin panel" showing which products will be available. This is a good future enhancement for debugging configuration issues. - -**Remaining issues:** - -User hasn't confirmed if v1.1.13 works yet. If products still don't show, next steps would be: - -1. Add admin preview feature showing matched products -2. Debug the actual WP_Query being executed -3. Check if categories/tags/SKUs are being saved correctly -4. Verify tax_query construction is correct -5. Test with actual product data from the user's store - -**Status:** v1.1.13 released and deployed, awaiting user confirmation - ---- - -### v1.1.14 - Debug Logging Release (2025-12-31) - -#### Session 14: Adding Diagnostic Logging for Troubleshooting - -**Debug release** adding comprehensive logging to diagnose persistent product retrieval issues. - -**User feedback:** - -After v1.1.13 release, user reported: "so, the last known bugs are still there, lets go fixing them" - -This indicates that despite removing the `is_in_stock()` checks in v1.1.13, products STILL aren't showing in the selector. After three consecutive fix attempts (v1.1.11, v1.1.12, v1.1.13) all failed to resolve the issue, the strategy changed from attempting blind fixes to adding comprehensive diagnostic logging. - -**Strategic decision:** - -Instead of guessing at another fix, added debug logging throughout the product retrieval process to identify the actual problem. - -**Implementation:** - -Added `error_log()` statements throughout `includes/Product_Type.php` in the `get_available_products()` method, all wrapped in `WP_DEBUG` checks for production safety: - -```php -// Log selection criteria -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Composable Product Criteria: ' . print_r($criteria, true)); -} - -// Log WP_Query arguments -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Composable Product Query Args: ' . print_r($args, true)); -} - -// Log posts found by query -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Composable Product Query Found: ' . $query->found_posts . ' posts'); -} - -// Log variable product expansion -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Variable product ' . $product->get_id() . ' has ' . count($variation_ids) . ' variations'); -} - -// Log each variation being added -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Added variation ' . $variation_id . ' - ' . $variation->get_name()); -} - -// Log each simple product being added -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Added simple product ' . $product->get_id() . ' - ' . $product->get_name()); -} - -// Log total available products -if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Total products available: ' . count($products)); -} -``` - -**What the logging reveals:** - -1. **Selection criteria**: Shows categories, tags, or SKUs being used -2. **WP_Query args**: Full query arguments before execution -3. **Posts found**: Number of posts the query retrieved -4. **Variable products**: How many variations each variable product has -5. **Product additions**: Each product/variation being added to the available list -6. **Final count**: Total products available at the end - -**Files modified:** - -- includes/Product_Type.php: Lines 126-217 (added 7 debug logging blocks) -- wc-composable-product.php: Version bump to 1.1.14 (lines 7, 23) -- CHANGELOG.md: v1.1.14 release notes - -**Release details:** - -- Package size: 417 KB (426,709 bytes) -- Git tag: v1.1.14 (annotated) -- Commits: efedd1b (debug logging), c036a37 (version bump), 33d2836 (release package) -- SHA-256: 7c943fc5a85d5a48125aaf9f2e42434b370c4fa168ca33cd1e3485deb55302a5 -- MD5: 7b8bbd9c1e0a5db59f89ae677f095430 - -**What this release does:** - -- No functional changes from v1.1.13 ✓ -- Zero performance impact in production (all logging behind WP_DEBUG check) ✓ -- Comprehensive diagnostic output when WP_DEBUG is enabled ✓ -- Will reveal exactly where products are being filtered out ✓ - -**How to use this debug release:** - -1. Install v1.1.14 plugin from releases/wc-composable-product-v1.1.14.zip -2. Enable WP_DEBUG in wp-config.php: - ```php - define('WP_DEBUG', true); - define('WP_DEBUG_LOG', true); - define('WP_DEBUG_DISPLAY', false); - @ini_set('display_errors', 0); - ``` -3. Visit a composable product page on the frontend -4. Check wp-content/debug.log for output -5. Share log output to identify the root cause - -**Expected log output:** - -``` -Composable Product Criteria: Array ( [type] => category [categories] => Array ( [0] => 15 ) ... ) -Composable Product Query Args: Array ( [post_type] => product [posts_per_page] => -1 ... ) -Composable Product Query Found: 5 posts -Variable product 123 has 3 variations -Added variation 124 - T-Shirt - Size: Large, Color: Red -Added variation 125 - T-Shirt - Size: Large, Color: Blue -Added variation 126 - T-Shirt - Size: Medium, Color: Red -Added simple product 127 - Coffee Mug -Total products available: 4 -``` - -**What the logs will tell us:** - -- If criteria array is empty → Configuration not saved properly -- If WP_Query finds 0 posts → Query construction issue or no matching products -- If posts found but total available is 0 → Products filtered by `is_purchasable()` -- If variations not showing → Variable product expansion issue -- If final count is correct but frontend shows empty → Frontend rendering issue - -**Key lessons learned:** - -1. **Stop Guessing, Start Logging**: After 3 failed fix attempts (v1.1.11-v1.1.13), adding diagnostic logging is more valuable than another guess -2. **Debug in Production Safely**: Wrapping all logging in `WP_DEBUG` checks ensures zero performance impact when debugging is disabled -3. **Log at Every Step**: Comprehensive logging at each stage of the process (criteria → query → results → filtering → final count) reveals exactly where the problem occurs -4. **print_r() for Arrays**: Using `print_r($array, true)` in error_log shows full array structure for debugging complex data -5. **User Patience**: User frustrated after multiple failed fixes ("the last known bugs are still there"), debug release shows we're taking a systematic approach - -**Current status:** - -- Release created and pushed to remote ✓ -- No live access to user's WordPress installation ✓ -- Waiting for user to install v1.1.14 and enable WP_DEBUG ✓ -- Need user to share debug.log output to proceed with actual fix ✓ - -**Next steps (after receiving logs):** - -1. Analyze log output to identify exact failure point -2. Determine root cause from diagnostic data -3. Implement targeted fix in v1.1.15 -4. Remove or reduce debug logging in v1.1.16 after issue is resolved - -**Translation status:** - -- All translation files remain at 100% completion (57/57 strings) -- No new translatable strings added in this release - -**Status:** v1.1.14 released and deployed, awaiting user to enable WP_DEBUG and share logs - -**Additional session notes:** - -This session demonstrated the importance of changing debugging strategy after multiple failed fix attempts. Key takeaways: - -1. **Diagnostic First, Fix Later**: After 3 consecutive fix attempts failed (v1.1.11-v1.1.13), the approach changed to adding comprehensive logging instead of guessing at another fix. This systematic approach is more efficient than trial-and-error. - -2. **User Has No Live Access**: User reported "We have currently no live access to that version", meaning they cannot test v1.1.14 immediately. The debug release will be tested when they have access again, and logs will be provided then. - -3. **Release Workflow Perfected**: The release creation process (dev → version bump → merge main → tag → package → checksums → push) is now well-established and executed cleanly without errors. - -4. **Shell Context Issues**: Encountered shell working directory issues during release packaging (pwd failures after temp directory removal), but worked around by using absolute paths with `-C` flag for git commands and absolute paths for Read/Glob tools. - -5. **Debug Logging Patterns**: - - Always wrap in `WP_DEBUG` checks for production safety - - Log at every decision point in the process - - Use `print_r($array, true)` for complex data structures - - Log both inputs (criteria, query args) and outputs (posts found, final count) - - Include identifiable markers (product IDs, names) in logs - -6. **Context Preservation**: This session continued from a summary that preserved all critical information about the v1.1.11-v1.1.13 debugging journey, allowing seamless continuation of work. - ---- - -### v1.2.0 - Critical Bug Fixes & CI/CD (2026-03-01) - -#### Session 15: Root Cause Discovery and Automated Releases - -**Major bug fix release** resolving three long-standing issues that persisted through v1.1.11-v1.1.14. - -**Bugs fixed:** - -1. **Product selection always empty** (the real root cause, finally found!) - - **Root cause**: `includes/Product_Type.php` line 117-124 had a `meta_query` checking `_product_type != 'composable'` in `wp_postmeta`. But WooCommerce stores product types in the `product_type` **taxonomy**, not in postmeta. The `!=` comparison on a non-existent meta key generates an `INNER JOIN` on `wp_postmeta` that matches zero rows — returning no products at all. - - **Fix**: Replaced `meta_query` with correct `tax_query` using `product_type` taxonomy with `NOT IN` operator. - - **Why v1.1.11-v1.1.14 failed**: All previous fix attempts addressed symptoms (variable products, stock filtering, debug logging) but never examined the actual WP_Query construction. The meta_query was the root cause all along. - -2. **Cart price always 0.00** - - **Root cause**: `includes/Cart_Handler.php` stored a `composable_price_calculated` flag in the cart session. On the next page load (cart, checkout), this flag was restored from session and prevented price recalculation — but `set_price()` only modifies the in-memory product object and is lost between requests. - - **Fix**: Removed the per-item `composable_price_calculated` flag entirely. The existing `static $already_calculated` flag already handles the "don't run twice in the same request" concern. - -3. **Admin tab rendering on initial page load** - - **Root cause**: JavaScript called `$('#composable_product_data').show()` which made the composable panel visible without hiding the General panel that WooCommerce shows by default. - - **Fix**: Trigger WooCommerce's native tab click (`$('ul.product_data_tabs li.composable_options a').trigger('click')`) so the tab system handles panel visibility correctly. - -**New feature:** - -4. **Gitea CI/CD release workflow** (`.gitea/workflows/release.yml`) - - Triggered on `v*` tags - - Installs PHP 8.3, Composer deps (production), compiles translations - - Verifies plugin version matches tag - - Builds release ZIP with proper WordPress directory structure - - Generates SHA-256 checksums, verifies package structure - - Creates Gitea release with ZIP and checksum attachments - - Uses `SRC_GITEA_TOKEN` secret for Gitea API - -**Files modified:** - -- includes/Product_Type.php: Replaced `meta_query` with `tax_query`, removed debug logging -- includes/Cart_Handler.php: Removed `composable_price_calculated` session flag -- assets/js/admin.js: Use native WooCommerce tab click instead of manual panel toggle - -**Files created:** - -- .gitea/workflows/release.yml: Gitea CI/CD release workflow - -**Key lessons learned:** - -1. **WooCommerce stores product types in taxonomy, not postmeta**: This is the single most important lesson from the entire v1.1.11-v1.1.14 debugging saga. `_product_type` does NOT exist in `wp_postmeta` — product types are terms in the `product_type` taxonomy. - -2. **WP_Query `!=` on non-existent meta keys returns zero results**: When you use `'compare' => '!='` in a meta_query, WordPress generates an `INNER JOIN` that only matches posts having that meta key. Posts without the key are excluded entirely. - -3. **Don't persist calculation flags in cart sessions**: `set_price()` only modifies in-memory objects. Any flag that prevents recalculation must NOT be stored in session data — use request-scoped variables (like `static`) instead. - -4. **Use native WooCommerce UI mechanisms**: For tab/panel visibility, trigger WooCommerce's own click handlers rather than manually toggling visibility. WooCommerce's tab system handles hiding all other panels automatically. - -5. **Read the actual query, not just the results**: v1.1.11-v1.1.14 all tried to fix what happened AFTER the query (stock filtering, variation expansion, debug logging), but the query itself was the problem. - -**Status:** v1.2.0 released with all three bugs resolved and CI/CD automation added. - ---- - -**For AI Assistants:** - -When starting a new session on this project: - -1. Read this CLAUDE.md file first -2. Review IMPLEMENTATION.md for technical details -3. Check git log for recent changes -4. Verify you're on the `dev` branch before making changes -5. Run `composer install` if vendor/ is missing -6. Test changes before committing -7. Follow commit message format with Claude Code attribution -8. Update this session history section with learnings - -Always refer to this document when starting work on this project. Good luck! +1. Read this CLAUDE.md first +2. Check git log for recent changes +3. Verify you're on the `dev` branch before making changes +4. Run `composer install` if vendor/ is missing +5. Test changes before committing +6. Follow commit message format with Claude Code attribution +7. Always use `tax_query` (not `meta_query`) for product type filtering diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md deleted file mode 100644 index fc78855..0000000 --- a/IMPLEMENTATION.md +++ /dev/null @@ -1,434 +0,0 @@ -# WooCommerce Composable Products - Implementation Summary - -## Overview - -This document provides a technical overview of the WooCommerce Composable Products plugin implementation. - -**Version:** 1.0.0 -**Created:** 2024-12-31 -**AI-Generated:** 100% created with Claude.AI assistance - -## Architecture - -### Plugin Structure - -```txt -wc-composable-product/ -├── assets/ # Frontend assets -│ ├── css/ -│ │ ├── admin.css # Admin styles -│ │ └── frontend.css # Frontend styles -│ └── js/ -│ ├── admin.js # Admin JavaScript -│ └── frontend.js # Frontend JavaScript -├── cache/ # Twig template cache -├── includes/ # PHP classes -│ ├── Admin/ -│ │ ├── Product_Data.php # Product data tab -│ │ └── Settings.php # Settings page -│ ├── Cart_Handler.php # Cart integration -│ ├── Plugin.php # Main plugin class -│ ├── Product_Selector.php # Frontend selector -│ └── Product_Type.php # Custom product type -├── languages/ # Translation files -│ └── wc-composable-product.pot -├── templates/ # Twig templates -│ └── product-selector.twig -└── wc-composable-product.php # Main plugin file -``` - -## Core Components - -### 1. Main Plugin Class (`Plugin.php`) - -**Responsibilities:** - -- Singleton pattern implementation -- Twig template engine initialization -- Hook registration -- Component initialization -- Asset enqueuing - -**Key Methods:** - -- `instance()`: Get singleton instance -- `init_twig()`: Initialize Twig with WordPress functions -- `render_template()`: Render Twig templates -- `add_product_type()`: Register composable product type - -### 2. Product Type (`Product_Type.php`) - -**Extends:** `WC_Product` - -**Key Features:** - -- Custom product type: `composable` -- Selection limit management (per-product or global) -- Pricing mode (fixed or sum) -- Product selection criteria (category/tag/SKU) -- Dynamic product availability -- Price calculation - -**Key Methods:** - -- `get_selection_limit()`: Get max selectable items -- `get_pricing_mode()`: Get pricing calculation mode -- `get_available_products()`: Query available products -- `calculate_composed_price()`: Calculate final price - -### 3. Admin Settings (`Admin/Settings.php`) - -**Extends:** `WC_Settings_Page` - -**Global Settings:** - -- Default selection limit -- Default pricing mode -- Display options (images, prices, total) - -**Integration:** Adds tab to WooCommerce Settings - -### 4. Product Data Tab (`Admin/Product_Data.php`) - -**Responsibilities:** - -- Add "Composable Options" tab to product edit page -- Render selection criteria fields -- Save product meta data -- Dynamic field visibility based on criteria type - -**Saved Meta:** - -- `_composable_selection_limit`: Item limit -- `_composable_pricing_mode`: Pricing calculation -- `_composable_criteria_type`: Selection method -- `_composable_categories`: Selected categories -- `_composable_tags`: Selected tags -- `_composable_skus`: SKU list - -### 5. Product Selector (`Product_Selector.php`) - -**Responsibilities:** - -- Render frontend product selection interface -- Prepare data for Twig template -- Apply display settings - -**Template Variables:** - -- `products`: Available products array -- `selection_limit`: Max selections -- `pricing_mode`: Pricing calculation -- `show_images/prices/total`: Display flags - -### 6. Cart Handler (`Cart_Handler.php`) - -**Responsibilities:** - -- Validate product selection -- Add selected products to cart data -- Calculate dynamic pricing -- Display selected products in cart - -**Hooks:** - -- `woocommerce_add_to_cart_validation`: Validate selections -- `woocommerce_add_cart_item_data`: Store selections -- `woocommerce_before_calculate_totals`: Update prices -- `woocommerce_get_item_data`: Display in cart - -## Frontend Implementation - -### Product Selector Template (`product-selector.twig`) - -**Features:** - -- Responsive grid layout -- Checkbox-based selection -- Product images and prices -- Real-time total calculation -- AJAX add-to-cart - -**Data Attributes:** - -- `data-product-id`: Composable product ID -- `data-selection-limit`: Max selections -- `data-pricing-mode`: Pricing mode -- `data-price`: Individual product prices - -### JavaScript (`frontend.js`) - -**Functionality:** - -- Selection limit enforcement -- Visual feedback on selection -- Real-time price updates (sum mode) -- AJAX cart operations -- Error/success messages - -**Key Functions:** - -- `handleCheckboxChange()`: Selection logic -- `updateTotalPrice()`: Calculate total -- `addToCart()`: AJAX add-to-cart -- `showMessage()`: User feedback - -### CSS Styling - -**Approach:** - -- Grid-based layout (responsive) -- Card-style product items -- Visual selection states -- Mobile-first design -- Breakpoints: 768px, 480px - -## Data Flow - -### Creating a Composable Product - -1. Admin selects "Composable product" type -2. Configure selection limit and pricing mode -3. Choose selection criteria (category/tag/SKU) -4. Save product metadata -5. WooCommerce registers product with custom type - -### Frontend Display - -1. Customer visits product page -2. `Cart_Handler` renders `Product_Selector` -3. `Product_Type::get_available_products()` queries products -4. Twig template renders grid with products -5. JavaScript handles interactions - -### Adding to Cart - -1. Customer selects products (JavaScript validation) -2. Click "Add to Cart" button -3. AJAX request with selected product IDs -4. `Cart_Handler::validate_add_to_cart()` validates -5. `Cart_Handler::add_cart_item_data()` stores selections -6. `Cart_Handler::calculate_cart_item_price()` updates price -7. Product added to cart with custom data - -### Cart Display - -1. WooCommerce loads cart -2. `Cart_Handler::get_cart_item_from_session()` restores data -3. `Cart_Handler::display_cart_item_data()` shows selections -4. Price calculated dynamically on each cart load - -## Security Implementation - -### Input Sanitization - -- **Integers:** `absint()` for IDs and limits -- **Text:** `sanitize_text_field()` for modes and types -- **Textarea:** `sanitize_textarea_field()` for SKUs -- **Arrays:** `array_map()` with sanitization functions - -### Output Escaping - -- **HTML:** `esc_html()`, `esc_html_e()` -- **Attributes:** `esc_attr()` -- **URLs:** `esc_url()` -- **JavaScript:** Localized scripts with escaped data - -### Validation - -- Selection limit enforcement -- Product availability verification -- Cart data validation -- Nonce verification (via WooCommerce) - -## Internationalization - -### Text Domain - -`wc-composable-product` - -### Translation Functions - -- `__()`: Return translated string -- `_e()`: Echo translated string -- `sprintf()` with `__()`: Variable substitution - -### POT File - -Generated template: `languages/wc-composable-product.pot` - -**Supported Locales (per CLAUDE.md):** - -- en_US (English) -- de_DE, de_DE_informal (German - Germany) -- de_CH, de_CH_informal (German - Switzerland) -- fr_CH (French - Switzerland) -- it_CH (Italian - Switzerland) - -## Performance Considerations - -### Caching - -- Twig templates cached in `cache/` directory -- Auto-reload enabled in debug mode -- Optimized Composer autoloader - -### Database Queries - -- Efficient `WP_Query` for product selection -- Meta queries for SKU filtering -- Taxonomy queries for category/tag filtering - -### Asset Loading - -- Scripts only on relevant pages -- Minification ready (use build tools) -- Conditional enqueuing - -## Extensibility - -### Hooks & Filters - -**Available Filters:** - -- `wc_composable_settings`: Modify settings array -- `woocommerce_product_class`: Custom product class -- `product_type_selector`: Product type registration - -**Customization Points:** - -- Twig templates (override in theme) -- CSS styling (enqueue custom styles) -- JavaScript behavior (extend object) - -### Developer API - -```php -// Get composable product -$product = wc_get_product($product_id); - -// Check if composable -if ($product->get_type() === 'composable') { - // Get available products - $products = $product->get_available_products(); - - // Get selection limit - $limit = $product->get_selection_limit(); - - // Calculate price - $price = $product->calculate_composed_price($selected_ids); -} -``` - -## Testing Checklist - -### Admin Testing - -- [ ] Product type appears in dropdown -- [ ] Composable Options tab displays -- [ ] Selection criteria toggle works -- [ ] Meta data saves correctly -- [ ] Settings page accessible -- [ ] Global defaults apply - -### Frontend Testing - -- [ ] Product selector renders -- [ ] Selection limit enforced -- [ ] Price calculation accurate (both modes) -- [ ] AJAX add-to-cart works -- [ ] Cart displays selections -- [ ] Checkout processes correctly - -### Edge Cases - -- [ ] Empty criteria (no products) -- [ ] Out of stock products excluded -- [ ] Invalid product selections rejected -- [ ] Multiple cart items unique -- [ ] Session persistence - -## Known Limitations - -1. **Variable Products:** Currently supports simple products in selection -2. **Grouped Products:** Cannot be used as selectable items -3. **Stock Management:** No automatic stock reduction for selected items -4. **Caching:** Template cache needs manual clearing after updates - -## Future Enhancements - -Potential features for future versions: - -- Variable product support in selection -- Quantity selection per item (not just presence) -- Visual bundle previews -- Advanced pricing rules -- Stock management integration -- Product recommendations -- Selection templates/presets -- Multi-currency support enhancements - -## Dependencies - -### Runtime - -- PHP 8.3+ -- WordPress 6.0+ -- WooCommerce 8.0+ -- Twig 3.0 (via Composer) - -### Development - -- Composer for dependency management -- WP-CLI for i18n operations (optional) - -## Deployment - -### Production Checklist - -1. Run `composer install --no-dev --optimize-autoloader` -2. Ensure `vendor/` directory is included -3. Ensure `cache/` directory is writable -4. Test on staging environment -5. Clear all caches after activation -6. Verify WooCommerce compatibility - -### Release Package - -Must include: - -- All PHP files -- `vendor/` directory -- Assets (CSS, JS) -- Templates -- Language files -- Documentation - -Must exclude: - -- `.git/` directory -- `composer.lock` -- Development files -- `wp-core/`, `wp-plugins/` symlinks - -## Support & Maintenance - -### Code Standards - -- WordPress Coding Standards -- WooCommerce best practices -- PSR-4 autoloading -- Inline documentation - -### Version Control - -- Semantic versioning (MAJOR.MINOR.PATCH) -- Changelog maintained -- Annotated git tags -- Development on `dev` branch - ---- - -**Last Updated:** 2024-12-31 -**Maintainer:** Marco Graetsch -**AI Assistant:** Claude.AI (Anthropic) diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index dae9bc0..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,150 +0,0 @@ -# Installation Guide - -## Requirements - -Before installing the WooCommerce Composable Products plugin, ensure your system meets these requirements: - -- **PHP**: 8.3 or higher -- **WordPress**: 6.0 or higher -- **WooCommerce**: 8.0 or higher -- **Composer**: For dependency management - -## Installation Steps - -### 1. Upload Plugin Files - -Upload the plugin directory to your WordPress installation: - -```bash -/wp-content/plugins/wc-composable-product/ -``` - -### 2. Install Dependencies - -Navigate to the plugin directory and install dependencies: - -```bash -cd /wp-content/plugins/wc-composable-product/ -composer install --no-dev --optimize-autoloader -``` - -### 3. Activate Plugin - -1. Log in to your WordPress admin panel -2. Navigate to **Plugins > Installed Plugins** -3. Find "WooCommerce Composable Products" -4. Click **Activate** - -### 4. Configure Settings - -After activation, configure the plugin: - -1. Navigate to **WooCommerce > Settings** -2. Click on the **Composable Products** tab -3. Configure default settings: - - **Default Selection Limit**: Number of items customers can select (default: 5) - - **Default Pricing Mode**: Choose between "Sum of selected products" or "Fixed price" - - **Display Options**: Toggle product images, prices, and totals - -## Creating Your First Composable Product - -### Step 1: Create a New Product - -1. Go to **Products > Add New** -2. Enter a product name (e.g., "Custom Sticker Pack") - -### Step 2: Set Product Type - -1. In the **Product Data** panel, select **Composable product** from the dropdown - -### Step 3: Configure General Settings - -In the **General** tab: -- Set a **Regular price** (used if pricing mode is "Fixed") -- Configure **Selection Limit** (leave empty to use global default) -- Choose **Pricing Mode** (leave empty to use global default) - -### Step 4: Configure Composable Options - -Click on the **Composable Options** tab: - -1. **Selection Criteria**: Choose how to select available products - - **By Category**: Select product categories - - **By Tag**: Select product tags - - **By SKU**: Enter comma-separated SKUs - -2. Based on your selection: - - **Categories**: Select one or more categories from the dropdown - - **Tags**: Select one or more tags from the dropdown - - **SKUs**: Enter SKUs like: `STICKER-01, STICKER-02, STICKER-03` - -### Step 5: Publish - -Click **Publish** to make your composable product live. - -## Frontend Usage - -When customers visit your composable product: - -1. They see a grid of available products based on your criteria -2. They can select up to the configured limit -3. The total price updates in real-time (if using sum pricing mode) -4. Click "Add to Cart" to add the composition to their cart -5. Selected products are displayed in the cart - -## Troubleshooting - -### Plugin Won't Activate - -- Ensure WooCommerce is installed and activated first -- Check PHP version (must be 8.3+) -- Verify Composer dependencies are installed - -### Products Not Showing in Selector - -- Check that products are published and in stock -- Verify the selection criteria (category/tag/SKU) is correct -- Ensure products match the criteria you configured - -### Twig Template Errors - -- Ensure the `vendor/` directory exists and contains Twig -- Run `composer install` again -- Check that the `cache/` directory is writable - -### JavaScript Not Working - -- Clear browser cache -- Check browser console for errors -- Ensure jQuery is loaded (WooCommerce includes it) - -## Updating - -When updating the plugin: - -1. Deactivate the plugin -2. Replace plugin files -3. Run `composer install --no-dev --optimize-autoloader` -4. Reactivate the plugin -5. Clear all caches (WordPress, browser, CDN) - -## Uninstallation - -To completely remove the plugin: - -1. Deactivate the plugin -2. Delete the plugin from the Plugins page -3. Optionally clean up database entries (WooCommerce will handle this automatically) - -## Support - -For issues and feature requests: -- GitHub: https://github.com/magdev/wc-composable-product/issues -- Documentation: See README.md - -## Next Steps - -- Customize the template by editing `templates/product-selector.twig` -- Modify styles in `assets/css/frontend.css` -- Translate the plugin using the provided `.pot` file -- Create categories/tags for easier product organization diff --git a/README.md b/README.md index 6305de6..c4f71e3 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ # WooCommerce Composable Products -Create composable products where customers can select a limited number of items from a configurable set of products. +Create composable products where customers can select a limited number of items from a configurable set of products. Think of it as a "build your own gift box" or "create your sticker pack" feature. -## Description - -This plugin adds a new product type to WooCommerce that allows customers to build their own product bundles by selecting from a predefined set of simple or variable products. Think of it as a "build your own gift box" or "create your sticker pack" feature. - -### Key Features +## Key Features - **Custom Product Type**: New "Composable Product" type in WooCommerce - **Flexible Selection**: Define available products by category, tag, or SKU @@ -23,33 +19,94 @@ This plugin adds a new product type to WooCommerce that allows customers to buil - PHP 8.3 or higher - WordPress 6.0 or higher - WooCommerce 8.0 or higher +- Composer (for dependency management) ## Installation -1. Upload the plugin files to `/wp-content/plugins/wc-composable-product/` -2. Run `composer install --no-dev` in the plugin directory -3. Activate the plugin through the 'Plugins' menu in WordPress -4. Configure global settings under WooCommerce > Settings > Composable Products +### From Release Package + +1. Download the latest release ZIP from the releases page +2. In WordPress admin, go to **Plugins > Add New > Upload Plugin** +3. Upload the ZIP file and click **Install Now** +4. Activate the plugin through the **Plugins** menu +5. Configure global settings under **WooCommerce > Settings > Composable Products** + +### From Source + +1. Upload the plugin directory to `/wp-content/plugins/wc-composable-product/` +2. Install dependencies: + + ```bash + cd /wp-content/plugins/wc-composable-product/ + composer install --no-dev --optimize-autoloader + ``` + +3. Activate the plugin through the **Plugins** menu in WordPress +4. Configure global settings under **WooCommerce > Settings > Composable Products** ## Usage -### Creating a Composable Product - -1. Go to Products > Add New -2. Select "Composable Product" as the product type -3. Configure product details: - - Set the selection limit (or use global default) - - Choose pricing mode (fixed or sum) - - Define available products by category, tag, or SKU -4. Publish the product - ### Global Settings -Navigate to WooCommerce > Settings > Composable Products to configure: +Navigate to **WooCommerce > Settings > Composable Products** to configure: -- Default selection limit -- Default pricing mode -- Display options +- **Default Selection Limit**: Number of items customers can select (default: 5) +- **Default Pricing Mode**: Choose between "Sum of selected products" or "Fixed price" +- **Display Options**: Toggle product images, prices, and totals + +### Creating a Composable Product + +1. Go to **Products > Add New** +2. Select **Composable product** from the Product Data dropdown +3. In the **General** tab: + - Set a **Regular price** (used when pricing mode is "Fixed") + - Configure **Selection Limit** (leave empty to use global default) + - Choose **Pricing Mode** (leave empty to use global default) +4. Click the **Composable Options** tab: + - **Selection Criteria**: Choose how to define available products + - **By Category**: Select one or more product categories + - **By Tag**: Select one or more product tags + - **By SKU**: Enter comma-separated SKUs (e.g., `STICKER-01, STICKER-02`) +5. Click **Publish** + +### Frontend Behavior + +When customers visit a composable product page: + +1. A grid of available products is displayed based on configured criteria +2. Customers select up to the configured limit via checkboxes +3. Total price updates in real-time (in sum pricing mode) +4. Stock indicators show availability (green/orange/red badges) +5. Click "Add to Cart" to add the composition to cart +6. Selected products are listed in the cart and checkout + +## Troubleshooting + +### Plugin Won't Activate + +- Ensure WooCommerce is installed and activated first +- Check PHP version (must be 8.3+) +- Verify Composer dependencies are installed (`vendor/` directory exists) + +### Products Not Showing in Selector + +- Check that products are published +- Verify the selection criteria (category/tag/SKU) matches existing products +- Ensure the criteria type and values are saved in the Composable Options tab + +### Twig Template Errors + +- Ensure the `vendor/` directory exists and contains Twig +- Run `composer install` again +- Check that the `cache/` directory is writable + +### Updating + +1. Deactivate the plugin +2. Replace plugin files (or upload new release ZIP) +3. If installed from source: run `composer install --no-dev --optimize-autoloader` +4. Reactivate the plugin +5. Clear all caches (WordPress, browser, CDN) ## Development @@ -96,4 +153,4 @@ Marco Graetsch ## Support -For issues and feature requests, please use the GitHub issue tracker. +For issues and feature requests, please use the issue tracker. diff --git a/wc-composable-product.php b/wc-composable-product.php index df198f2..596f9b7 100644 --- a/wc-composable-product.php +++ b/wc-composable-product.php @@ -4,7 +4,7 @@ * Plugin Name: WooCommerce Composable Products * Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-composable-product * Description: Create composable products where customers select a limited number of items from a configurable set - * Version: 1.2.0 + * Version: 1.2.1 * Author: Marco Graetsch * Author URI: https://src.bundespruefstelle.ch/magdev * License: GPL v3 or later @@ -20,7 +20,7 @@ defined('ABSPATH') || exit; // Define plugin constants -define('WC_COMPOSABLE_PRODUCT_VERSION', '1.2.0'); +define('WC_COMPOSABLE_PRODUCT_VERSION', '1.2.1'); define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__); define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__)); define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__));