You've already forked wc-composable-product
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b271c90c0 | |||
| 0dd4408b23 | |||
| 7a4a0a0135 | |||
| c6a48d6404 | |||
| ac1cb9b135 | |||
| f5bc0d0335 | |||
| 88a907c4dd | |||
| 03a7624564 | |||
| 1c3f44f3c2 | |||
| 287f8b778b | |||
| 63d8f9ed52 | |||
| 601570d724 | |||
| e9b2d1c79b | |||
| d27dd4b7bd | |||
| 1b7c7a0257 | |||
| 4f65c8e5e0 | |||
| 054617f320 | |||
| 8fc0614334 | |||
| 867abc8f63 | |||
| 818fd51502 | |||
| 392559dedc | |||
| 17d5312df3 | |||
| 037be97ece | |||
| 28d2223306 | |||
| 413b5d8acd | |||
| 8aaf30de99 | |||
| 18d340d029 | |||
| f1382490ec | |||
| f1b255a7f8 | |||
| 7520a37b05 | |||
| 7bde9e2f0c | |||
| e491dadc67 | |||
| 91f44b0080 | |||
| 7b1b778f5b | |||
| 67bc61cf91 | |||
| e9df6e4278 | |||
| a581ef42e6 | |||
| bcbf12702e |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Linked sources
|
||||
wp-core
|
||||
wp-plugins
|
||||
tpp
|
||||
|
||||
# Editor swap files
|
||||
*.*swp
|
||||
@@ -21,6 +22,3 @@ cache/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Release files
|
||||
*.zip
|
||||
releases/
|
||||
|
||||
211
CHANGELOG.md
211
CHANGELOG.md
@@ -5,6 +5,217 @@ 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.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
|
||||
|
||||
- WooCommerce HPOS (High-Performance Order Storage) compatibility declaration
|
||||
- Prevents duplicate price calculations to avoid conflicts with other pricing plugins
|
||||
|
||||
### Fixed
|
||||
|
||||
- WooCommerce compatibility warnings with Analytics and other WooCommerce extensions
|
||||
- Price calculation conflicts with third-party pricing plugins
|
||||
|
||||
### Technical
|
||||
|
||||
- Added `before_woocommerce_init` hook to declare HPOS compatibility
|
||||
- Implemented static flag in `Cart_Handler::calculate_cart_item_price()` to prevent multiple executions
|
||||
- Added `composable_price_calculated` flag to cart items to prevent re-calculation by other plugins
|
||||
- Ensures composable products work with WooCommerce's modern order storage system
|
||||
|
||||
## [1.1.2] - 2024-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL**: Fixed persistent "Class WC_Settings_Page not found" error that continued in v1.1.1
|
||||
- Root cause: Settings.php was being included too early (during plugin init) before WC_Settings_Page was loaded
|
||||
- Solution: Delayed Settings.php inclusion until `woocommerce_get_settings_pages` filter when class is guaranteed to exist
|
||||
|
||||
### Technical
|
||||
|
||||
- Removed `require_once Settings.php` from `Plugin::includes()` (line 93)
|
||||
- Added `require_once Settings.php` to `Plugin::add_settings_page()` (line 196)
|
||||
- Settings file now loads on-demand when WooCommerce requests settings pages
|
||||
- Previous hook change (woocommerce_init) was insufficient - class loading order was the real issue
|
||||
|
||||
### Notes
|
||||
|
||||
- v1.1.1 attempted to fix this with hook change but the error persisted
|
||||
- This version addresses the actual root cause: premature class extension
|
||||
|
||||
## [1.1.1] - 2024-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Settings page initialization timing issue causing "Class WC_Settings_Page not found" error
|
||||
- Changed hook from `woocommerce_loaded` to `woocommerce_init` to ensure WC_Settings_Page class is available
|
||||
- Plugin now initializes after all WooCommerce core classes are loaded
|
||||
|
||||
### Technical
|
||||
|
||||
- Hook changed from `woocommerce_loaded` to `woocommerce_init` in wc-composable-product.php:65
|
||||
- `woocommerce_init` fires after WooCommerce has finished loading all its core classes including settings
|
||||
|
||||
## [1.1.0] - 2024-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- **Stock Management Integration**: Complete inventory tracking system for composable products
|
||||
- Stock validation during product selection and add-to-cart
|
||||
- Automatic stock deduction when orders are completed/processed
|
||||
- Automatic stock restoration on order cancellation/refund
|
||||
- Stock status indicators in product selector (In stock, Low stock, Out of stock)
|
||||
- Visual feedback for out-of-stock items (disabled checkboxes, reduced opacity)
|
||||
- Low stock warnings when 5 or fewer items remain
|
||||
- Prevention of out-of-stock item selection
|
||||
- Order notes documenting stock changes
|
||||
|
||||
### Technical
|
||||
|
||||
- New `Stock_Manager` class handling all stock operations
|
||||
- Integration with WooCommerce order status hooks
|
||||
- Stock information passed to frontend via Twig template
|
||||
- Enhanced CSS styling for stock status badges
|
||||
- Stock data stored in order item meta for accurate tracking
|
||||
- Backorder support detection and handling
|
||||
|
||||
### Translation
|
||||
|
||||
- Added 8 new translatable strings for stock messages
|
||||
- Updated Italian (Switzerland) translation with stock-related terms
|
||||
- Updated translation template (.pot file)
|
||||
|
||||
## [1.0.1] - 2024-12-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fatal error "Class WC_Settings_Page not found" during plugin activation
|
||||
- Changed initialization hook from `plugins_loaded` to `woocommerce_loaded` to ensure WooCommerce classes are available before plugin initialization
|
||||
- Settings page now correctly integrates as a tab in WooCommerce > Settings
|
||||
|
||||
### Technical
|
||||
|
||||
- Plugin now waits for `woocommerce_loaded` action before initializing
|
||||
- Prevents race condition where WooCommerce classes weren't loaded yet
|
||||
- Settings tab appears correctly in WooCommerce settings interface
|
||||
|
||||
## [1.0.0] - 2024-12-31
|
||||
|
||||
### Added
|
||||
|
||||
@@ -12,7 +12,7 @@ This document provides a technical overview of the WooCommerce Composable Produc
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
```
|
||||
```txt
|
||||
wc-composable-product/
|
||||
├── assets/ # Frontend assets
|
||||
│ ├── css/
|
||||
@@ -42,6 +42,7 @@ wc-composable-product/
|
||||
### 1. Main Plugin Class (`Plugin.php`)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Singleton pattern implementation
|
||||
- Twig template engine initialization
|
||||
- Hook registration
|
||||
@@ -49,6 +50,7 @@ wc-composable-product/
|
||||
- Asset enqueuing
|
||||
|
||||
**Key Methods:**
|
||||
|
||||
- `instance()`: Get singleton instance
|
||||
- `init_twig()`: Initialize Twig with WordPress functions
|
||||
- `render_template()`: Render Twig templates
|
||||
@@ -59,6 +61,7 @@ wc-composable-product/
|
||||
**Extends:** `WC_Product`
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- Custom product type: `composable`
|
||||
- Selection limit management (per-product or global)
|
||||
- Pricing mode (fixed or sum)
|
||||
@@ -67,6 +70,7 @@ wc-composable-product/
|
||||
- Price calculation
|
||||
|
||||
**Key Methods:**
|
||||
|
||||
- `get_selection_limit()`: Get max selectable items
|
||||
- `get_pricing_mode()`: Get pricing calculation mode
|
||||
- `get_available_products()`: Query available products
|
||||
@@ -77,6 +81,7 @@ wc-composable-product/
|
||||
**Extends:** `WC_Settings_Page`
|
||||
|
||||
**Global Settings:**
|
||||
|
||||
- Default selection limit
|
||||
- Default pricing mode
|
||||
- Display options (images, prices, total)
|
||||
@@ -86,12 +91,14 @@ wc-composable-product/
|
||||
### 4. Product Data Tab (`Admin/Product_Data.php`)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Add "Composable Options" tab to product edit page
|
||||
- Render selection criteria fields
|
||||
- Save product meta data
|
||||
- Dynamic field visibility based on criteria type
|
||||
|
||||
**Saved Meta:**
|
||||
|
||||
- `_composable_selection_limit`: Item limit
|
||||
- `_composable_pricing_mode`: Pricing calculation
|
||||
- `_composable_criteria_type`: Selection method
|
||||
@@ -102,11 +109,13 @@ wc-composable-product/
|
||||
### 5. Product Selector (`Product_Selector.php`)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Render frontend product selection interface
|
||||
- Prepare data for Twig template
|
||||
- Apply display settings
|
||||
|
||||
**Template Variables:**
|
||||
|
||||
- `products`: Available products array
|
||||
- `selection_limit`: Max selections
|
||||
- `pricing_mode`: Pricing calculation
|
||||
@@ -115,12 +124,14 @@ wc-composable-product/
|
||||
### 6. Cart Handler (`Cart_Handler.php`)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Validate product selection
|
||||
- Add selected products to cart data
|
||||
- Calculate dynamic pricing
|
||||
- Display selected products in cart
|
||||
|
||||
**Hooks:**
|
||||
|
||||
- `woocommerce_add_to_cart_validation`: Validate selections
|
||||
- `woocommerce_add_cart_item_data`: Store selections
|
||||
- `woocommerce_before_calculate_totals`: Update prices
|
||||
@@ -131,6 +142,7 @@ wc-composable-product/
|
||||
### Product Selector Template (`product-selector.twig`)
|
||||
|
||||
**Features:**
|
||||
|
||||
- Responsive grid layout
|
||||
- Checkbox-based selection
|
||||
- Product images and prices
|
||||
@@ -138,6 +150,7 @@ wc-composable-product/
|
||||
- AJAX add-to-cart
|
||||
|
||||
**Data Attributes:**
|
||||
|
||||
- `data-product-id`: Composable product ID
|
||||
- `data-selection-limit`: Max selections
|
||||
- `data-pricing-mode`: Pricing mode
|
||||
@@ -146,6 +159,7 @@ wc-composable-product/
|
||||
### JavaScript (`frontend.js`)
|
||||
|
||||
**Functionality:**
|
||||
|
||||
- Selection limit enforcement
|
||||
- Visual feedback on selection
|
||||
- Real-time price updates (sum mode)
|
||||
@@ -153,6 +167,7 @@ wc-composable-product/
|
||||
- Error/success messages
|
||||
|
||||
**Key Functions:**
|
||||
|
||||
- `handleCheckboxChange()`: Selection logic
|
||||
- `updateTotalPrice()`: Calculate total
|
||||
- `addToCart()`: AJAX add-to-cart
|
||||
@@ -161,6 +176,7 @@ wc-composable-product/
|
||||
### CSS Styling
|
||||
|
||||
**Approach:**
|
||||
|
||||
- Grid-based layout (responsive)
|
||||
- Card-style product items
|
||||
- Visual selection states
|
||||
@@ -242,6 +258,7 @@ wc-composable-product/
|
||||
Generated template: `languages/wc-composable-product.pot`
|
||||
|
||||
**Supported Locales (per CLAUDE.md):**
|
||||
|
||||
- en_US (English)
|
||||
- de_DE, de_DE_informal (German - Germany)
|
||||
- de_CH, de_CH_informal (German - Switzerland)
|
||||
@@ -273,11 +290,13 @@ Generated template: `languages/wc-composable-product.pot`
|
||||
### Hooks & Filters
|
||||
|
||||
**Available Filters:**
|
||||
|
||||
- `wc_composable_settings`: Modify settings array
|
||||
- `woocommerce_product_class`: Custom product class
|
||||
- `product_type_selector`: Product type registration
|
||||
|
||||
**Customization Points:**
|
||||
|
||||
- Twig templates (override in theme)
|
||||
- CSS styling (enqueue custom styles)
|
||||
- JavaScript behavior (extend object)
|
||||
@@ -304,6 +323,7 @@ if ($product->get_type() === 'composable') {
|
||||
## Testing Checklist
|
||||
|
||||
### Admin Testing
|
||||
|
||||
- [ ] Product type appears in dropdown
|
||||
- [ ] Composable Options tab displays
|
||||
- [ ] Selection criteria toggle works
|
||||
@@ -312,6 +332,7 @@ if ($product->get_type() === 'composable') {
|
||||
- [ ] Global defaults apply
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
- [ ] Product selector renders
|
||||
- [ ] Selection limit enforced
|
||||
- [ ] Price calculation accurate (both modes)
|
||||
@@ -320,6 +341,7 @@ if ($product->get_type() === 'composable') {
|
||||
- [ ] Checkout processes correctly
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- [ ] Empty criteria (no products)
|
||||
- [ ] Out of stock products excluded
|
||||
- [ ] Invalid product selections rejected
|
||||
@@ -349,12 +371,14 @@ Potential features for future versions:
|
||||
## Dependencies
|
||||
|
||||
### Runtime
|
||||
|
||||
- PHP 8.3+
|
||||
- WordPress 6.0+
|
||||
- WooCommerce 8.0+
|
||||
- Twig 3.0 (via Composer)
|
||||
|
||||
### Development
|
||||
|
||||
- Composer for dependency management
|
||||
- WP-CLI for i18n operations (optional)
|
||||
|
||||
@@ -372,6 +396,7 @@ Potential features for future versions:
|
||||
### Release Package
|
||||
|
||||
Must include:
|
||||
|
||||
- All PHP files
|
||||
- `vendor/` directory
|
||||
- Assets (CSS, JS)
|
||||
@@ -380,6 +405,7 @@ Must include:
|
||||
- Documentation
|
||||
|
||||
Must exclude:
|
||||
|
||||
- `.git/` directory
|
||||
- `composer.lock`
|
||||
- Development files
|
||||
@@ -388,12 +414,14 @@ Must exclude:
|
||||
## Support & Maintenance
|
||||
|
||||
### Code Standards
|
||||
|
||||
- WordPress Coding Standards
|
||||
- WooCommerce best practices
|
||||
- PSR-4 autoloading
|
||||
- Inline documentation
|
||||
|
||||
### Version Control
|
||||
|
||||
- Semantic versioning (MAJOR.MINOR.PATCH)
|
||||
- Changelog maintained
|
||||
- Annotated git tags
|
||||
|
||||
@@ -19,11 +19,29 @@
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Ensure General tab fields don't show in Composable Options panel initially */
|
||||
#composable_product_data .options_group {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Hide the Composable Options tab link by default */
|
||||
.product_data_tabs .composable_options {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.product-type-composable .show_if_composable {
|
||||
/* Show the Composable Options tab when composable type selected */
|
||||
body.product-type-composable .product_data_tabs .composable_options {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,20 @@
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
.composable-product-item.out-of-stock {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.composable-product-item.out-of-stock:hover {
|
||||
border-color: #e0e0e0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.composable-product-item.out-of-stock .product-item-label {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.product-item-label {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
@@ -94,6 +108,33 @@
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.product-item-stock {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.stock-status {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stock-status.in-stock {
|
||||
color: #2e7d32;
|
||||
background: #e8f5e9;
|
||||
}
|
||||
|
||||
.stock-status.low-stock {
|
||||
color: #f57c00;
|
||||
background: #fff3e0;
|
||||
}
|
||||
|
||||
.stock-status.out-of-stock {
|
||||
color: #c62828;
|
||||
background: #ffebee;
|
||||
}
|
||||
|
||||
.product-item-checkmark {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,41 @@ defined('ABSPATH') || exit;
|
||||
* Handles adding composable products to cart and calculating prices
|
||||
*/
|
||||
class Cart_Handler {
|
||||
/**
|
||||
* Stock manager instance
|
||||
*
|
||||
* @var Stock_Manager
|
||||
*/
|
||||
private $stock_manager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->stock_manager = new Stock_Manager();
|
||||
|
||||
add_filter('woocommerce_add_to_cart_validation', [$this, 'validate_add_to_cart'], 10, 3);
|
||||
add_filter('woocommerce_add_cart_item_data', [$this, 'add_cart_item_data'], 10, 2);
|
||||
add_filter('woocommerce_get_cart_item_from_session', [$this, 'get_cart_item_from_session'], 10, 2);
|
||||
add_filter('woocommerce_get_item_data', [$this, 'display_cart_item_data'], 10, 2);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +112,13 @@ class Cart_Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate stock availability
|
||||
$stock_validation = $this->stock_manager->validate_stock_availability($selected_products, $quantity);
|
||||
if ($stock_validation !== true) {
|
||||
wc_add_notice($stock_validation, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return $passed;
|
||||
}
|
||||
|
||||
@@ -168,14 +200,25 @@ class Cart_Handler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use static flag to prevent multiple executions
|
||||
static $already_calculated = false;
|
||||
if ($already_calculated) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
||||
if (isset($cart_item['data']) && $cart_item['data']->get_type() === 'composable') {
|
||||
if (isset($cart_item['composable_products'])) {
|
||||
if (isset($cart_item['composable_products']) && !isset($cart_item['composable_price_calculated'])) {
|
||||
$product = $cart_item['data'];
|
||||
$price = $product->calculate_composed_price($cart_item['composable_products']);
|
||||
$cart_item['data']->set_price($price);
|
||||
|
||||
// Mark as calculated to prevent re-calculation by other plugins
|
||||
$cart->cart_contents[$cart_item_key]['composable_price_calculated'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$already_calculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,15 +84,23 @@ 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Include required files
|
||||
*/
|
||||
private function includes() {
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Settings.php';
|
||||
// Note: Settings.php is NOT included here because it extends WC_Settings_Page
|
||||
// which isn't loaded until later. It's included in add_settings_page() instead.
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Product_Data.php';
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Product_Type.php';
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Stock_Manager.php';
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Cart_Handler.php';
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Product_Selector.php';
|
||||
|
||||
@@ -154,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(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -190,6 +205,8 @@ class Plugin {
|
||||
* @return array
|
||||
*/
|
||||
public function add_settings_page($settings) {
|
||||
// Include Settings.php here, when WC_Settings_Page is guaranteed to be loaded
|
||||
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Settings.php';
|
||||
$settings[] = new Admin\Settings();
|
||||
return $settings;
|
||||
}
|
||||
|
||||
@@ -33,9 +33,14 @@ class Product_Selector {
|
||||
$show_prices = get_option('wc_composable_show_prices', 'yes') === 'yes';
|
||||
$show_total = get_option('wc_composable_show_total', 'yes') === 'yes';
|
||||
|
||||
// Get stock manager for stock information
|
||||
$stock_manager = new Stock_Manager();
|
||||
|
||||
// Prepare product data for template
|
||||
$products_data = [];
|
||||
foreach ($available_products as $available_product) {
|
||||
$stock_info = $stock_manager->get_product_stock_info($available_product->get_id());
|
||||
|
||||
$products_data[] = [
|
||||
'id' => $available_product->get_id(),
|
||||
'name' => $available_product->get_name(),
|
||||
@@ -43,6 +48,11 @@ class Product_Selector {
|
||||
'price_html' => $available_product->get_price_html(),
|
||||
'image_url' => wp_get_attachment_image_url($available_product->get_image_id(), 'thumbnail'),
|
||||
'permalink' => $available_product->get_permalink(),
|
||||
'stock_status' => $stock_info['stock_status'],
|
||||
'in_stock' => $stock_info['in_stock'],
|
||||
'stock_quantity' => $stock_info['stock_quantity'],
|
||||
'managing_stock' => $stock_info['managing_stock'],
|
||||
'backorders_allowed' => $stock_info['backorders_allowed'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -55,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(),
|
||||
];
|
||||
|
||||
|
||||
283
includes/Stock_Manager.php
Normal file
283
includes/Stock_Manager.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
/**
|
||||
* Stock Manager
|
||||
*
|
||||
* @package WC_Composable_Product
|
||||
*/
|
||||
|
||||
namespace WC_Composable_Product;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Stock Manager Class
|
||||
*
|
||||
* Handles stock management for composable products
|
||||
*/
|
||||
class Stock_Manager {
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
// Hook into order completion to reduce stock
|
||||
add_action('woocommerce_order_status_completed', [$this, 'reduce_stock_on_order_complete'], 10, 1);
|
||||
add_action('woocommerce_order_status_processing', [$this, 'reduce_stock_on_order_complete'], 10, 1);
|
||||
|
||||
// Hook into order cancellation/refund to restore stock
|
||||
add_action('woocommerce_order_status_cancelled', [$this, 'restore_stock_on_order_cancel'], 10, 1);
|
||||
add_action('woocommerce_order_status_refunded', [$this, 'restore_stock_on_order_cancel'], 10, 1);
|
||||
|
||||
// Prevent double stock reduction
|
||||
add_filter('woocommerce_can_reduce_order_stock', [$this, 'prevent_composable_stock_reduction'], 10, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate stock availability for selected products
|
||||
*
|
||||
* @param array $selected_product_ids Array of product IDs
|
||||
* @param int $quantity Quantity of composable product being added
|
||||
* @return bool|string True if in stock, error message otherwise
|
||||
*/
|
||||
public function validate_stock_availability($selected_product_ids, $quantity = 1) {
|
||||
foreach ($selected_product_ids as $product_id) {
|
||||
$product = wc_get_product($product_id);
|
||||
|
||||
if (!$product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip stock check if stock management is disabled for this product
|
||||
if (!$product->managing_stock()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stock_quantity = $product->get_stock_quantity();
|
||||
|
||||
// Check if product is in stock
|
||||
if (!$product->is_in_stock()) {
|
||||
return sprintf(
|
||||
/* translators: %s: product name */
|
||||
__('"%s" is out of stock and cannot be selected.', 'wc-composable-product'),
|
||||
$product->get_name()
|
||||
);
|
||||
}
|
||||
|
||||
// Check if enough stock is available
|
||||
if ($stock_quantity !== null && $stock_quantity < $quantity) {
|
||||
return sprintf(
|
||||
/* translators: 1: product name, 2: stock quantity */
|
||||
__('Only %2$d of "%1$s" are available in stock.', 'wc-composable-product'),
|
||||
$product->get_name(),
|
||||
$stock_quantity
|
||||
);
|
||||
}
|
||||
|
||||
// Check for backorders
|
||||
if ($product->backorders_allowed()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a product has sufficient stock
|
||||
*
|
||||
* @param int $product_id Product ID
|
||||
* @param int $required_quantity Required quantity
|
||||
* @return array Stock information [in_stock, stock_quantity, backorders_allowed]
|
||||
*/
|
||||
public function get_product_stock_info($product_id, $required_quantity = 1) {
|
||||
$product = wc_get_product($product_id);
|
||||
|
||||
if (!$product) {
|
||||
return [
|
||||
'in_stock' => false,
|
||||
'stock_quantity' => 0,
|
||||
'backorders_allowed' => false,
|
||||
'stock_status' => 'outofstock',
|
||||
];
|
||||
}
|
||||
|
||||
$stock_quantity = $product->get_stock_quantity();
|
||||
$managing_stock = $product->managing_stock();
|
||||
|
||||
return [
|
||||
'in_stock' => $product->is_in_stock(),
|
||||
'stock_quantity' => $stock_quantity,
|
||||
'backorders_allowed' => $product->backorders_allowed(),
|
||||
'stock_status' => $product->get_stock_status(),
|
||||
'managing_stock' => $managing_stock,
|
||||
'has_enough_stock' => !$managing_stock || $stock_quantity === null || $stock_quantity >= $required_quantity,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce stock for composable products when order is completed
|
||||
*
|
||||
* @param int $order_id Order ID
|
||||
*/
|
||||
public function reduce_stock_on_order_complete($order_id) {
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (!$order) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if stock has already been reduced
|
||||
if ($order->get_meta('_composable_stock_reduced', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($order->get_items() as $item) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if (!$product || $product->get_type() !== 'composable') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get selected products from order item meta
|
||||
$selected_products = $item->get_meta('_composable_products', true);
|
||||
|
||||
if (empty($selected_products) || !is_array($selected_products)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$quantity = $item->get_quantity();
|
||||
|
||||
// Reduce stock for each selected product
|
||||
foreach ($selected_products as $product_id) {
|
||||
$selected_product = wc_get_product($product_id);
|
||||
|
||||
if (!$selected_product || !$selected_product->managing_stock()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stock_quantity = $selected_product->get_stock_quantity();
|
||||
|
||||
if ($stock_quantity !== null) {
|
||||
$new_stock = $stock_quantity - $quantity;
|
||||
$selected_product->set_stock_quantity($new_stock);
|
||||
$selected_product->save();
|
||||
|
||||
// Add order note
|
||||
$order->add_order_note(
|
||||
sprintf(
|
||||
/* translators: 1: product name, 2: quantity, 3: remaining stock */
|
||||
__('Stock reduced for "%1$s": -%2$d (remaining: %3$d)', 'wc-composable-product'),
|
||||
$selected_product->get_name(),
|
||||
$quantity,
|
||||
$new_stock
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark stock as reduced
|
||||
$order->update_meta_data('_composable_stock_reduced', true);
|
||||
$order->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore stock when order is cancelled or refunded
|
||||
*
|
||||
* @param int $order_id Order ID
|
||||
*/
|
||||
public function restore_stock_on_order_cancel($order_id) {
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (!$order) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if stock was reduced
|
||||
if (!$order->get_meta('_composable_stock_reduced', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($order->get_items() as $item) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if (!$product || $product->get_type() !== 'composable') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get selected products from order item meta
|
||||
$selected_products = $item->get_meta('_composable_products', true);
|
||||
|
||||
if (empty($selected_products) || !is_array($selected_products)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$quantity = $item->get_quantity();
|
||||
|
||||
// Restore stock for each selected product
|
||||
foreach ($selected_products as $product_id) {
|
||||
$selected_product = wc_get_product($product_id);
|
||||
|
||||
if (!$selected_product || !$selected_product->managing_stock()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stock_quantity = $selected_product->get_stock_quantity();
|
||||
|
||||
if ($stock_quantity !== null) {
|
||||
$new_stock = $stock_quantity + $quantity;
|
||||
$selected_product->set_stock_quantity($new_stock);
|
||||
$selected_product->save();
|
||||
|
||||
// Add order note
|
||||
$order->add_order_note(
|
||||
sprintf(
|
||||
/* translators: 1: product name, 2: quantity, 3: new stock */
|
||||
__('Stock restored for "%1$s": +%2$d (total: %3$d)', 'wc-composable-product'),
|
||||
$selected_product->get_name(),
|
||||
$quantity,
|
||||
$new_stock
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark stock as restored
|
||||
$order->update_meta_data('_composable_stock_reduced', false);
|
||||
$order->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent WooCommerce from reducing stock for composable products
|
||||
* We handle stock reduction manually for selected products
|
||||
*
|
||||
* @param bool $reduce_stock Whether to reduce stock
|
||||
* @param \WC_Order $order Order object
|
||||
* @return bool
|
||||
*/
|
||||
public function prevent_composable_stock_reduction($reduce_stock, $order) {
|
||||
foreach ($order->get_items() as $item) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if ($product && $product->get_type() === 'composable') {
|
||||
// We'll handle stock reduction manually
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $reduce_stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store selected products in order item meta
|
||||
*
|
||||
* @param \WC_Order_Item_Product $item Order item
|
||||
* @param string $cart_item_key Cart item key
|
||||
* @param array $values Cart item values
|
||||
*/
|
||||
public function store_selected_products_in_order($item, $cart_item_key, $values) {
|
||||
if (isset($values['composable_products']) && !empty($values['composable_products'])) {
|
||||
$item->add_meta_data('_composable_products', $values['composable_products'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
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,35 @@ 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"
|
||||
|
||||
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,35 @@ 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"
|
||||
|
||||
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,35 @@ 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"
|
||||
|
||||
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,35 @@ 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"
|
||||
|
||||
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,35 @@ 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"
|
||||
|
||||
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"
|
||||
@@ -198,3 +206,35 @@ msgstr "Inserire i codici articolo dei prodotti separati da virgole."
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr "COD-1, COD-2, COD-3"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr "\"%s\" è esaurito e non può essere selezionato."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr "Solo %2$d di \"%1$s\" sono disponibili in magazzino."
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr "Giacenza ridotta per \"%1$s\": -%2$d (rimanenti: %3$d)"
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr "Giacenza ripristinata per \"%1$s\": +%2$d (totale: %3$d)"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr "Esaurito"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr "Solo"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr "rimasti"
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr "Disponibile"
|
||||
|
||||
@@ -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
|
||||
@@ -197,3 +205,35 @@ msgstr ""
|
||||
#: includes/Admin/Product_Data.php
|
||||
msgid "SKU-1, SKU-2, SKU-3"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "\"%s\" is out of stock and cannot be selected."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Only %2$d of \"%1$s\" are available in stock."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Stock_Manager.php
|
||||
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Out of stock"
|
||||
msgstr ""
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "Only"
|
||||
msgstr ""
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "left"
|
||||
msgstr ""
|
||||
|
||||
#: templates/product-selector.twig
|
||||
msgid "In stock"
|
||||
msgstr ""
|
||||
|
||||
BIN
releases/wc-composable-product-v1.0.0.zip
Normal file
BIN
releases/wc-composable-product-v1.0.0.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.0.0.zip.md5
Normal file
1
releases/wc-composable-product-v1.0.0.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
aec3bae001f0013322a73fa941169688 wc-composable-product-v1.0.0.zip
|
||||
1
releases/wc-composable-product-v1.0.0.zip.sha256
Normal file
1
releases/wc-composable-product-v1.0.0.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
4a0f7ec2171aeabfdfe155419fd6124f35f3e14501ee2ca324bbab447259a8bb wc-composable-product-v1.0.0.zip
|
||||
BIN
releases/wc-composable-product-v1.1.0.zip
Normal file
BIN
releases/wc-composable-product-v1.1.0.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.0.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.0.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
0a60816bbc5a01c0057c1ffa72679d93 releases/wc-composable-product-v1.1.0.zip
|
||||
1
releases/wc-composable-product-v1.1.0.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.0.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
645fdd68aca95cba77d961f3a48d41b9c12b3d17552572b7c039575dcfcab693 releases/wc-composable-product-v1.1.0.zip
|
||||
BIN
releases/wc-composable-product-v1.1.1.zip
Normal file
BIN
releases/wc-composable-product-v1.1.1.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.1.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.1.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
db09928aea6fffbf9c2e754d2264f2bc wc-composable-product-v1.1.1.zip
|
||||
1
releases/wc-composable-product-v1.1.1.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.1.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
761eef69da910ecfdb20ceeed70b5d0381c7cab895e81a040d132cb0f88d749b wc-composable-product-v1.1.1.zip
|
||||
BIN
releases/wc-composable-product-v1.1.2.zip
Normal file
BIN
releases/wc-composable-product-v1.1.2.zip
Normal file
Binary file not shown.
1
releases/wc-composable-product-v1.1.2.zip.md5
Normal file
1
releases/wc-composable-product-v1.1.2.zip.md5
Normal file
@@ -0,0 +1 @@
|
||||
37cef191778b448dcbd2ae10141f64c6 wc-composable-product-v1.1.2.zip
|
||||
1
releases/wc-composable-product-v1.1.2.zip.sha256
Normal file
1
releases/wc-composable-product-v1.1.2.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
191eae035b34ce8b33b90cf9d85ed54e493c1b471cda0efe5c992a512e91cc36 wc-composable-product-v1.1.2.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
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<div class="composable-products-grid">
|
||||
{% for product in products %}
|
||||
<div class="composable-product-item" data-product-id="{{ product.id }}" data-price="{{ product.price }}">
|
||||
<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">
|
||||
<label class="product-item-label">
|
||||
<input type="checkbox"
|
||||
@@ -18,7 +18,8 @@
|
||||
value="{{ product.id }}"
|
||||
class="composable-product-checkbox"
|
||||
data-product-id="{{ product.id }}"
|
||||
data-price="{{ product.price }}">
|
||||
data-price="{{ product.price }}"
|
||||
{% if not product.in_stock %}disabled{% endif %}>
|
||||
|
||||
{% if show_images and product.image_url %}
|
||||
<div class="product-item-image">
|
||||
@@ -34,6 +35,16 @@
|
||||
{{ product.price_html|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="product-item-stock">
|
||||
{% if not product.in_stock %}
|
||||
<span class="stock-status out-of-stock">{{ __('Out of stock') }}</span>
|
||||
{% elseif product.managing_stock and product.stock_quantity is not null and product.stock_quantity <= 5 %}
|
||||
<span class="stock-status low-stock">{{ __('Only') }} {{ product.stock_quantity }} {{ __('left') }}</span>
|
||||
{% elseif product.in_stock %}
|
||||
<span class="stock-status in-stock">{{ __('In stock') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="product-item-checkmark"></span>
|
||||
@@ -46,11 +57,11 @@
|
||||
{% 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.0.0
|
||||
* Version: 1.1.8
|
||||
* 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.0.0');
|
||||
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.1.8');
|
||||
define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__);
|
||||
define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__));
|
||||
define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__));
|
||||
@@ -61,7 +61,17 @@ function wc_composable_product_init() {
|
||||
// Initialize main plugin class
|
||||
WC_Composable_Product\Plugin::instance();
|
||||
}
|
||||
add_action('plugins_loaded', 'wc_composable_product_init');
|
||||
// Use woocommerce_init to ensure all WooCommerce classes including settings are loaded
|
||||
add_action('woocommerce_init', 'wc_composable_product_init');
|
||||
|
||||
/**
|
||||
* Declare HPOS compatibility
|
||||
*/
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Activation hook
|
||||
|
||||
Reference in New Issue
Block a user