You've already forked wc-composable-product
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd5965ae4c | |||
| 9bc7a62f20 | |||
| ed66c96d3d | |||
| ea64dbfb33 | |||
| 6507f4d8bb | |||
| 29a68b0be4 | |||
| fb8ddf903e | |||
| 33d2836de0 | |||
| c036a37602 | |||
| efedd1bf29 | |||
| 12388af5a0 | |||
| c39c13ffed | |||
| 7931dbeef9 | |||
| ee81de86c2 | |||
| 669888817b | |||
| 5564b888fc | |||
| 91aca25169 | |||
| 4195fb2651 | |||
| ba28ae174f | |||
| 755108a7d3 | |||
| 85983d5473 | |||
| 252b187600 | |||
| 8185a77697 | |||
| 6c2e317230 | |||
| 58f5329bc4 | |||
| 0767016370 | |||
| fa7ec0e422 | |||
| f4d2543d4e | |||
| 9e4513f911 | |||
| 4dc7b767a8 | |||
| f763e35d19 |
205
.gitea/workflows/release.yml
Normal file
205
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
name: Create Release Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.3'
|
||||||
|
extensions: mbstring, xml, zip, intl, gettext
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: Get version from tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
VERSION=${GITHUB_REF_NAME#v}
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "Building version: $VERSION"
|
||||||
|
|
||||||
|
- name: Validate composer.json
|
||||||
|
run: composer validate --strict
|
||||||
|
|
||||||
|
- name: Install Composer dependencies (production)
|
||||||
|
run: |
|
||||||
|
composer config platform.php 8.3.0
|
||||||
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
|
||||||
|
- name: Install gettext
|
||||||
|
run: apt-get update && apt-get install -y gettext
|
||||||
|
|
||||||
|
- name: Compile translations
|
||||||
|
run: |
|
||||||
|
for po in languages/*.po; do
|
||||||
|
if [ -f "$po" ]; then
|
||||||
|
mo="${po%.po}.mo"
|
||||||
|
echo "Compiling $po to $mo"
|
||||||
|
msgfmt -o "$mo" "$po"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify plugin version matches tag
|
||||||
|
run: |
|
||||||
|
PLUGIN_VERSION=$(grep -oP "Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+" wc-composable-product.php | head -1)
|
||||||
|
TAG_VERSION=${{ steps.version.outputs.version }}
|
||||||
|
if [ "$PLUGIN_VERSION" != "$TAG_VERSION" ]; then
|
||||||
|
echo "Error: Plugin version ($PLUGIN_VERSION) does not match tag version ($TAG_VERSION)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Version verified: $PLUGIN_VERSION"
|
||||||
|
|
||||||
|
- name: Create release directory
|
||||||
|
run: mkdir -p releases
|
||||||
|
|
||||||
|
- name: Build release package
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
PLUGIN_NAME="wc-composable-product"
|
||||||
|
RELEASE_FILE="releases/${PLUGIN_NAME}-${VERSION}.zip"
|
||||||
|
|
||||||
|
# Move to parent directory for proper zip structure
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Create zip with proper WordPress plugin structure
|
||||||
|
zip -r "${PLUGIN_NAME}/${RELEASE_FILE}" "${PLUGIN_NAME}" \
|
||||||
|
-x "${PLUGIN_NAME}/.git/*" \
|
||||||
|
-x "${PLUGIN_NAME}/.gitea/*" \
|
||||||
|
-x "${PLUGIN_NAME}/.github/*" \
|
||||||
|
-x "${PLUGIN_NAME}/.vscode/*" \
|
||||||
|
-x "${PLUGIN_NAME}/.claude/*" \
|
||||||
|
-x "${PLUGIN_NAME}/CLAUDE.md" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-core" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-core/*" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-plugins" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-plugins/*" \
|
||||||
|
-x "${PLUGIN_NAME}/releases/*" \
|
||||||
|
-x "${PLUGIN_NAME}/cache/*" \
|
||||||
|
-x "${PLUGIN_NAME}/composer.lock" \
|
||||||
|
-x "${PLUGIN_NAME}/*.log" \
|
||||||
|
-x "${PLUGIN_NAME}/.gitignore" \
|
||||||
|
-x "${PLUGIN_NAME}/.editorconfig" \
|
||||||
|
-x "${PLUGIN_NAME}/phpcs.xml*" \
|
||||||
|
-x "${PLUGIN_NAME}/phpunit.xml*" \
|
||||||
|
-x "${PLUGIN_NAME}/tests/*" \
|
||||||
|
-x "${PLUGIN_NAME}/*.po~" \
|
||||||
|
-x "${PLUGIN_NAME}/*.bak" \
|
||||||
|
-x "*.DS_Store"
|
||||||
|
|
||||||
|
cd "${PLUGIN_NAME}"
|
||||||
|
echo "Created: ${RELEASE_FILE}"
|
||||||
|
|
||||||
|
- name: Generate checksums
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
|
cd releases
|
||||||
|
sha256sum "wc-composable-product-${VERSION}.zip" > "wc-composable-product-${VERSION}.zip.sha256"
|
||||||
|
|
||||||
|
echo "SHA256:"
|
||||||
|
cat "wc-composable-product-${VERSION}.zip.sha256"
|
||||||
|
|
||||||
|
- name: Verify package structure
|
||||||
|
run: |
|
||||||
|
set +o pipefail
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
echo "Package contents:"
|
||||||
|
unzip -l "releases/wc-composable-product-${VERSION}.zip" | head -50 || true
|
||||||
|
|
||||||
|
# Verify main file is at correct location
|
||||||
|
if unzip -l "releases/wc-composable-product-${VERSION}.zip" | grep -q "wc-composable-product/wc-composable-product.php"; then
|
||||||
|
echo "✓ Main plugin file at correct location"
|
||||||
|
else
|
||||||
|
echo "✗ Error: Main plugin file not found at wc-composable-product/wc-composable-product.php"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify vendor directory is included
|
||||||
|
if unzip -l "releases/wc-composable-product-${VERSION}.zip" | grep -q "wc-composable-product/vendor/"; then
|
||||||
|
echo "✓ Vendor directory included"
|
||||||
|
else
|
||||||
|
echo "✗ Error: Vendor directory not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Extract changelog for release notes
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
# Extract changelog section for this version
|
||||||
|
NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/p" CHANGELOG.md | sed '$ d' | tail -n +2)
|
||||||
|
if [ -z "$NOTES" ]; then
|
||||||
|
NOTES="Release version ${VERSION}"
|
||||||
|
fi
|
||||||
|
# Save to file for multi-line output
|
||||||
|
echo "$NOTES" > release_notes.txt
|
||||||
|
echo "Release notes extracted"
|
||||||
|
|
||||||
|
- name: Create Gitea Release
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.SRC_GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
TAG_NAME=${{ github.ref_name }}
|
||||||
|
PRERELEASE="false"
|
||||||
|
if [[ "$TAG_NAME" == *-* ]]; then
|
||||||
|
PRERELEASE="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read release notes
|
||||||
|
BODY=$(cat release_notes.txt)
|
||||||
|
|
||||||
|
# Check if release already exists for this tag and delete it
|
||||||
|
EXISTING_RELEASE=$(curl -s \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}")
|
||||||
|
|
||||||
|
EXISTING_ID=$(echo "$EXISTING_RELEASE" | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
|
||||||
|
echo "Deleting existing release ID: $EXISTING_ID"
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${EXISTING_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create release via Gitea API
|
||||||
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${VERSION}\", \"body\": $(echo "$BODY" | jq -Rs .), \"draft\": false, \"prerelease\": ${PRERELEASE}}" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
|
||||||
|
|
||||||
|
if [ "$RELEASE_ID" == "null" ] || [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "Failed to create release:"
|
||||||
|
echo "$RELEASE_RESPONSE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Created release ID: $RELEASE_ID"
|
||||||
|
|
||||||
|
# Upload attachments
|
||||||
|
for file in "releases/wc-composable-product-${VERSION}.zip" "releases/wc-composable-product-${VERSION}.zip.sha256"; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
FILENAME=$(basename "$file")
|
||||||
|
echo "Uploading $FILENAME..."
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary "@$file" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${FILENAME}"
|
||||||
|
echo "Uploaded $FILENAME"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Release created successfully: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG_NAME}"
|
||||||
11
.gitignore
vendored
Normal file → Executable file
11
.gitignore
vendored
Normal file → Executable file
@@ -1,14 +1,8 @@
|
|||||||
# Linked sources
|
|
||||||
wp-core
|
|
||||||
wp-plugins
|
|
||||||
tpp
|
|
||||||
|
|
||||||
# Editor swap files
|
# Editor swap files
|
||||||
*.*swp
|
*.*swp
|
||||||
|
|
||||||
# Composer
|
# Composer
|
||||||
vendor/
|
vendor/
|
||||||
composer.lock
|
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
cache/
|
cache/
|
||||||
@@ -16,9 +10,14 @@ cache/
|
|||||||
# Development files
|
# Development files
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Binary files
|
||||||
|
languages/*.mo
|
||||||
|
|
||||||
|
|||||||
244
CHANGELOG.md
244
CHANGELOG.md
@@ -5,6 +5,250 @@ 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/),
|
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).
|
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
|
||||||
|
|
||||||
|
- **CRITICAL**: Product selection always empty regardless of configuration (categories, tags, or SKUs)
|
||||||
|
- Root cause: Product query used a `meta_query` checking `_product_type` in `wp_postmeta`, but WooCommerce stores product types in the `product_type` **taxonomy** — the `!=` comparison with a non-existent meta key caused an INNER JOIN returning zero results
|
||||||
|
- Fix: Replaced broken `meta_query` with correct `tax_query` using `product_type` taxonomy to exclude composable products
|
||||||
|
- **CRITICAL**: Cart price always 0.00 despite correct frontend price calculation
|
||||||
|
- Root cause: `composable_price_calculated` flag was persisted to the cart session, preventing price recalculation on subsequent page loads — but `set_price()` only modifies the in-memory product object and is lost between requests
|
||||||
|
- Fix: Removed per-item session flag; the existing static `$already_calculated` flag already prevents duplicate calculation within a single request
|
||||||
|
- **Admin tab rendering**: Both General and Composable Options panels visible on initial page load
|
||||||
|
- Root cause: JavaScript manually showed `#composable_product_data` via `.show()` without hiding the General panel
|
||||||
|
- Fix: Trigger WooCommerce's native tab click instead, so the tab system handles panel visibility correctly
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Gitea CI/CD release workflow** (`.gitea/workflows/release.yml`)
|
||||||
|
- Triggered on `v*` tags
|
||||||
|
- Installs PHP 8.3 with production Composer dependencies
|
||||||
|
- Compiles `.po` → `.mo` translations
|
||||||
|
- Verifies plugin version matches tag
|
||||||
|
- Builds release ZIP with proper WordPress directory structure
|
||||||
|
- Generates SHA-256 checksums
|
||||||
|
- Verifies package contains main plugin file and vendor directory
|
||||||
|
- Extracts changelog for release notes
|
||||||
|
- Creates Gitea release with attachments via API
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Debug logging from v1.1.14 (no longer needed after root cause identified)
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified files: includes/Product_Type.php, includes/Cart_Handler.php, assets/js/admin.js
|
||||||
|
- New file: .gitea/workflows/release.yml
|
||||||
|
- Product query now correctly uses `tax_query` with `product_type` taxonomy (`NOT IN` operator)
|
||||||
|
- Cart price recalculated on every request via `woocommerce_before_calculate_totals` hook
|
||||||
|
- Admin JS uses `$('ul.product_data_tabs li.composable_options a').trigger('click')` for native WooCommerce tab handling
|
||||||
|
|
||||||
|
## [1.1.14] - 2025-12-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **DEBUG**: Comprehensive debug logging to troubleshoot product retrieval issues
|
||||||
|
- Error log output shows selection criteria, query arguments, and results
|
||||||
|
- Logs each product/variation being added to help identify filtering issues
|
||||||
|
- Enable by setting WP_DEBUG to true in wp-config.php
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified file: includes/Product_Type.php (added error_log statements throughout get_available_products())
|
||||||
|
- Logs criteria array (categories, tags, SKUs)
|
||||||
|
- Logs WP_Query arguments before execution
|
||||||
|
- Logs number of posts found by query
|
||||||
|
- Logs each variable product's variation count
|
||||||
|
- Logs each variation/simple product being added with name
|
||||||
|
- Logs total products available at end
|
||||||
|
- All logging wrapped in WP_DEBUG checks (no performance impact in production)
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- This is a debug release to help diagnose why products aren't showing
|
||||||
|
- No functional changes from v1.1.13
|
||||||
|
- User should enable WP_DEBUG and check debug.log or error.log
|
||||||
|
- Log output will show exactly where products are being filtered out
|
||||||
|
- All translation files remain at 100% completion (57/57 strings)
|
||||||
|
|
||||||
|
## [1.1.13] - 2025-12-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CRITICAL**: "No products available for selection" message showing even when products are configured
|
||||||
|
- Removed overly strict `is_in_stock()` requirement that was filtering out all products
|
||||||
|
- Products now show regardless of stock status (out-of-stock items are displayed but disabled)
|
||||||
|
- Added `'relation' => 'AND'` to meta_query for proper handling of multiple meta conditions
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Product retrieval now shows all purchasable products, not just in-stock ones
|
||||||
|
- Stock status still displayed on frontend with appropriate styling
|
||||||
|
- Out-of-stock items shown but disabled via checkbox and visual indicators
|
||||||
|
- Frontend stock management from v1.1.0 still fully functional
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified file: includes/Product_Type.php (lines 117-124, 177, 181)
|
||||||
|
- Changed from `$variation->is_in_stock() && $variation->is_purchasable()` to just `$variation->is_purchasable()`
|
||||||
|
- Changed from `$product->is_in_stock() && $product->is_purchasable()` to just `$product->is_purchasable()`
|
||||||
|
- Added `'relation' => 'AND'` to meta_query array for WordPress query compatibility
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- This fixes the issue where NO products were showing in the selector
|
||||||
|
- Stock validation still occurs at add-to-cart time (Stock_Manager class)
|
||||||
|
- Frontend still displays stock badges (in stock, low stock, out of stock)
|
||||||
|
- Out-of-stock items remain non-selectable via disabled checkboxes
|
||||||
|
- All translation files remain at 100% completion (57/57 strings)
|
||||||
|
|
||||||
|
## [1.1.12] - 2025-12-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CRITICAL**: Variable product variations still not appearing in product selector after v1.1.11 release
|
||||||
|
- Changed variation retrieval method from `get_available_variations()` to `get_children()` for more reliable variation ID retrieval
|
||||||
|
- `get_available_variations()` returns complex data arrays which may not work in all contexts
|
||||||
|
- `get_children()` returns simple array of variation IDs directly, ensuring consistent results
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified file: includes/Product_Type.php (lines 171-184)
|
||||||
|
- Changed from `$product->get_available_variations()` to `$product->get_children()`
|
||||||
|
- More direct and reliable method for retrieving variation IDs
|
||||||
|
- Each variation ID passed to `wc_get_product()` for full product object
|
||||||
|
- Maintains all stock and purchasability checks from v1.1.11
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- This is a patch release fixing the variable product support introduced in v1.1.11
|
||||||
|
- User reported "nope, still no product selectable" after v1.1.11
|
||||||
|
- Root cause: `get_available_variations()` returns variation data arrays instead of clean IDs
|
||||||
|
- `get_children()` is the standard WooCommerce method for retrieving variation IDs
|
||||||
|
- All translation files remain at 100% completion (57/57 strings - no changes needed)
|
||||||
|
|
||||||
|
## [1.1.11] - 2025-12-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **FEATURE**: Variable product support - composable products can now include variable products and their variations
|
||||||
|
- Variable products automatically expand to show all available variations as selectable items
|
||||||
|
- Each variation displays with full attribute information (e.g., "Product - Color: Red, Size: Large")
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Products not showing in selector when all available products were variable products
|
||||||
|
- Variable products were being filtered out because parent products aren't directly purchasable
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Modified `get_available_products()` to detect and handle variable products
|
||||||
|
- Variable products now expand into their individual variations
|
||||||
|
- Each variation checked individually for stock status and purchasability
|
||||||
|
- Simple products continue to work exactly as before
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified file: includes/Product_Type.php (lines 160-188)
|
||||||
|
- Added logic to detect `is_type('variable')` products
|
||||||
|
- Uses `get_available_variations()` to retrieve all variations
|
||||||
|
- Each variation validated with `is_in_stock()` and `is_purchasable()`
|
||||||
|
- Maintains backward compatibility with simple products
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- This is a feature enhancement release, not a bug fix
|
||||||
|
- Resolves the issue where categories containing only variable products showed no selections
|
||||||
|
- Variations display with their parent product name plus selected attributes
|
||||||
|
- Stock management works correctly for both simple products and variations
|
||||||
|
- All translation files remain at 100% completion (57/57 strings - no new strings added)
|
||||||
|
|
||||||
|
## [1.1.10] - 2025-12-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CRITICAL**: Admin panel - Both General and Composable tabs visible simultaneously on initial page load
|
||||||
|
- **CRITICAL**: Frontend - No products showing in product selector, only cart button and pricing visible
|
||||||
|
- Empty product grid now shows helpful message instead of blank space
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added explicit `display: none` to `#composable_product_data` panel for proper initial hiding
|
||||||
|
- Panel now only shows when `body.product-type-composable` class is present
|
||||||
|
- Added empty state message in product selector template when no products are configured
|
||||||
|
- Cleared Twig cache to ensure template changes take effect
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Empty state message: "No products available for selection. Please configure the product criteria in the admin panel."
|
||||||
|
- Translations for empty state message in all 6 supported locales (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH, it_CH)
|
||||||
|
- Recompiled .mo translation files
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified files: assets/css/admin.css (lines 7-16), templates/product-selector.twig (lines 12-15)
|
||||||
|
- Root cause (admin): Panel lacked explicit CSS hiding rule, relied only on `hidden` class
|
||||||
|
- Root cause (frontend): No feedback when products array is empty
|
||||||
|
- Solution: CSS specificity + empty state conditional in Twig template
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- This release fixes two critical bugs discovered immediately after v1.1.9
|
||||||
|
- Admin interface now correctly hides composable panel until product type is selected
|
||||||
|
- Frontend provides clear user feedback when product selection is unavailable
|
||||||
|
- All translation files now 100% complete (57/57 strings)
|
||||||
|
|
||||||
|
## [1.1.9] - 2025-12-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CRITICAL**: Admin rendering completely broken - tabs disappeared and fields appeared out of context after v1.1.8 release
|
||||||
|
- CSS selectors were too broad, hiding tab navigation along with field groups
|
||||||
|
- Removed `!important` flags that caused overly aggressive hiding
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Made CSS selectors more specific: `.options_group.show_if_composable` for field groups only
|
||||||
|
- Added separate rule for tab links: `.product_data_tabs li.composable_options`
|
||||||
|
- Tab navigation now works correctly without hiding itself
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- Modified files: assets/css/admin.css (lines 22-40)
|
||||||
|
- Root cause: `.show_if_composable` class used by WooCommerce for both tab links AND field groups
|
||||||
|
- Solution: Separate selectors for each use case to prevent unintended hiding
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- This release fixes critical regression introduced in v1.1.8
|
||||||
|
- Admin interface now renders correctly with visible tabs and properly positioned fields
|
||||||
|
- No `!important` flags needed with specific selectors
|
||||||
|
|
||||||
## [1.1.8] - 2025-12-31
|
## [1.1.8] - 2025-12-31
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -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)
|
|
||||||
150
INSTALL.md
150
INSTALL.md
@@ -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
|
|
||||||
133
README.md
133
README.md
@@ -1,51 +1,112 @@
|
|||||||
# WooCommerce Composable Products
|
# 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
|
## Key Features
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
- **Custom Product Type**: New "Composable Product" type in WooCommerce
|
- **Custom Product Type**: New "Composable Product" type in WooCommerce
|
||||||
- **Flexible Selection**: Define available products by category, tag, or SKU
|
- **Flexible Selection**: Define available products by category, tag, or SKU
|
||||||
|
- **Variable Product Support**: Automatically expands variable products into selectable variations
|
||||||
|
- **Stock Management**: Real-time stock validation, visual indicators, and automatic inventory tracking
|
||||||
- **Configurable Limits**: Set global or per-product selection limits
|
- **Configurable Limits**: Set global or per-product selection limits
|
||||||
- **Pricing Options**: Fixed price or sum of selected products
|
- **Pricing Options**: Fixed price or sum of selected products with full locale-aware formatting
|
||||||
- **Multi-language Support**: Fully translatable with i18n support
|
- **Multi-language Support**: Fully translated in 6 locales (de_DE, de_CH, fr_CH, it_CH + informal variants)
|
||||||
- **Modern UI**: Clean interface built with Twig templates and vanilla JavaScript
|
- **Modern UI**: Clean interface built with Twig templates and vanilla JavaScript
|
||||||
|
- **CI/CD**: Automated release workflow for Gitea
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- PHP 8.3 or higher
|
- PHP 8.3 or higher
|
||||||
- WordPress 6.0 or higher
|
- WordPress 6.0 or higher
|
||||||
- WooCommerce 8.0 or higher
|
- WooCommerce 8.0 or higher
|
||||||
|
- Composer (for dependency management)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Upload the plugin files to `/wp-content/plugins/wc-composable-product/`
|
### From Release Package
|
||||||
2. Run `composer install --no-dev` in the plugin directory
|
|
||||||
3. Activate the plugin through the 'Plugins' menu in WordPress
|
1. Download the latest release ZIP from the releases page
|
||||||
4. Configure global settings under WooCommerce > Settings > Composable Products
|
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
|
## 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
|
### Global Settings
|
||||||
|
|
||||||
Navigate to WooCommerce > Settings > Composable Products to configure:
|
Navigate to **WooCommerce > Settings > Composable Products** to configure:
|
||||||
- Default selection limit
|
|
||||||
- Default pricing mode
|
- **Default Selection Limit**: Number of items customers can select (default: 5)
|
||||||
- Display options
|
- **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
|
## Development
|
||||||
|
|
||||||
@@ -60,10 +121,28 @@ composer install
|
|||||||
### Translation
|
### Translation
|
||||||
|
|
||||||
Generate POT file:
|
Generate POT file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wp i18n make-pot . languages/wc-composable-product.pot
|
wp i18n make-pot . languages/wc-composable-product.pot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Compile translations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating Releases
|
||||||
|
|
||||||
|
Releases are automated via Gitea CI/CD. Push an annotated tag to trigger:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a v1.2.0 -m "Release v1.2.0"
|
||||||
|
git push origin v1.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
The workflow builds the release ZIP, compiles translations, generates checksums, and creates a Gitea release with attachments.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GPL v3 or later - see LICENSE file for details
|
GPL v3 or later - see LICENSE file for details
|
||||||
@@ -74,4 +153,4 @@ Marco Graetsch
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues and feature requests, please use the GitHub issue tracker.
|
For issues and feature requests, please use the issue tracker.
|
||||||
|
|||||||
25
assets/css/admin.css
Normal file → Executable file
25
assets/css/admin.css
Normal file → Executable file
@@ -4,10 +4,17 @@
|
|||||||
* @package WC_Composable_Product
|
* @package WC_Composable_Product
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Hide composable panel by default */
|
||||||
#composable_product_data {
|
#composable_product_data {
|
||||||
|
display: none;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Show composable panel when composable type is selected */
|
||||||
|
body.product-type-composable #composable_product_data {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.composable_criteria_group {
|
.composable_criteria_group {
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
@@ -19,29 +26,23 @@
|
|||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide composable-specific elements by default */
|
/* Hide composable-specific elements by default (but not tabs) */
|
||||||
.show_if_composable {
|
.options_group.show_if_composable {
|
||||||
display: none !important;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show composable elements when composable product type is selected */
|
/* Show composable elements when composable product type is selected */
|
||||||
body.product-type-composable .show_if_composable,
|
body.product-type-composable .options_group.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;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the Composable Options tab link by default */
|
/* Hide the Composable Options tab link by default */
|
||||||
.product_data_tabs .composable_options {
|
.product_data_tabs li.composable_options {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show the Composable Options tab when composable type selected */
|
/* Show the Composable Options tab when composable type selected */
|
||||||
body.product-type-composable .product_data_tabs .composable_options {
|
body.product-type-composable .product_data_tabs li.composable_options {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
assets/css/frontend.css
Normal file → Executable file
0
assets/css/frontend.css
Normal file → Executable file
8
assets/js/admin.js
Normal file → Executable file
8
assets/js/admin.js
Normal file → Executable file
@@ -17,12 +17,14 @@
|
|||||||
if (productType === 'composable') {
|
if (productType === 'composable') {
|
||||||
$('.show_if_composable').show();
|
$('.show_if_composable').show();
|
||||||
$('.hide_if_composable').hide();
|
$('.hide_if_composable').hide();
|
||||||
$('#composable_product_data').show();
|
// Show the composable tab, then click it so WooCommerce's
|
||||||
$('.product_data_tabs .composable_options a').show();
|
// native tab system hides all other panels properly
|
||||||
|
$('.product_data_tabs li.composable_options').show();
|
||||||
|
$('ul.product_data_tabs li.composable_options a').trigger('click');
|
||||||
} else {
|
} else {
|
||||||
$('.show_if_composable').hide();
|
$('.show_if_composable').hide();
|
||||||
|
$('.product_data_tabs li.composable_options').hide();
|
||||||
$('#composable_product_data').hide();
|
$('#composable_product_data').hide();
|
||||||
$('.product_data_tabs .composable_options a').hide();
|
|
||||||
}
|
}
|
||||||
}).trigger('change');
|
}).trigger('change');
|
||||||
|
|
||||||
|
|||||||
0
assets/js/frontend.js
Normal file → Executable file
0
assets/js/frontend.js
Normal file → Executable file
@@ -6,7 +6,7 @@
|
|||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Marco Graetsch",
|
"name": "Marco Graetsch",
|
||||||
"email": "marco@example.com"
|
"email": "magdev3.0@gmail.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
|
|||||||
335
composer.lock
generated
Normal file
335
composer.lock
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -95,6 +95,19 @@ class Product_Data {
|
|||||||
<div id="composable_product_data" class="panel woocommerce_options_panel hidden">
|
<div id="composable_product_data" class="panel woocommerce_options_panel hidden">
|
||||||
<div class="options_group">
|
<div class="options_group">
|
||||||
<?php
|
<?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([
|
woocommerce_wp_select([
|
||||||
'id' => '_composable_criteria_type',
|
'id' => '_composable_criteria_type',
|
||||||
'label' => __('Selection Criteria', 'wc-composable-product'),
|
'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']) : '';
|
$pricing_mode = isset($_POST['_composable_pricing_mode']) ? sanitize_text_field($_POST['_composable_pricing_mode']) : '';
|
||||||
update_post_meta($post_id, '_composable_pricing_mode', $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
|
// Save criteria type
|
||||||
$criteria_type = isset($_POST['_composable_criteria_type']) ? sanitize_text_field($_POST['_composable_criteria_type']) : 'category';
|
$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);
|
update_post_meta($post_id, '_composable_criteria_type', $criteria_type);
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ class Settings extends \WC_Settings_Page {
|
|||||||
],
|
],
|
||||||
'desc_tip' => true,
|
'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'),
|
'title' => __('Show Product Images', 'wc-composable-product'),
|
||||||
'desc' => __('Display product images in the selection interface.', 'wc-composable-product'),
|
'desc' => __('Display product images in the selection interface.', 'wc-composable-product'),
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ class Cart_Handler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use static flag to prevent multiple executions
|
// Use static flag to prevent multiple executions within the same request
|
||||||
static $already_calculated = false;
|
static $already_calculated = false;
|
||||||
if ($already_calculated) {
|
if ($already_calculated) {
|
||||||
return;
|
return;
|
||||||
@@ -208,13 +208,10 @@ class Cart_Handler {
|
|||||||
|
|
||||||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
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['data']) && $cart_item['data']->get_type() === 'composable') {
|
||||||
if (isset($cart_item['composable_products']) && !isset($cart_item['composable_price_calculated'])) {
|
if (isset($cart_item['composable_products'])) {
|
||||||
$product = $cart_item['data'];
|
$product = $cart_item['data'];
|
||||||
$price = $product->calculate_composed_price($cart_item['composable_products']);
|
$price = $product->calculate_composed_price($cart_item['composable_products']);
|
||||||
$cart_item['data']->set_price($price);
|
$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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,22 @@ class Product_Type extends \WC_Product {
|
|||||||
return true;
|
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
|
* Get available products based on criteria
|
||||||
*
|
*
|
||||||
@@ -104,21 +120,24 @@ class Product_Type extends \WC_Product {
|
|||||||
*/
|
*/
|
||||||
public function get_available_products() {
|
public function get_available_products() {
|
||||||
$criteria = $this->get_selection_criteria();
|
$criteria = $this->get_selection_criteria();
|
||||||
|
$include_unpublished = $this->should_include_unpublished();
|
||||||
$args = [
|
$args = [
|
||||||
'post_type' => 'product',
|
'post_type' => 'product',
|
||||||
'posts_per_page' => -1,
|
'posts_per_page' => -1,
|
||||||
'post_status' => 'publish',
|
'post_status' => $include_unpublished ? ['publish', 'draft', 'private'] : 'publish',
|
||||||
'orderby' => 'title',
|
'orderby' => 'title',
|
||||||
'order' => 'ASC',
|
'order' => 'ASC',
|
||||||
'tax_query' => [],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Exclude composable products from selection
|
// Exclude composable products using the product_type taxonomy
|
||||||
$args['meta_query'] = [
|
// (WooCommerce stores product types as taxonomy terms, NOT as postmeta)
|
||||||
|
$args['tax_query'] = [
|
||||||
|
'relation' => 'AND',
|
||||||
[
|
[
|
||||||
'key' => '_product_type',
|
'taxonomy' => 'product_type',
|
||||||
'value' => 'composable',
|
'field' => 'slug',
|
||||||
'compare' => '!=',
|
'terms' => ['composable'],
|
||||||
|
'operator' => 'NOT IN',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -148,10 +167,12 @@ class Product_Type extends \WC_Product {
|
|||||||
case 'sku':
|
case 'sku':
|
||||||
if (!empty($criteria['skus'])) {
|
if (!empty($criteria['skus'])) {
|
||||||
$skus = array_map('trim', explode(',', $criteria['skus']));
|
$skus = array_map('trim', explode(',', $criteria['skus']));
|
||||||
$args['meta_query'][] = [
|
$args['meta_query'] = [
|
||||||
|
[
|
||||||
'key' => '_sku',
|
'key' => '_sku',
|
||||||
'value' => $skus,
|
'value' => $skus,
|
||||||
'compare' => 'IN',
|
'compare' => 'IN',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -163,13 +184,28 @@ class Product_Type extends \WC_Product {
|
|||||||
if ($query->have_posts()) {
|
if ($query->have_posts()) {
|
||||||
foreach ($query->posts as $post) {
|
foreach ($query->posts as $post) {
|
||||||
$product = wc_get_product($post->ID);
|
$product = wc_get_product($post->ID);
|
||||||
if ($product && $product->is_in_stock() && $product->is_purchasable()) {
|
|
||||||
|
if (!$product) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle variable products by including their variations
|
||||||
|
if ($product->is_type('variable')) {
|
||||||
|
$variation_ids = $product->get_children();
|
||||||
|
foreach ($variation_ids as $variation_id) {
|
||||||
|
$variation = wc_get_product($variation_id);
|
||||||
|
if ($variation && ($include_unpublished || $variation->is_purchasable())) {
|
||||||
|
$products[] = $variation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($include_unpublished || $product->is_purchasable()) {
|
||||||
$products[] = $product;
|
$products[] = $product;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_reset_postdata();
|
wp_reset_postdata();
|
||||||
|
|
||||||
return $products;
|
return $products;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
24
languages/wc-composable-product-de_CH.po
Normal file → Executable file
24
languages/wc-composable-product-de_CH.po
Normal file → Executable file
@@ -238,3 +238,27 @@ msgstr "übrig"
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr "An Lager"
|
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"
|
||||||
|
|||||||
Binary file not shown.
24
languages/wc-composable-product-de_CH_informal.po
Normal file → Executable file
24
languages/wc-composable-product-de_CH_informal.po
Normal file → Executable file
@@ -238,3 +238,27 @@ msgstr "übrig"
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr "An Lager"
|
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"
|
||||||
|
|||||||
Binary file not shown.
24
languages/wc-composable-product-de_DE.po
Normal file → Executable file
24
languages/wc-composable-product-de_DE.po
Normal file → Executable file
@@ -238,3 +238,27 @@ msgstr "übrig"
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr "Auf Lager"
|
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"
|
||||||
|
|||||||
Binary file not shown.
24
languages/wc-composable-product-de_DE_informal.po
Normal file → Executable file
24
languages/wc-composable-product-de_DE_informal.po
Normal file → Executable file
@@ -238,3 +238,27 @@ msgstr "übrig"
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr "Auf Lager"
|
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"
|
||||||
|
|||||||
Binary file not shown.
24
languages/wc-composable-product-fr_CH.po
Normal file → Executable file
24
languages/wc-composable-product-fr_CH.po
Normal file → Executable file
@@ -238,3 +238,27 @@ msgstr "restant"
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr "En stock"
|
msgstr "En stock"
|
||||||
|
|
||||||
|
#: templates/product-selector.twig
|
||||||
|
msgid "No products available for selection. Please configure the product criteria in the admin panel."
|
||||||
|
msgstr "Aucun produit disponible pour la sélection. Veuillez configurer les critères de produit dans le panneau d'administration."
|
||||||
|
|
||||||
|
#: 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"
|
||||||
|
|||||||
Binary file not shown.
24
languages/wc-composable-product-it_CH.po
Normal file → Executable file
24
languages/wc-composable-product-it_CH.po
Normal file → Executable file
@@ -238,3 +238,27 @@ msgstr "rimasti"
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr "Disponibile"
|
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"
|
||||||
|
|||||||
24
languages/wc-composable-product.pot
Normal file → Executable file
24
languages/wc-composable-product.pot
Normal file → Executable file
@@ -237,3 +237,27 @@ msgstr ""
|
|||||||
#: templates/product-selector.twig
|
#: templates/product-selector.twig
|
||||||
msgid "In stock"
|
msgid "In stock"
|
||||||
msgstr ""
|
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 ""
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
aec3bae001f0013322a73fa941169688 wc-composable-product-v1.0.0.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
4a0f7ec2171aeabfdfe155419fd6124f35f3e14501ee2ca324bbab447259a8bb wc-composable-product-v1.0.0.zip
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
0a60816bbc5a01c0057c1ffa72679d93 releases/wc-composable-product-v1.1.0.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
645fdd68aca95cba77d961f3a48d41b9c12b3d17552572b7c039575dcfcab693 releases/wc-composable-product-v1.1.0.zip
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
db09928aea6fffbf9c2e754d2264f2bc wc-composable-product-v1.1.1.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
761eef69da910ecfdb20ceeed70b5d0381c7cab895e81a040d132cb0f88d749b wc-composable-product-v1.1.1.zip
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
37cef191778b448dcbd2ae10141f64c6 wc-composable-product-v1.1.2.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
191eae035b34ce8b33b90cf9d85ed54e493c1b471cda0efe5c992a512e91cc36 wc-composable-product-v1.1.2.zip
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
9bbed416019a796b4d4a5ef72e016e1f wc-composable-product-v1.1.3.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0ca23ca12570f0e9c518514ffc5209d78c76c3295954d10ec74a28013a762956 wc-composable-product-v1.1.3.zip
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
eae384e342450abd4ac83af0266ac764 wc-composable-product-v1.1.6.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
d64f4f5f1a00d392989cb613780e5726106a08c6aace08e0c74c80553a0b0f1e wc-composable-product-v1.1.6.zip
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
871fbb3b910380c0e43bcf1538408eda releases/wc-composable-product-v1.1.7.zip
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
866e7dd34431f4c881629fd8b59ddd3a27c7a45b7324a3d88cd064a3e01c1b83 releases/wc-composable-product-v1.1.7.zip
|
|
||||||
6
templates/product-selector.twig
Normal file → Executable file
6
templates/product-selector.twig
Normal file → Executable file
@@ -9,6 +9,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="composable-products-grid">
|
<div class="composable-products-grid">
|
||||||
|
{% if products is empty %}
|
||||||
|
<div class="composable-no-products">
|
||||||
|
<p>{{ __('No products available for selection. Please configure the product criteria in the admin panel.') }}</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
<div class="composable-product-item{% if not product.in_stock %} out-of-stock{% endif %}" data-product-id="{{ product.id }}" data-price="{{ product.price }}" data-stock-status="{{ product.stock_status }}">
|
<div class="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">
|
<div class="product-item-inner">
|
||||||
@@ -52,6 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if show_total %}
|
{% if show_total %}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin Name: WooCommerce Composable Products
|
* Plugin Name: WooCommerce Composable Products
|
||||||
* Plugin URI: https://github.com/magdev/wc-composable-product
|
* 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
|
* Description: Create composable products where customers select a limited number of items from a configurable set
|
||||||
* Version: 1.1.8
|
* Version: 1.3.0
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
* Author URI: https://example.com
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
* License: GPL v3 or later
|
* License: GPL v3 or later
|
||||||
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
* Text Domain: wc-composable-product
|
* Text Domain: wc-composable-product
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
// Define plugin constants
|
// Define plugin constants
|
||||||
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.1.8');
|
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.3.0');
|
||||||
define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__);
|
define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__);
|
||||||
define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__));
|
define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__));
|
||||||
define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__));
|
define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user