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.') }}
-