4 Commits

Author SHA1 Message Date
dd5965ae4c Add option to include non-public products in selections (v1.3.0)
All checks were successful
Create Release Package / build-release (push) Successful in 1m6s
Allow draft and private products to appear in composable product
selections. Useful when products should only be sold as part of a
composition, not individually. Includes global setting and per-product
override with translations in all 6 locales.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:23:01 +01:00
9bc7a62f20 commit the composer lockfile 2026-03-01 12:17:04 +01:00
ed66c96d3d Consolidate documentation, bump to v1.2.1
All checks were successful
Create Release Package / build-release (push) Successful in 56s
Condense CLAUDE.md from ~1960 to ~160 lines keeping only essential
architecture and lessons learned. Merge INSTALL.md into README.md
and IMPLEMENTATION.md into CLAUDE.md, then delete the source files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:14:07 +01:00
ea64dbfb33 Update .gitignore: add logs/ dir and compiled .mo files
CI/CD workflow now compiles .mo files during release, so they
no longer need to be tracked in git.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:04:34 +01:00
18 changed files with 734 additions and 2494 deletions

11
.gitignore vendored
View File

@@ -1,14 +1,8 @@
# Linked sources
wp-core
wp-plugins
tpp
# Editor swap files
*.*swp
# Composer
vendor/
composer.lock
# Cache
cache/
@@ -16,9 +10,14 @@ cache/
# Development files
.vscode/
.idea/
logs/
*.log
# OS files
.DS_Store
Thumbs.db
.directory
# Binary files
languages/*.mo

View File

@@ -5,6 +5,30 @@ 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.3.0] - 2026-03-01
### Added
- **Include Non-Public Products**: New option to include draft and private products in composable product selections
- Global setting under WooCommerce > Settings > Composable Products
- Per-product override in the Composable Options tab (Use global default / Yes / No)
- Useful when products should only be sold as part of a composition, not individually
- Translations for the new setting in all 6 locales (de_DE, de_CH, fr_CH, it_CH + informal variants)
## [1.2.1] - 2026-03-01
### Changed
- Consolidated documentation: merged INSTALL.md into README.md, merged IMPLEMENTATION.md into CLAUDE.md
- Condensed CLAUDE.md from ~1960 lines to ~160 lines, keeping only essential architecture and lessons learned
- README.md now includes full installation guide, usage tutorial, and troubleshooting section
- Cleaned up .gitignore
### Removed
- INSTALL.md (content merged into README.md)
- IMPLEMENTATION.md (content merged into CLAUDE.md)
## [1.2.0] - 2026-03-01
### Fixed

1976
CLAUDE.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,434 +0,0 @@
# WooCommerce Composable Products - Implementation Summary
## Overview
This document provides a technical overview of the WooCommerce Composable Products plugin implementation.
**Version:** 1.0.0
**Created:** 2024-12-31
**AI-Generated:** 100% created with Claude.AI assistance
## Architecture
### Plugin Structure
```txt
wc-composable-product/
├── assets/ # Frontend assets
│ ├── css/
│ │ ├── admin.css # Admin styles
│ │ └── frontend.css # Frontend styles
│ └── js/
│ ├── admin.js # Admin JavaScript
│ └── frontend.js # Frontend JavaScript
├── cache/ # Twig template cache
├── includes/ # PHP classes
│ ├── Admin/
│ │ ├── Product_Data.php # Product data tab
│ │ └── Settings.php # Settings page
│ ├── Cart_Handler.php # Cart integration
│ ├── Plugin.php # Main plugin class
│ ├── Product_Selector.php # Frontend selector
│ └── Product_Type.php # Custom product type
├── languages/ # Translation files
│ └── wc-composable-product.pot
├── templates/ # Twig templates
│ └── product-selector.twig
└── wc-composable-product.php # Main plugin file
```
## Core Components
### 1. Main Plugin Class (`Plugin.php`)
**Responsibilities:**
- Singleton pattern implementation
- Twig template engine initialization
- Hook registration
- Component initialization
- Asset enqueuing
**Key Methods:**
- `instance()`: Get singleton instance
- `init_twig()`: Initialize Twig with WordPress functions
- `render_template()`: Render Twig templates
- `add_product_type()`: Register composable product type
### 2. Product Type (`Product_Type.php`)
**Extends:** `WC_Product`
**Key Features:**
- Custom product type: `composable`
- Selection limit management (per-product or global)
- Pricing mode (fixed or sum)
- Product selection criteria (category/tag/SKU)
- Dynamic product availability
- Price calculation
**Key Methods:**
- `get_selection_limit()`: Get max selectable items
- `get_pricing_mode()`: Get pricing calculation mode
- `get_available_products()`: Query available products
- `calculate_composed_price()`: Calculate final price
### 3. Admin Settings (`Admin/Settings.php`)
**Extends:** `WC_Settings_Page`
**Global Settings:**
- Default selection limit
- Default pricing mode
- Display options (images, prices, total)
**Integration:** Adds tab to WooCommerce Settings
### 4. Product Data Tab (`Admin/Product_Data.php`)
**Responsibilities:**
- Add "Composable Options" tab to product edit page
- Render selection criteria fields
- Save product meta data
- Dynamic field visibility based on criteria type
**Saved Meta:**
- `_composable_selection_limit`: Item limit
- `_composable_pricing_mode`: Pricing calculation
- `_composable_criteria_type`: Selection method
- `_composable_categories`: Selected categories
- `_composable_tags`: Selected tags
- `_composable_skus`: SKU list
### 5. Product Selector (`Product_Selector.php`)
**Responsibilities:**
- Render frontend product selection interface
- Prepare data for Twig template
- Apply display settings
**Template Variables:**
- `products`: Available products array
- `selection_limit`: Max selections
- `pricing_mode`: Pricing calculation
- `show_images/prices/total`: Display flags
### 6. Cart Handler (`Cart_Handler.php`)
**Responsibilities:**
- Validate product selection
- Add selected products to cart data
- Calculate dynamic pricing
- Display selected products in cart
**Hooks:**
- `woocommerce_add_to_cart_validation`: Validate selections
- `woocommerce_add_cart_item_data`: Store selections
- `woocommerce_before_calculate_totals`: Update prices
- `woocommerce_get_item_data`: Display in cart
## Frontend Implementation
### Product Selector Template (`product-selector.twig`)
**Features:**
- Responsive grid layout
- Checkbox-based selection
- Product images and prices
- Real-time total calculation
- AJAX add-to-cart
**Data Attributes:**
- `data-product-id`: Composable product ID
- `data-selection-limit`: Max selections
- `data-pricing-mode`: Pricing mode
- `data-price`: Individual product prices
### JavaScript (`frontend.js`)
**Functionality:**
- Selection limit enforcement
- Visual feedback on selection
- Real-time price updates (sum mode)
- AJAX cart operations
- Error/success messages
**Key Functions:**
- `handleCheckboxChange()`: Selection logic
- `updateTotalPrice()`: Calculate total
- `addToCart()`: AJAX add-to-cart
- `showMessage()`: User feedback
### CSS Styling
**Approach:**
- Grid-based layout (responsive)
- Card-style product items
- Visual selection states
- Mobile-first design
- Breakpoints: 768px, 480px
## Data Flow
### Creating a Composable Product
1. Admin selects "Composable product" type
2. Configure selection limit and pricing mode
3. Choose selection criteria (category/tag/SKU)
4. Save product metadata
5. WooCommerce registers product with custom type
### Frontend Display
1. Customer visits product page
2. `Cart_Handler` renders `Product_Selector`
3. `Product_Type::get_available_products()` queries products
4. Twig template renders grid with products
5. JavaScript handles interactions
### Adding to Cart
1. Customer selects products (JavaScript validation)
2. Click "Add to Cart" button
3. AJAX request with selected product IDs
4. `Cart_Handler::validate_add_to_cart()` validates
5. `Cart_Handler::add_cart_item_data()` stores selections
6. `Cart_Handler::calculate_cart_item_price()` updates price
7. Product added to cart with custom data
### Cart Display
1. WooCommerce loads cart
2. `Cart_Handler::get_cart_item_from_session()` restores data
3. `Cart_Handler::display_cart_item_data()` shows selections
4. Price calculated dynamically on each cart load
## Security Implementation
### Input Sanitization
- **Integers:** `absint()` for IDs and limits
- **Text:** `sanitize_text_field()` for modes and types
- **Textarea:** `sanitize_textarea_field()` for SKUs
- **Arrays:** `array_map()` with sanitization functions
### Output Escaping
- **HTML:** `esc_html()`, `esc_html_e()`
- **Attributes:** `esc_attr()`
- **URLs:** `esc_url()`
- **JavaScript:** Localized scripts with escaped data
### Validation
- Selection limit enforcement
- Product availability verification
- Cart data validation
- Nonce verification (via WooCommerce)
## Internationalization
### Text Domain
`wc-composable-product`
### Translation Functions
- `__()`: Return translated string
- `_e()`: Echo translated string
- `sprintf()` with `__()`: Variable substitution
### POT File
Generated template: `languages/wc-composable-product.pot`
**Supported Locales (per CLAUDE.md):**
- en_US (English)
- de_DE, de_DE_informal (German - Germany)
- de_CH, de_CH_informal (German - Switzerland)
- fr_CH (French - Switzerland)
- it_CH (Italian - Switzerland)
## Performance Considerations
### Caching
- Twig templates cached in `cache/` directory
- Auto-reload enabled in debug mode
- Optimized Composer autoloader
### Database Queries
- Efficient `WP_Query` for product selection
- Meta queries for SKU filtering
- Taxonomy queries for category/tag filtering
### Asset Loading
- Scripts only on relevant pages
- Minification ready (use build tools)
- Conditional enqueuing
## Extensibility
### Hooks & Filters
**Available Filters:**
- `wc_composable_settings`: Modify settings array
- `woocommerce_product_class`: Custom product class
- `product_type_selector`: Product type registration
**Customization Points:**
- Twig templates (override in theme)
- CSS styling (enqueue custom styles)
- JavaScript behavior (extend object)
### Developer API
```php
// Get composable product
$product = wc_get_product($product_id);
// Check if composable
if ($product->get_type() === 'composable') {
// Get available products
$products = $product->get_available_products();
// Get selection limit
$limit = $product->get_selection_limit();
// Calculate price
$price = $product->calculate_composed_price($selected_ids);
}
```
## Testing Checklist
### Admin Testing
- [ ] Product type appears in dropdown
- [ ] Composable Options tab displays
- [ ] Selection criteria toggle works
- [ ] Meta data saves correctly
- [ ] Settings page accessible
- [ ] Global defaults apply
### Frontend Testing
- [ ] Product selector renders
- [ ] Selection limit enforced
- [ ] Price calculation accurate (both modes)
- [ ] AJAX add-to-cart works
- [ ] Cart displays selections
- [ ] Checkout processes correctly
### Edge Cases
- [ ] Empty criteria (no products)
- [ ] Out of stock products excluded
- [ ] Invalid product selections rejected
- [ ] Multiple cart items unique
- [ ] Session persistence
## Known Limitations
1. **Variable Products:** Currently supports simple products in selection
2. **Grouped Products:** Cannot be used as selectable items
3. **Stock Management:** No automatic stock reduction for selected items
4. **Caching:** Template cache needs manual clearing after updates
## Future Enhancements
Potential features for future versions:
- Variable product support in selection
- Quantity selection per item (not just presence)
- Visual bundle previews
- Advanced pricing rules
- Stock management integration
- Product recommendations
- Selection templates/presets
- Multi-currency support enhancements
## Dependencies
### Runtime
- PHP 8.3+
- WordPress 6.0+
- WooCommerce 8.0+
- Twig 3.0 (via Composer)
### Development
- Composer for dependency management
- WP-CLI for i18n operations (optional)
## Deployment
### Production Checklist
1. Run `composer install --no-dev --optimize-autoloader`
2. Ensure `vendor/` directory is included
3. Ensure `cache/` directory is writable
4. Test on staging environment
5. Clear all caches after activation
6. Verify WooCommerce compatibility
### Release Package
Must include:
- All PHP files
- `vendor/` directory
- Assets (CSS, JS)
- Templates
- Language files
- Documentation
Must exclude:
- `.git/` directory
- `composer.lock`
- Development files
- `wp-core/`, `wp-plugins/` symlinks
## Support & Maintenance
### Code Standards
- WordPress Coding Standards
- WooCommerce best practices
- PSR-4 autoloading
- Inline documentation
### Version Control
- Semantic versioning (MAJOR.MINOR.PATCH)
- Changelog maintained
- Annotated git tags
- Development on `dev` branch
---
**Last Updated:** 2024-12-31
**Maintainer:** Marco Graetsch
**AI Assistant:** Claude.AI (Anthropic)

View File

@@ -1,150 +0,0 @@
# Installation Guide
## Requirements
Before installing the WooCommerce Composable Products plugin, ensure your system meets these requirements:
- **PHP**: 8.3 or higher
- **WordPress**: 6.0 or higher
- **WooCommerce**: 8.0 or higher
- **Composer**: For dependency management
## Installation Steps
### 1. Upload Plugin Files
Upload the plugin directory to your WordPress installation:
```bash
/wp-content/plugins/wc-composable-product/
```
### 2. Install Dependencies
Navigate to the plugin directory and install dependencies:
```bash
cd /wp-content/plugins/wc-composable-product/
composer install --no-dev --optimize-autoloader
```
### 3. Activate Plugin
1. Log in to your WordPress admin panel
2. Navigate to **Plugins > Installed Plugins**
3. Find "WooCommerce Composable Products"
4. Click **Activate**
### 4. Configure Settings
After activation, configure the plugin:
1. Navigate to **WooCommerce > Settings**
2. Click on the **Composable Products** tab
3. Configure default settings:
- **Default Selection Limit**: Number of items customers can select (default: 5)
- **Default Pricing Mode**: Choose between "Sum of selected products" or "Fixed price"
- **Display Options**: Toggle product images, prices, and totals
## Creating Your First Composable Product
### Step 1: Create a New Product
1. Go to **Products > Add New**
2. Enter a product name (e.g., "Custom Sticker Pack")
### Step 2: Set Product Type
1. In the **Product Data** panel, select **Composable product** from the dropdown
### Step 3: Configure General Settings
In the **General** tab:
- Set a **Regular price** (used if pricing mode is "Fixed")
- Configure **Selection Limit** (leave empty to use global default)
- Choose **Pricing Mode** (leave empty to use global default)
### Step 4: Configure Composable Options
Click on the **Composable Options** tab:
1. **Selection Criteria**: Choose how to select available products
- **By Category**: Select product categories
- **By Tag**: Select product tags
- **By SKU**: Enter comma-separated SKUs
2. Based on your selection:
- **Categories**: Select one or more categories from the dropdown
- **Tags**: Select one or more tags from the dropdown
- **SKUs**: Enter SKUs like: `STICKER-01, STICKER-02, STICKER-03`
### Step 5: Publish
Click **Publish** to make your composable product live.
## Frontend Usage
When customers visit your composable product:
1. They see a grid of available products based on your criteria
2. They can select up to the configured limit
3. The total price updates in real-time (if using sum pricing mode)
4. Click "Add to Cart" to add the composition to their cart
5. Selected products are displayed in the cart
## Troubleshooting
### Plugin Won't Activate
- Ensure WooCommerce is installed and activated first
- Check PHP version (must be 8.3+)
- Verify Composer dependencies are installed
### Products Not Showing in Selector
- Check that products are published and in stock
- Verify the selection criteria (category/tag/SKU) is correct
- Ensure products match the criteria you configured
### Twig Template Errors
- Ensure the `vendor/` directory exists and contains Twig
- Run `composer install` again
- Check that the `cache/` directory is writable
### JavaScript Not Working
- Clear browser cache
- Check browser console for errors
- Ensure jQuery is loaded (WooCommerce includes it)
## Updating
When updating the plugin:
1. Deactivate the plugin
2. Replace plugin files
3. Run `composer install --no-dev --optimize-autoloader`
4. Reactivate the plugin
5. Clear all caches (WordPress, browser, CDN)
## Uninstallation
To completely remove the plugin:
1. Deactivate the plugin
2. Delete the plugin from the Plugins page
3. Optionally clean up database entries (WooCommerce will handle this automatically)
## Support
For issues and feature requests:
- GitHub: https://github.com/magdev/wc-composable-product/issues
- Documentation: See README.md
## Next Steps
- Customize the template by editing `templates/product-selector.twig`
- Modify styles in `assets/css/frontend.css`
- Translate the plugin using the provided `.pot` file
- Create categories/tags for easier product organization

107
README.md
View File

@@ -1,12 +1,8 @@
# WooCommerce Composable Products
Create composable products where customers can select a limited number of items from a configurable set of products.
Create composable products where customers can select a limited number of items from a configurable set of products. Think of it as a "build your own gift box" or "create your sticker pack" feature.
## Description
This plugin adds a new product type to WooCommerce that allows customers to build their own product bundles by selecting from a predefined set of simple or variable products. Think of it as a "build your own gift box" or "create your sticker pack" feature.
### Key Features
## Key Features
- **Custom Product Type**: New "Composable Product" type in WooCommerce
- **Flexible Selection**: Define available products by category, tag, or SKU
@@ -23,33 +19,94 @@ This plugin adds a new product type to WooCommerce that allows customers to buil
- PHP 8.3 or higher
- WordPress 6.0 or higher
- WooCommerce 8.0 or higher
- Composer (for dependency management)
## Installation
1. Upload the plugin files to `/wp-content/plugins/wc-composable-product/`
2. Run `composer install --no-dev` in the plugin directory
3. Activate the plugin through the 'Plugins' menu in WordPress
4. Configure global settings under WooCommerce > Settings > Composable Products
### From Release Package
1. Download the latest release ZIP from the releases page
2. In WordPress admin, go to **Plugins > Add New > Upload Plugin**
3. Upload the ZIP file and click **Install Now**
4. Activate the plugin through the **Plugins** menu
5. Configure global settings under **WooCommerce > Settings > Composable Products**
### From Source
1. Upload the plugin directory to `/wp-content/plugins/wc-composable-product/`
2. Install dependencies:
```bash
cd /wp-content/plugins/wc-composable-product/
composer install --no-dev --optimize-autoloader
```
3. Activate the plugin through the **Plugins** menu in WordPress
4. Configure global settings under **WooCommerce > Settings > Composable Products**
## Usage
### Creating a Composable Product
1. Go to Products > Add New
2. Select "Composable Product" as the product type
3. Configure product details:
- Set the selection limit (or use global default)
- Choose pricing mode (fixed or sum)
- Define available products by category, tag, or SKU
4. Publish the product
### Global Settings
Navigate to WooCommerce > Settings > Composable Products to configure:
Navigate to **WooCommerce > Settings > Composable Products** to configure:
- Default selection limit
- Default pricing mode
- Display options
- **Default Selection Limit**: Number of items customers can select (default: 5)
- **Default Pricing Mode**: Choose between "Sum of selected products" or "Fixed price"
- **Display Options**: Toggle product images, prices, and totals
### Creating a Composable Product
1. Go to **Products > Add New**
2. Select **Composable product** from the Product Data dropdown
3. In the **General** tab:
- Set a **Regular price** (used when pricing mode is "Fixed")
- Configure **Selection Limit** (leave empty to use global default)
- Choose **Pricing Mode** (leave empty to use global default)
4. Click the **Composable Options** tab:
- **Selection Criteria**: Choose how to define available products
- **By Category**: Select one or more product categories
- **By Tag**: Select one or more product tags
- **By SKU**: Enter comma-separated SKUs (e.g., `STICKER-01, STICKER-02`)
5. Click **Publish**
### Frontend Behavior
When customers visit a composable product page:
1. A grid of available products is displayed based on configured criteria
2. Customers select up to the configured limit via checkboxes
3. Total price updates in real-time (in sum pricing mode)
4. Stock indicators show availability (green/orange/red badges)
5. Click "Add to Cart" to add the composition to cart
6. Selected products are listed in the cart and checkout
## Troubleshooting
### Plugin Won't Activate
- Ensure WooCommerce is installed and activated first
- Check PHP version (must be 8.3+)
- Verify Composer dependencies are installed (`vendor/` directory exists)
### Products Not Showing in Selector
- Check that products are published
- Verify the selection criteria (category/tag/SKU) matches existing products
- Ensure the criteria type and values are saved in the Composable Options tab
### Twig Template Errors
- Ensure the `vendor/` directory exists and contains Twig
- Run `composer install` again
- Check that the `cache/` directory is writable
### Updating
1. Deactivate the plugin
2. Replace plugin files (or upload new release ZIP)
3. If installed from source: run `composer install --no-dev --optimize-autoloader`
4. Reactivate the plugin
5. Clear all caches (WordPress, browser, CDN)
## Development
@@ -96,4 +153,4 @@ Marco Graetsch
## Support
For issues and feature requests, please use the GitHub issue tracker.
For issues and feature requests, please use the issue tracker.

335
composer.lock generated Normal file
View File

@@ -0,0 +1,335 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1342d597ec95bd7abf806825a199cae0",
"packages": [
{
"name": "symfony/deprecation-contracts",
"version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-23T08:48:59+00:00"
},
{
"name": "twig/twig",
"version": "v3.22.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2",
"reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2",
"shasum": ""
},
"require": {
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
},
"type": "library",
"autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.22.2"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2025-12-14T11:28:47+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.3"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View File

@@ -95,6 +95,19 @@ class Product_Data {
<div id="composable_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<?php
woocommerce_wp_select([
'id' => '_composable_include_unpublished',
'label' => __('Include Non-Public Products', 'wc-composable-product'),
'description' => __('Allow draft and private products in the selection. Useful when products should only be sold as part of a composition.', 'wc-composable-product'),
'desc_tip' => true,
'options' => [
'' => __('Use global default', 'wc-composable-product'),
'yes' => __('Yes', 'wc-composable-product'),
'no' => __('No', 'wc-composable-product'),
],
'value' => get_post_meta($post->ID, '_composable_include_unpublished', true) ?: '',
]);
woocommerce_wp_select([
'id' => '_composable_criteria_type',
'label' => __('Selection Criteria', 'wc-composable-product'),
@@ -181,6 +194,10 @@ class Product_Data {
$pricing_mode = isset($_POST['_composable_pricing_mode']) ? sanitize_text_field($_POST['_composable_pricing_mode']) : '';
update_post_meta($post_id, '_composable_pricing_mode', $pricing_mode);
// Save include unpublished
$include_unpublished = isset($_POST['_composable_include_unpublished']) ? sanitize_text_field($_POST['_composable_include_unpublished']) : '';
update_post_meta($post_id, '_composable_include_unpublished', $include_unpublished);
// Save criteria type
$criteria_type = isset($_POST['_composable_criteria_type']) ? sanitize_text_field($_POST['_composable_criteria_type']) : 'category';
update_post_meta($post_id, '_composable_criteria_type', $criteria_type);

View File

@@ -60,6 +60,13 @@ class Settings extends \WC_Settings_Page {
],
'desc_tip' => true,
],
[
'title' => __('Include Non-Public Products', 'wc-composable-product'),
'desc' => __('Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually.', 'wc-composable-product'),
'id' => 'wc_composable_include_unpublished',
'type' => 'checkbox',
'default' => 'no',
],
[
'title' => __('Show Product Images', 'wc-composable-product'),
'desc' => __('Display product images in the selection interface.', 'wc-composable-product'),

View File

@@ -97,6 +97,22 @@ class Product_Type extends \WC_Product {
return true;
}
/**
* Check if non-public products should be included
*
* @return bool
*/
public function should_include_unpublished() {
$per_product = $this->get_meta('_composable_include_unpublished', true);
if ($per_product === 'yes') {
return true;
}
if ($per_product === 'no') {
return false;
}
return get_option('wc_composable_include_unpublished', 'no') === 'yes';
}
/**
* Get available products based on criteria
*
@@ -104,10 +120,11 @@ class Product_Type extends \WC_Product {
*/
public function get_available_products() {
$criteria = $this->get_selection_criteria();
$include_unpublished = $this->should_include_unpublished();
$args = [
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish',
'post_status' => $include_unpublished ? ['publish', 'draft', 'private'] : 'publish',
'orderby' => 'title',
'order' => 'ASC',
];
@@ -177,11 +194,11 @@ class Product_Type extends \WC_Product {
$variation_ids = $product->get_children();
foreach ($variation_ids as $variation_id) {
$variation = wc_get_product($variation_id);
if ($variation && $variation->is_purchasable()) {
if ($variation && ($include_unpublished || $variation->is_purchasable())) {
$products[] = $variation;
}
}
} elseif ($product->is_purchasable()) {
} elseif ($include_unpublished || $product->is_purchasable()) {
$products[] = $product;
}
}

View File

@@ -242,3 +242,23 @@ 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."
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln."
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr "Ja"
#: includes/Admin/Product_Data.php
msgid "No"
msgstr "Nein"

View File

@@ -242,3 +242,23 @@ 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."
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln."
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr "Ja"
#: includes/Admin/Product_Data.php
msgid "No"
msgstr "Nein"

View File

@@ -242,3 +242,23 @@ 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."
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln."
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr "Ja"
#: includes/Admin/Product_Data.php
msgid "No"
msgstr "Nein"

View File

@@ -242,3 +242,23 @@ 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."
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln."
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr "Ja"
#: includes/Admin/Product_Data.php
msgid "No"
msgstr "Nein"

View File

@@ -242,3 +242,23 @@ 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."
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr "Inclure les produits non publics"
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr "Autoriser les brouillons et les produits privés dans les sélections de produits composables. Utile lorsque les produits ne doivent être vendus que dans le cadre d'une composition, pas individuellement."
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "Autoriser les brouillons et les produits privés dans la sélection. Utile lorsque les produits ne doivent être vendus que dans le cadre d'une composition."
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr "Oui"
#: includes/Admin/Product_Data.php
msgid "No"
msgstr "Non"

View File

@@ -242,3 +242,23 @@ 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."
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr "Includi prodotti non pubblici"
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr "Consenti la visualizzazione di bozze e prodotti privati nelle selezioni dei prodotti componibili. Utile quando i prodotti devono essere venduti solo come parte di una composizione, non singolarmente."
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "Consenti bozze e prodotti privati nella selezione. Utile quando i prodotti devono essere venduti solo come parte di una composizione."
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr "Sì"
#: includes/Admin/Product_Data.php
msgid "No"
msgstr "No"

View File

@@ -241,3 +241,23 @@ msgstr ""
#: templates/product-selector.twig
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr ""
#: includes/Admin/Settings.php, includes/Admin/Product_Data.php
msgid "Include Non-Public Products"
msgstr ""
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr ""
#: includes/Admin/Product_Data.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr ""
#: includes/Admin/Product_Data.php
msgid "Yes"
msgstr ""
#: includes/Admin/Product_Data.php
msgid "No"
msgstr ""

View File

@@ -4,7 +4,7 @@
* Plugin Name: WooCommerce Composable Products
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-composable-product
* Description: Create composable products where customers select a limited number of items from a configurable set
* Version: 1.2.0
* Version: 1.3.0
* Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev
* License: GPL v3 or later
@@ -20,7 +20,7 @@
defined('ABSPATH') || exit;
// Define plugin constants
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.2.0');
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.3.0');
define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__);
define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__));
define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__));