You've already forked wc-composable-product
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85983d5473 | |||
| 252b187600 | |||
| 8185a77697 | |||
| 6c2e317230 | |||
| 58f5329bc4 | |||
| 0767016370 | |||
| fa7ec0e422 | |||
| f4d2543d4e | |||
| 9e4513f911 | |||
| 4dc7b767a8 | |||
| f763e35d19 | |||
| 8b271c90c0 | |||
| 0dd4408b23 | |||
| 7a4a0a0135 | |||
| c6a48d6404 | |||
| ac1cb9b135 | |||
| f5bc0d0335 | |||
| 88a907c4dd | |||
| 03a7624564 | |||
| 1c3f44f3c2 | |||
| 287f8b778b | |||
| 63d8f9ed52 | |||
| 601570d724 | |||
| e9b2d1c79b | |||
| d27dd4b7bd | |||
| 1b7c7a0257 | |||
| 4f65c8e5e0 | |||
| 054617f320 | |||
| 8fc0614334 | |||
| 867abc8f63 | |||
| 818fd51502 | |||
| 392559dedc | |||
| 17d5312df3 | |||
| 037be97ece | |||
| 28d2223306 |
213
CHANGELOG.md
213
CHANGELOG.md
@@ -5,6 +5,219 @@ 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.1.11] - 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- **FEATURE**: Variable product support - composable products can now include variable products and their variations
|
||||
- Variable products automatically expand to show all available variations as selectable items
|
||||
- Each variation displays with full attribute information (e.g., "Product - Color: Red, Size: Large")
|
||||
|
||||
### Fixed
|
||||
|
||||
- Products not showing in selector when all available products were variable products
|
||||
- Variable products were being filtered out because parent products aren't directly purchasable
|
||||
|
||||
### Changed
|
||||
|
||||
- Modified `get_available_products()` to detect and handle variable products
|
||||
- Variable products now expand into their individual variations
|
||||
- Each variation checked individually for stock status and purchasability
|
||||
- Simple products continue to work exactly as before
|
||||
|
||||
### Technical
|
||||
|
||||
- Modified file: includes/Product_Type.php (lines 160-188)
|
||||
- Added logic to detect `is_type('variable')` products
|
||||
- Uses `get_available_variations()` to retrieve all variations
|
||||
- Each variation validated with `is_in_stock()` and `is_purchasable()`
|
||||
- Maintains backward compatibility with simple products
|
||||
|
||||
### Notes
|
||||
|
||||
- This is a feature enhancement release, not a bug fix
|
||||
- Resolves the issue where categories containing only variable products showed no selections
|
||||
- Variations display with their parent product name plus selected attributes
|
||||
- Stock management works correctly for both simple products and variations
|
||||
- All translation files remain at 100% completion (57/57 strings - no new strings added)
|
||||
|
||||
## [1.1.10] - 2025-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL**: Admin panel - Both General and Composable tabs visible simultaneously on initial page load
|
||||
- **CRITICAL**: Frontend - No products showing in product selector, only cart button and pricing visible
|
||||
- Empty product grid now shows helpful message instead of blank space
|
||||
|
||||
### Changed
|
||||
|
||||
- Added explicit `display: none` to `#composable_product_data` panel for proper initial hiding
|
||||
- Panel now only shows when `body.product-type-composable` class is present
|
||||
- Added empty state message in product selector template when no products are configured
|
||||
- Cleared Twig cache to ensure template changes take effect
|
||||
|
||||
### Added
|
||||
|
||||
- Empty state message: "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
- Translations for empty state message in all 6 supported locales (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH, it_CH)
|
||||
- Recompiled .mo translation files
|
||||
|
||||
### Technical
|
||||
|
||||
- Modified files: assets/css/admin.css (lines 7-16), templates/product-selector.twig (lines 12-15)
|
||||
- Root cause (admin): Panel lacked explicit CSS hiding rule, relied only on `hidden` class
|
||||
- Root cause (frontend): No feedback when products array is empty
|
||||
- Solution: CSS specificity + empty state conditional in Twig template
|
||||
|
||||
### Notes
|
||||
|
||||
- This release fixes two critical bugs discovered immediately after v1.1.9
|
||||
- Admin interface now correctly hides composable panel until product type is selected
|
||||
- Frontend provides clear user feedback when product selection is unavailable
|
||||
- All translation files now 100% complete (57/57 strings)
|
||||
|
||||
## [1.1.9] - 2025-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL**: Admin rendering completely broken - tabs disappeared and fields appeared out of context after v1.1.8 release
|
||||
- CSS selectors were too broad, hiding tab navigation along with field groups
|
||||
- Removed `!important` flags that caused overly aggressive hiding
|
||||
|
||||
### Changed
|
||||
|
||||
- Made CSS selectors more specific: `.options_group.show_if_composable` for field groups only
|
||||
- Added separate rule for tab links: `.product_data_tabs li.composable_options`
|
||||
- Tab navigation now works correctly without hiding itself
|
||||
|
||||
### Technical
|
||||
|
||||
- Modified files: assets/css/admin.css (lines 22-40)
|
||||
- Root cause: `.show_if_composable` class used by WooCommerce for both tab links AND field groups
|
||||
- Solution: Separate selectors for each use case to prevent unintended hiding
|
||||
|
||||
### Notes
|
||||
|
||||
- This release fixes critical regression introduced in v1.1.8
|
||||
- Admin interface now renders correctly with visible tabs and properly positioned fields
|
||||
- No `!important` flags needed with specific selectors
|
||||
|
||||
## [1.1.8] - 2025-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL**: Admin rendering bug where both General and Composable Options tabs showed simultaneously on initial page load
|
||||
- **CRITICAL**: Frontend product selector not appearing on product pages - WooCommerce's default add-to-cart button now hidden for composable products
|
||||
- **CRITICAL**: Price formatting not localized - prices now display with proper currency symbols, decimal separators, and thousand separators for all locales
|
||||
|
||||
### Added
|
||||
|
||||
- `wc_price()` Twig function for proper price formatting in templates
|
||||
- `formatPrice()` JavaScript method with full WooCommerce locale support
|
||||
- Price format localization data passed to frontend JavaScript (decimal/thousand separators, currency position, number of decimals)
|
||||
- `hide_default_add_to_cart()` method to prevent WooCommerce's default purchase UI for composable products
|
||||
|
||||
### Changed
|
||||
|
||||
- Enhanced CSS specificity with `!important` flags for proper tab visibility control
|
||||
- Template now uses `{{ fixed_price_html|raw }}` instead of raw currency concatenation
|
||||
- Product selector passes pre-formatted price HTML from `wc_price()` function
|
||||
- Frontend JavaScript updates prices dynamically using WooCommerce format settings
|
||||
|
||||
### Technical
|
||||
|
||||
- Modified files: assets/css/admin.css (+24 lines), includes/Cart_Handler.php (+14 lines), includes/Plugin.php (+7 lines), includes/Product_Selector.php (+2 lines), templates/product-selector.twig, assets/js/frontend.js (+28 lines)
|
||||
- All PHP files pass syntax validation
|
||||
- Supports Swiss format (CHF 50.-), European format (50,00 €), US format ($50.00), and all other WooCommerce locales
|
||||
- Thousand separator support: comma (1,000), dot (1.000), apostrophe (1'000), space (1 000)
|
||||
|
||||
### Notes
|
||||
|
||||
- This release fixes all three critical UI bugs reported in CLAUDE.md
|
||||
- Admin tabs now display correctly on initial page load without JavaScript flicker
|
||||
- Frontend product selector is now the only purchase interface (no WooCommerce default button)
|
||||
- All prices maintain proper locale formatting during dynamic updates
|
||||
|
||||
## [1.1.7] - 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- Compiled .mo translation files for all 6 supported locales (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH, it_CH)
|
||||
- WordPress can now load translations in admin and frontend areas
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL**: Missing translations in WordPress admin when using non-English locales
|
||||
- Settings page ("Composable Products", "Default Selection Limit", etc.) now properly translated
|
||||
- Product settings ("Composable Options", "Selection Criteria", etc.) now properly translated
|
||||
|
||||
### Technical
|
||||
|
||||
- Compiled .mo files from .po sources using msgfmt
|
||||
- All 6 locales now have complete translation coverage (56/56 strings translated and compiled)
|
||||
- .mo files required for WordPress i18n system to display translations
|
||||
|
||||
### Notes
|
||||
|
||||
- Previous versions included .po translation files but WordPress requires compiled .mo files
|
||||
- This release makes all existing translations actually visible to users
|
||||
|
||||
## [1.1.6] - 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- Complete translations for all admin area strings across all 6 supported locales
|
||||
- "Fixed Price" field label and description translations
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated translation template (.pot) to version 1.1.6
|
||||
- Simplified "How to calculate the price" description text
|
||||
|
||||
### Technical
|
||||
|
||||
- All .po files now include translations for v1.1.4 admin strings
|
||||
- 100% translation coverage maintained across all locales (56/56 strings)
|
||||
- German formal/informal variants properly differentiated (Sie vs. du)
|
||||
|
||||
## [1.1.5] - 2025-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL**: Fixed Twig template error "Unknown 'esc_attr' filter" when rendering product selector
|
||||
- Template compatibility issue when other plugins (e.g., WooCommerce Tier and Package Prices) use Twig
|
||||
- WordPress escaping functions now properly registered as both Twig functions AND filters
|
||||
|
||||
### Technical
|
||||
|
||||
- Added `TwigFilter` registrations for `esc_html`, `esc_attr`, and `esc_url` in `Plugin::init_twig()`
|
||||
- Template can now use both syntax styles: `{{ value|esc_attr }}` (filter) and `{{ esc_attr(value) }}` (function)
|
||||
- Prevents conflicts when multiple plugins bundle their own Twig installations
|
||||
|
||||
### Notes
|
||||
|
||||
- Previous versions only registered escaping functions as Twig functions, not filters
|
||||
- Template used filter syntax (`|esc_attr`) which failed when parsed by external Twig instances
|
||||
- Fix ensures compatibility regardless of which Twig instance processes the template
|
||||
|
||||
## [1.1.4] - 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- Fixed price field in Composable Options tab for easier price configuration
|
||||
- JavaScript toggle to show/hide fixed price field based on selected pricing mode
|
||||
|
||||
### Changed
|
||||
|
||||
- Simplified pricing mode description text in admin interface
|
||||
- Fixed price field now appears dynamically when "Fixed" pricing mode is selected
|
||||
|
||||
### Technical
|
||||
|
||||
- Added `_regular_price` field with `composable_fixed_price_field` CSS class in `Product_Data.php`
|
||||
- Implemented `toggleFixedPriceField()` JavaScript function in `assets/js/admin.js`
|
||||
- Progressive disclosure pattern improves admin UX by showing relevant fields only
|
||||
|
||||
## [1.1.3] - 2024-12-31
|
||||
|
||||
### Added
|
||||
|
||||
837
CLAUDE.md
837
CLAUDE.md
@@ -121,6 +121,7 @@ wc-composable-product/
|
||||
│ └── Stock_Manager.php # Stock management & inventory tracking (v1.1.0+)
|
||||
├── languages/
|
||||
│ └── wc-composable-product.pot # Translation template
|
||||
├── releases/ # Releases files
|
||||
├── templates/
|
||||
│ └── product-selector.twig # Frontend selection interface (with stock display)
|
||||
├── vendor/ # Composer dependencies (gitignored)
|
||||
@@ -247,6 +248,16 @@ unzip -l wc-composable-product-vX.X.X.zip
|
||||
# IMPORTANT: Ensure vendor/ is included!
|
||||
```
|
||||
|
||||
## Bugs found
|
||||
|
||||
- ✅ ~~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
|
||||
- The tab rendering is still no correct. first both tabs are shown on initial page load. After clicking a tab, they behave as expected.
|
||||
|
||||
## Session History
|
||||
|
||||
### v1.0.0 - Initial Implementation & Release (2024-12-31)
|
||||
@@ -576,6 +587,832 @@ Everything from v1.1.0 plus:
|
||||
|
||||
---
|
||||
|
||||
### 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 %}
|
||||
<span class="calculated-total">{{ zero_price_html|raw }}</span>
|
||||
{% 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', '<span class="woocommerce-Price-currencySymbol">' + format.currency_symbol + '</span>')
|
||||
.replace('%2$s', priceStr);
|
||||
|
||||
return '<span class="woocommerce-Price-amount amount">' + formatted + '</span>';
|
||||
},
|
||||
```
|
||||
|
||||
**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 %}
|
||||
<div class="composable-no-products">
|
||||
<p>{{ __('No products available for selection. Please configure the product criteria in the admin panel.') }}</p>
|
||||
</div>
|
||||
{% 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
|
||||
|
||||
---
|
||||
|
||||
**For AI Assistants:**
|
||||
|
||||
When starting a new session on this project:
|
||||
|
||||
@@ -4,10 +4,17 @@
|
||||
* @package WC_Composable_Product
|
||||
*/
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.composable_criteria_group {
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 12px;
|
||||
@@ -19,11 +26,23 @@
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.show_if_composable {
|
||||
/* Hide composable-specific elements by default (but not tabs) */
|
||||
.options_group.show_if_composable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.product-type-composable .show_if_composable {
|
||||
/* Show composable elements when composable product type is selected */
|
||||
body.product-type-composable .options_group.show_if_composable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Hide the Composable Options tab link by default */
|
||||
.product_data_tabs li.composable_options {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show the Composable Options tab when composable type selected */
|
||||
body.product-type-composable .product_data_tabs li.composable_options {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,23 @@
|
||||
$('#composable_criteria_' + criteriaType).show();
|
||||
}).trigger('change');
|
||||
|
||||
/**
|
||||
* Toggle fixed price field based on pricing mode
|
||||
*/
|
||||
function toggleFixedPriceField() {
|
||||
const pricingMode = $('#_composable_pricing_mode').val();
|
||||
const $fixedPriceField = $('.composable_fixed_price_field');
|
||||
|
||||
if (pricingMode === 'fixed') {
|
||||
$fixedPriceField.show();
|
||||
} else {
|
||||
$fixedPriceField.hide();
|
||||
}
|
||||
}
|
||||
|
||||
$('#_composable_pricing_mode').on('change', toggleFixedPriceField);
|
||||
toggleFixedPriceField();
|
||||
|
||||
/**
|
||||
* Initialize enhanced select for categories and tags
|
||||
*/
|
||||
|
||||
@@ -63,6 +63,36 @@
|
||||
this.clearMessages($container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Format price using WooCommerce settings
|
||||
*
|
||||
* @param {number} price Price amount
|
||||
* @return {string} Formatted price HTML
|
||||
*/
|
||||
formatPrice: function(price) {
|
||||
if (typeof wcComposableProduct === 'undefined' || !wcComposableProduct.price_format) {
|
||||
return price.toFixed(2);
|
||||
}
|
||||
|
||||
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 or "%2$s%1$s" for price+symbol)
|
||||
let formatted = format.price_format
|
||||
.replace('%1$s', '<span class="woocommerce-Price-currencySymbol">' + format.currency_symbol + '</span>')
|
||||
.replace('%2$s', priceStr);
|
||||
|
||||
return '<span class="woocommerce-Price-amount amount">' + formatted + '</span>';
|
||||
},
|
||||
|
||||
/**
|
||||
* Update total price
|
||||
*
|
||||
@@ -79,8 +109,8 @@
|
||||
}
|
||||
});
|
||||
|
||||
const currencySymbol = $container.find('.total-price').data('currency');
|
||||
$container.find('.calculated-total').text(currencySymbol + total.toFixed(2));
|
||||
const formattedPrice = this.formatPrice(total);
|
||||
$container.find('.calculated-total').html(formattedPrice);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,7 +63,7 @@ class Product_Data {
|
||||
woocommerce_wp_select([
|
||||
'id' => '_composable_pricing_mode',
|
||||
'label' => __('Pricing Mode', 'wc-composable-product'),
|
||||
'description' => __('How to calculate the price. Leave empty to use global default.', 'wc-composable-product'),
|
||||
'description' => __('How to calculate the price.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'options' => [
|
||||
'' => __('Use global default', 'wc-composable-product'),
|
||||
@@ -72,6 +72,16 @@ class Product_Data {
|
||||
],
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_regular_price',
|
||||
'label' => __('Fixed Price', 'wc-composable-product') . ' (' . get_woocommerce_currency_symbol() . ')',
|
||||
'description' => __('Enter the fixed price for this composable product.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'type' => 'text',
|
||||
'data_type' => 'price',
|
||||
'wrapper_class' => 'composable_fixed_price_field',
|
||||
]);
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,21 @@ class Cart_Handler {
|
||||
add_action('woocommerce_before_calculate_totals', [$this, 'calculate_cart_item_price']);
|
||||
add_action('woocommerce_single_product_summary', [$this, 'render_product_selector'], 25);
|
||||
add_action('woocommerce_checkout_create_order_line_item', [$this->stock_manager, 'store_selected_products_in_order'], 10, 3);
|
||||
add_filter('woocommerce_is_purchasable', [$this, 'hide_default_add_to_cart'], 10, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide default WooCommerce add to cart button for composable products
|
||||
*
|
||||
* @param bool $is_purchasable Is purchasable status
|
||||
* @param \WC_Product $product Product object
|
||||
* @return bool
|
||||
*/
|
||||
public function hide_default_add_to_cart($is_purchasable, $product) {
|
||||
if ($product && $product->get_type() === 'composable') {
|
||||
return false;
|
||||
}
|
||||
return $is_purchasable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,6 +84,12 @@ class Plugin {
|
||||
$this->twig->addFunction(new \Twig\TwigFunction('esc_html', 'esc_html'));
|
||||
$this->twig->addFunction(new \Twig\TwigFunction('esc_attr', 'esc_attr'));
|
||||
$this->twig->addFunction(new \Twig\TwigFunction('esc_url', 'esc_url'));
|
||||
$this->twig->addFunction(new \Twig\TwigFunction('wc_price', 'wc_price'));
|
||||
|
||||
// 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,6 +162,13 @@ class Plugin {
|
||||
'max_items' => __('Maximum items selected', 'wc-composable-product'),
|
||||
'min_items' => __('Please select at least one item', 'wc-composable-product'),
|
||||
],
|
||||
'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(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ class Product_Selector {
|
||||
'show_prices' => $show_prices,
|
||||
'show_total' => $show_total,
|
||||
'fixed_price' => $product->get_price(),
|
||||
'fixed_price_html' => wc_price($product->get_price()),
|
||||
'zero_price_html' => wc_price(0),
|
||||
'currency_symbol' => get_woocommerce_currency_symbol(),
|
||||
];
|
||||
|
||||
|
||||
@@ -163,7 +163,22 @@ class Product_Type extends \WC_Product {
|
||||
if ($query->have_posts()) {
|
||||
foreach ($query->posts as $post) {
|
||||
$product = wc_get_product($post->ID);
|
||||
if ($product && $product->is_in_stock() && $product->is_purchasable()) {
|
||||
|
||||
if (!$product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle variable products by including their variations
|
||||
if ($product->is_type('variable')) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
languages/wc-composable-product-de_CH.mo
Normal file
BIN
languages/wc-composable-product-de_CH.mo
Normal file
Binary file not shown.
@@ -144,8 +144,16 @@ msgid "Pricing Mode"
|
||||
msgstr "Preismodus"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgstr "Wie der Preis berechnet wird. Leer lassen, um den globalen Standard zu verwenden."
|
||||
msgid "How to calculate the price."
|
||||
msgstr "Wie der Preis berechnet wird."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr "Festpreis"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Use global default"
|
||||
@@ -198,3 +206,39 @@ msgstr "Geben Sie Produkt-Artikelnummern durch Kommas getrennt ein."
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr "ART-1, ART-2, ART-3"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr "\"%s\" ist nicht an Lager und kann nicht ausgewählt werden."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr "Nur %2$d von \"%1$s\" sind an Lager verfügbar."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr "Nicht an Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr "Nur"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr "übrig"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "An Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die Produktkriterien im Admin-Bereich."
|
||||
|
||||
BIN
languages/wc-composable-product-de_CH_informal.mo
Normal file
BIN
languages/wc-composable-product-de_CH_informal.mo
Normal file
Binary file not shown.
@@ -144,8 +144,16 @@ msgid "Pricing Mode"
|
||||
msgstr "Preismodus"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgstr "Wie der Preis berechnet wird. Leer lassen, um den globalen Standard zu verwenden."
|
||||
msgid "How to calculate the price."
|
||||
msgstr "Wie der Preis berechnet wird."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr "Festpreis"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr "Gib den Festpreis für dieses zusammenstellbare Produkt ein."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Use global default"
|
||||
@@ -198,3 +206,39 @@ msgstr "Gib Produkt-Artikelnummern durch Kommas getrennt ein."
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr "ART-1, ART-2, ART-3"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr "\"%s\" ist nicht an Lager und kann nicht ausgewählt werden."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr "Nur %2$d von \"%1$s\" sind an Lager verfügbar."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr "Nicht an Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr "Nur"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr "übrig"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "An Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfiguriere die Produktkriterien im Admin-Bereich."
|
||||
|
||||
BIN
languages/wc-composable-product-de_DE.mo
Normal file
BIN
languages/wc-composable-product-de_DE.mo
Normal file
Binary file not shown.
@@ -144,8 +144,16 @@ msgid "Pricing Mode"
|
||||
msgstr "Preismodus"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgstr "Wie der Preis berechnet wird. Leer lassen, um den globalen Standard zu verwenden."
|
||||
msgid "How to calculate the price."
|
||||
msgstr "Wie der Preis berechnet wird."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr "Festpreis"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Use global default"
|
||||
@@ -198,3 +206,39 @@ msgstr "Geben Sie Produkt-Artikelnummern durch Kommas getrennt ein."
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr "ART-1, ART-2, ART-3"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr "\"%s\" ist nicht auf Lager und kann nicht ausgewählt werden."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr "Nur %2$d von \"%1$s\" sind auf Lager verfügbar."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr "Nicht auf Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr "Nur"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr "übrig"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "Auf Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die Produktkriterien im Admin-Bereich."
|
||||
|
||||
BIN
languages/wc-composable-product-de_DE_informal.mo
Normal file
BIN
languages/wc-composable-product-de_DE_informal.mo
Normal file
Binary file not shown.
@@ -144,8 +144,16 @@ msgid "Pricing Mode"
|
||||
msgstr "Preismodus"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgstr "Wie der Preis berechnet wird. Leer lassen, um den globalen Standard zu verwenden."
|
||||
msgid "How to calculate the price."
|
||||
msgstr "Wie der Preis berechnet wird."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr "Festpreis"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr "Gib den Festpreis für dieses zusammenstellbare Produkt ein."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Use global default"
|
||||
@@ -198,3 +206,39 @@ msgstr "Gib Produkt-Artikelnummern durch Kommas getrennt ein."
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr "ART-1, ART-2, ART-3"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr "\"%s\" ist nicht auf Lager und kann nicht ausgewählt werden."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr "Nur %2$d von \"%1$s\" sind auf Lager verfügbar."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr "Nicht auf Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr "Nur"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr "übrig"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "Auf Lager"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfiguriere die Produktkriterien im Admin-Bereich."
|
||||
|
||||
BIN
languages/wc-composable-product-fr_CH.mo
Normal file
BIN
languages/wc-composable-product-fr_CH.mo
Normal file
Binary file not shown.
@@ -144,8 +144,16 @@ msgid "Pricing Mode"
|
||||
msgstr "Mode de tarification"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgstr "Comment calculer le prix. Laisser vide pour utiliser la valeur par défaut globale."
|
||||
msgid "How to calculate the price."
|
||||
msgstr "Comment calculer le prix."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr "Prix fixe"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr "Entrez le prix fixe pour ce produit composable."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Use global default"
|
||||
@@ -198,3 +206,39 @@ msgstr "Entrez les références des produits séparées par des virgules."
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr "REF-1, REF-2, REF-3"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr "\"%s\" est en rupture de stock et ne peut pas être sélectionné."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr "Seulement %2$d de \"%1$s\" sont disponibles en stock."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr "Stock réduit pour \"%1$s\": -%2$d (restant: %3$d)"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr "Stock restauré pour \"%1$s\": +%2$d (total: %3$d)"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr "Rupture de stock"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr "Seulement"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr "restant"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "En stock"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr "Aucun produit disponible pour la sélection. Veuillez configurer les critères de produit dans le panneau d'administration."
|
||||
|
||||
BIN
languages/wc-composable-product-it_CH.mo
Normal file
BIN
languages/wc-composable-product-it_CH.mo
Normal file
Binary file not shown.
@@ -144,8 +144,16 @@ msgid "Pricing Mode"
|
||||
msgstr "Modalità di prezzo"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgstr "Come calcolare il prezzo. Lasciare vuoto per utilizzare il valore predefinito globale."
|
||||
msgid "How to calculate the price."
|
||||
msgstr "Come calcolare il prezzo."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr "Prezzo fisso"
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr "Inserisci il prezzo fisso per questo prodotto componibile."
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Use global default"
|
||||
@@ -230,3 +238,7 @@ msgstr "rimasti"
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "Disponibile"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr "Nessun prodotto disponibile per la selezione. Si prega di configurare i criteri del prodotto nel pannello di amministrazione."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# This file is distributed under the GPL v3 or later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
|
||||
"Project-Id-Version: WooCommerce Composable Products 1.1.6\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n"
|
||||
"POT-Creation-Date: 2024-12-31 00:00+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
@@ -143,7 +143,15 @@ msgid "Pricing Mode"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "How to calculate the price. Leave empty to use global default."
|
||||
msgid "How to calculate the price."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Fixed Price"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "Enter the fixed price for this composable product."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/Product_Data.php
|
||||
@@ -229,3 +237,7 @@ msgstr ""
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||
msgstr ""
|
||||
|
||||
BIN
releases/wc-composable-product-v1.1.10.zip
Normal file
BIN
releases/wc-composable-product-v1.1.10.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.10.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.10.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
271aad47684ee8318a8824861d5fc387 wc-composable-product-v1.1.10.zip
|
||||
1
releases/wc-composable-product-v1.1.10.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.10.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
63bfe97aa9fd98e74750786ed0e1579b069505e85558316f7042787994c856ac wc-composable-product-v1.1.10.zip
|
||||
BIN
releases/wc-composable-product-v1.1.3.zip
Normal file
BIN
releases/wc-composable-product-v1.1.3.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.3.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.3.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
9bbed416019a796b4d4a5ef72e016e1f wc-composable-product-v1.1.3.zip
|
||||
1
releases/wc-composable-product-v1.1.3.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.3.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
0ca23ca12570f0e9c518514ffc5209d78c76c3295954d10ec74a28013a762956 wc-composable-product-v1.1.3.zip
|
||||
BIN
releases/wc-composable-product-v1.1.6.zip
Normal file
BIN
releases/wc-composable-product-v1.1.6.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.6.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.6.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
eae384e342450abd4ac83af0266ac764 wc-composable-product-v1.1.6.zip
|
||||
1
releases/wc-composable-product-v1.1.6.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.6.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
d64f4f5f1a00d392989cb613780e5726106a08c6aace08e0c74c80553a0b0f1e wc-composable-product-v1.1.6.zip
|
||||
BIN
releases/wc-composable-product-v1.1.7.zip
Normal file
BIN
releases/wc-composable-product-v1.1.7.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.7.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.7.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
871fbb3b910380c0e43bcf1538408eda releases/wc-composable-product-v1.1.7.zip
|
||||
1
releases/wc-composable-product-v1.1.7.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.7.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
866e7dd34431f4c881629fd8b59ddd3a27c7a45b7324a3d88cd064a3e01c1b83 releases/wc-composable-product-v1.1.7.zip
|
||||
BIN
releases/wc-composable-product-v1.1.8.zip
Normal file
BIN
releases/wc-composable-product-v1.1.8.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.8.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.8.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
78eee5eee4762c308c5d37d1aac06b04 wc-composable-product-v1.1.8.zip
|
||||
1
releases/wc-composable-product-v1.1.8.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.8.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
d7d06e2a5d336609249f803b681cdf270dbe60d6fc28bdd6c451c6744d2fdab6 wc-composable-product-v1.1.8.zip
|
||||
BIN
releases/wc-composable-product-v1.1.9.zip
Normal file
BIN
releases/wc-composable-product-v1.1.9.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.9.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.9.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
a5b08f3613d1b1e8aba0c2b7b82a1582 wc-composable-product-v1.1.9.zip
|
||||
1
releases/wc-composable-product-v1.1.9.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.9.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
f9fc497c0531c7ea828e164137f3db6e0a2755b899690dfb7d6411baf0c7a65a wc-composable-product-v1.1.9.zip
|
||||
@@ -9,6 +9,11 @@
|
||||
</div>
|
||||
|
||||
<div class="composable-products-grid">
|
||||
{% if products is empty %}
|
||||
<div class="composable-no-products">
|
||||
<p>{{ __('No products available for selection. Please configure the product criteria in the admin panel.') }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% for product in products %}
|
||||
<div class="composable-product-item{% if not product.in_stock %} out-of-stock{% endif %}" data-product-id="{{ product.id }}" data-price="{{ product.price }}" data-stock-status="{{ product.stock_status }}">
|
||||
<div class="product-item-inner">
|
||||
@@ -52,16 +57,17 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if show_total %}
|
||||
<div class="composable-total">
|
||||
<div class="total-label">{{ __('Total Price:') }}</div>
|
||||
<div class="total-price" data-currency="{{ currency_symbol }}">
|
||||
<div class="total-price" data-currency="{{ currency_symbol }}" data-fixed-price="{{ fixed_price }}">
|
||||
{% if pricing_mode == 'fixed' %}
|
||||
{{ currency_symbol }}{{ fixed_price }}
|
||||
{{ fixed_price_html|raw }}
|
||||
{% else %}
|
||||
<span class="calculated-total">{{ currency_symbol }}0.00</span>
|
||||
<span class="calculated-total">{{ zero_price_html|raw }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: WooCommerce Composable Products
|
||||
* Plugin URI: https://github.com/magdev/wc-composable-product
|
||||
* Description: Create composable products where customers select a limited number of items from a configurable set
|
||||
* Version: 1.1.3
|
||||
* Version: 1.1.11
|
||||
* Author: Marco Graetsch
|
||||
* Author URI: https://example.com
|
||||
* License: GPL v3 or later
|
||||
@@ -19,7 +19,7 @@
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// Define plugin constants
|
||||
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.1.3');
|
||||
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.1.11');
|
||||
define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__);
|
||||
define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__));
|
||||
define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
Reference in New Issue
Block a user