Document the critical bug fix journey for the WC_Settings_Page error: v1.1.1 session (failed attempt): - Attempted to fix with hook timing change - Changed to woocommerce_init hook - Bug persisted - error logs showed it still failed - Lesson: Always verify fixes with error logs v1.1.2 session (successful fix): - Discovered root cause: Settings.php loaded too early - require_once happened during Plugin::includes() - PHP tried to parse 'extends WC_Settings_Page' before class existed - Solution: Delayed require_once to add_settings_page() callback - This callback runs when WC has loaded all settings classes Key learnings documented: 1. Class loading order matters more than hook timing 2. Always verify fixes don't just assume they work 3. Lazy loading pattern for extending third-party classes 4. Read error logs thoroughly - backtrace reveals the sequence 5. Don't assume hook order means all classes are loaded 6. Test immediately after each release Also documents the debugging approach that worked and the 5-version journey to finally fix this persistent issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
22 KiB
WooCommerce plugin for user 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.
Key Fact: 100% AI-Generated
This project is proudly "vibe-coded" using Claude.AI - the entire codebase was created through AI assistance.
Technical Stack
- Language: PHP 8.3+
- Framework: Latest 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
ABSPATHcheck
Translation Ready
All user-facing strings use:
__('Text to translate', 'wc-composable-product')
_e('Text to translate', 'wc-composable-product')
Text domain: wc-composable-product
Translation Template:
- Base
.potfile 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) ✓ Completede_DE_informal- German (Germany, informal "du") ✓ Completede_CH- German (Switzerland, formal "Sie") ✓ Completede_CH_informal- German (Switzerland, informal "du") ✓ Completefr_CH- French (Switzerland) ✓ Completeit_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:
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 -land test extraction before committing - The
wp-core/andwp-plugins/directories MUST NOT be included in releases - Releases are stored in
releases/including checksums
Important Git Notes:
- Always commit from
devbranch 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.jsonchanges 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(butvendor/is included)
Project Structure
wc-composable-product/
├── assets/
│ ├── css/
│ │ ├── admin.css # Admin panel styling
│ │ └── frontend.css # Customer-facing styles (with stock indicators)
│ └── js/
│ ├── admin.js # Product edit interface logic
│ └── frontend.js # AJAX cart & selection UI
├── cache/ # Twig template cache (writable)
├── includes/
│ ├── Admin/
│ │ ├── Product_Data.php # Product data tab & meta boxes
│ │ └── Settings.php # WooCommerce settings integration
│ ├── Cart_Handler.php # Add-to-cart & cart display logic (with stock validation)
│ ├── 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
├── 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.
Architecture Overview
Core Classes
-
Plugin.php - Main singleton class
- Initializes Twig template engine
- Registers hooks and filters
- Manages asset enqueuing
- Provides template rendering API
-
Product_Type.php - Custom WooCommerce product type
- Extends
WC_Product - Handles selection criteria (category/tag/SKU)
- Manages pricing modes (fixed/sum)
- Queries available products dynamically
- Extends
-
Cart_Handler.php - Cart integration
- Validates product selections
- Stores selected products in cart meta
- Calculates dynamic pricing
- Displays selections in cart/checkout
-
Product_Selector.php - Frontend renderer
- Renders Twig template with product data
- Applies display settings (images/prices/total)
-
Admin/Product_Data.php - Product edit interface
- Adds "Composable Options" tab
- Category/tag/SKU selection fields
- Saves product metadata
-
Admin/Settings.php - Global settings
- WooCommerce settings tab integration
- Default limits and pricing mode
- Display preferences
-
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
Data Flow
Product Creation:
- Admin selects "Composable product" type
- Configures criteria, limits, pricing in product data tab
- Metadata saved:
_composable_*fields
Frontend Display:
Cart_Handler::render_product_selector()called on product pageProduct_Type::get_available_products()queries matching productsProduct_Selector::render()passes data to Twig template- JavaScript handles selection UI and AJAX
Add to Cart:
- Customer selects products (JS validates limit)
- AJAX request with
composable_products[]array Cart_Handler::validate_add_to_cart()server-side validationStock_Manager::validate_stock_availability()checks stock levels (v1.1.0+)Cart_Handler::add_cart_item_data()stores selectionsCart_Handler::calculate_cart_item_price()applies pricing
Order Processing (v1.1.0+):
- Order status changes to completed/processing
Stock_Manager::reduce_stock_on_order_complete()deducts inventory- Selected product IDs stored in order meta:
_composable_products - Order notes added documenting stock changes
- On cancellation/refund:
Stock_Manager::restore_stock_on_order_cancel()reverses deduction
Development Workflow
Initial Setup
composer install
Making Changes
- PHP Classes: Edit files in
includes/orincludes/Admin/ - Templates: Modify
templates/*.twig(cache clears on auto-reload) - Styles: Update
assets/css/*.css - JavaScript: Edit
assets/js/*.js - Translations: Update
.potfile, create.potranslations
Testing Checklist
- 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
Creating Releases
# From project root
zip -r 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!
Session History
v1.0.0 - Initial Implementation & Release (2024-12-31)
Session 1: Core Implementation
- 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
mainbranch (1edb0be)
Session 2: Documentation & Translations
- 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
Session 3: Release Creation
- 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)
Key decisions made:
- 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
Files created: 28 files total (21 PHP/templates + 7 translations), 3,842 lines of code
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:
-
New Stock_Manager class (includes/Stock_Manager.php - 7.7 KB, 263 lines)
validate_stock_availability()- Real-time stock checkingget_product_stock_info()- Stock data for frontend displayreduce_stock_on_order_complete()- Automatic deduction on order completionrestore_stock_on_order_cancel()- Automatic restoration on cancellation/refundprevent_composable_stock_reduction()- Prevents WooCommerce double-deductionstore_selected_products_in_order()- Saves selection to order meta
-
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)
-
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
-
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:
- Stock Manager Pattern: Separate class for inventory logic keeps Cart_Handler focused on cart operations
- Order Meta Storage: Storing selected product IDs in order meta enables accurate stock operations even after order placement
- Hook Priority: Must prevent WooCommerce's default stock reduction for composable products since we handle it manually
- Visual Feedback: Color-coded stock badges (green/orange/red) provide immediate clarity to customers
- Audit Trail: Order notes are crucial for debugging stock discrepancies
- Defensive Programming: Check for
_composable_stock_reducedflag 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:
- v1.0.0: Used
plugins_loadedhook → Fatal error - v1.0.1: Changed to
woocommerce_loaded→ Still failed - v1.1.0: Kept
woocommerce_loaded→ Bug continued - v1.1.1: Changed to
woocommerce_init→ STILL FAILING! - 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 doingrequire_once Settings.phpat 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_initfires before WooCommerce loads settings page classes - Result: Instant fatal error
The fix:
Delayed Settings.php inclusion until it's actually needed:
// 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.phpinadd_settings_page()method
- Line 93: Removed
- 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:
- 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
- Always Verify Fixes: v1.1.1 was released thinking the hook change fixed it, but checking the error logs revealed it still failed
- Lazy Loading Pattern: When extending third-party classes, defer
require_onceuntil you're certain the parent class exists - Read Error Logs Thoroughly: The backtrace showed the exact sequence -
woocommerce_initfired, then our code required Settings.php, then PHP crashed trying to parse theextendsstatement - 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
- 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_oncehappens at call time, not when callback runs - Moved
require_onceto the actual callback when WC guarantees class exists - Verified fix with PHP syntax check before release
For AI Assistants:
When starting a new session on this project:
- Read this CLAUDE.md file first
- Review IMPLEMENTATION.md for technical details
- Check git log for recent changes
- Verify you're on the
devbranch before making changes - Run
composer installif vendor/ is missing - Test changes before committing
- Follow commit message format with Claude Code attribution
- Update this session history section with learnings
Always refer to this document when starting work on this project. Good luck!