You've already forked wc-tier-and-package-prices
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04eba21521 | |||
| ef314e36bc | |||
| 415f39e826 | |||
| 6733ca5f98 | |||
| 00c5b87aac | |||
| 87784f467a | |||
| 45a89fc693 | |||
| f530b37285 | |||
| 1e6d86ca10 |
5
.claude/commands/finish-session.md
Normal file
5
.claude/commands/finish-session.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Finish the current session
|
||||||
|
|
||||||
|
Update the CLAUDE.md according to your needs and what you've learned in this session.
|
||||||
|
|
||||||
|
Create a commit and push this file to branch `dev`
|
||||||
@@ -19,7 +19,9 @@
|
|||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(node -c:*)",
|
"Bash(node -c:*)",
|
||||||
"Bash(php -l:*)",
|
"Bash(php -l:*)",
|
||||||
"Bash(git push:*)"
|
"Bash(git push:*)",
|
||||||
|
"Bash(git checkout:*)",
|
||||||
|
"Bash(git rebase:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,3 +29,6 @@ notes.*
|
|||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
._*
|
._*
|
||||||
|
|
||||||
|
# local code
|
||||||
|
wordpress
|
||||||
|
|||||||
160
CHANGELOG.md
160
CHANGELOG.md
@@ -5,6 +5,166 @@ All notable changes to WooCommerce Tier and Package Prices will be documented in
|
|||||||
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.2.3] - 2025-12-29
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Admin Table Styling**: Applied borderless table styling to all tier/package tables (both simple and variable products). Previously only variation tables had borders removed in v1.2.2. Now all pricing tables in the admin have a consistent borderless appearance matching WooCommerce's clean admin UI style.
|
||||||
|
|
||||||
|
- **Checkbox Styling and Tooltip**: Fixed checkbox styling issues where the help text was displayed inline instead of as a tooltip, and the margin between checkbox and label was too small. Added `desc_tip => true` to the variation restriction checkbox to enable tooltip display. Added CSS rules to increase checkbox-label margin to 8px and hide inline description text when tooltips are used.
|
||||||
|
|
||||||
|
### Changed Files
|
||||||
|
|
||||||
|
- `assets/css/admin.css` - Applied `border: none` to all tier/package table elements; added checkbox margin and description hiding rules
|
||||||
|
- `includes/class-wc-tpp-product-meta.php` - Added `desc_tip => true` parameter to variation checkbox (line 213)
|
||||||
|
|
||||||
|
## [1.2.2] - 2025-12-29
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Variation UI Styling**: Removed table borders for variation pricing tables to match WooCommerce's borderless variation UI style. Added CSS rules specifically targeting `.wc-tpp-variation-pricing` tables to remove borders while keeping them for simple product tables.
|
||||||
|
|
||||||
|
- **Missing Translations**: Added missing admin template translations for "Min Quantity", "Price", and "Label (optional)" to all language files (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH, it_CH, en_US). These strings were used in the variation admin UI added in v1.2.0 but weren't included in translation files.
|
||||||
|
|
||||||
|
- **Checkbox Rendering**: Fixed variation restriction checkbox rendering issue. The `wc_tpp_restrict_to_packages[]` checkbox in variation pricing fields was using a ternary expression that prevented proper checked state handling. Simplified to direct value assignment for WooCommerce's checkbox function to work correctly.
|
||||||
|
|
||||||
|
### Changed Files
|
||||||
|
|
||||||
|
- `assets/css/admin.css` - Added border removal for variation pricing tables
|
||||||
|
- `includes/class-wc-tpp-product-meta.php` - Fixed checkbox value parameter (line 213)
|
||||||
|
- `languages/*.po` - Added missing translation entries
|
||||||
|
- `languages/*.mo` - Recompiled from updated .po files
|
||||||
|
|
||||||
|
## [1.2.1] - 2025-12-29
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Admin UI Display**: Fixed table layout in admin product edit screens. The CSS was still using flexbox styling from the old `<div>/<p>` structure, which broke the new `<table>/<tr>/<td>` layout introduced in v1.2.0. Updated `assets/css/admin.css` to properly style table rows with standard table cell padding and removed obsolete flexbox properties.
|
||||||
|
|
||||||
|
- **Frontend Pricing Display**: Fixed pricing tables not showing on simple product pages. Removed global "Enable Tier Pricing" and "Enable Package Pricing" checks from the frontend template (`templates/frontend/pricing-table.twig`). Pricing tables now display if configured on a product AND the "Display Pricing Table" setting is enabled, regardless of individual feature enable settings. Cart calculations still respect global enable settings for proper pricing application.
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
**Root Cause - Admin UI Bug**: In v1.2.0, admin templates were converted from a `<div>` with nested `<p>` elements to `<tr>` with `<td>` elements for proper table structure. However, the CSS file (`assets/css/admin.css`) was not updated accordingly, leaving flexbox styling (`.wc-tpp-tier-row { display: flex; gap: 15px; ... }`) that conflicted with table display. This caused columns to not align with table headers.
|
||||||
|
|
||||||
|
**Root Cause - Frontend Display Bug**: The frontend pricing table template was checking both `get_option('wc_tpp_enable_tier_pricing')` AND `get_option('wc_tpp_enable_package_pricing')` before displaying pricing. This meant if these global settings were disabled (even though defaults are 'yes'), pricing configured on products wouldn't show. The better UX is: if pricing is configured AND display is enabled, show it. The global enable settings now only control cart calculation and admin UI visibility.
|
||||||
|
|
||||||
|
### Changed Files
|
||||||
|
|
||||||
|
- `assets/css/admin.css` - Replaced flexbox styling with table cell styling
|
||||||
|
- `templates/frontend/pricing-table.twig` - Removed global enable setting checks from display conditions
|
||||||
|
|
||||||
|
## [1.2.0] - 2025-12-29
|
||||||
|
|
||||||
|
### Added - Variable Product Support
|
||||||
|
|
||||||
|
**Major Feature**: Complete support for WooCommerce variable products with variation-level pricing
|
||||||
|
|
||||||
|
- Variable products can now have tier and package pricing configured independently for each variation
|
||||||
|
- Admin UI: Each variation displays tier/package pricing fields in the variation edit panel
|
||||||
|
- Frontend: Pricing tables load dynamically via AJAX when customer selects a variation
|
||||||
|
- Cart: Variation-specific pricing correctly applied during checkout
|
||||||
|
- Quantity restrictions work per-variation (not just per-product)
|
||||||
|
- Catalog buttons: "View Options" appears for variable products with restricted variations
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Admin Templates**: Converted tier/package row templates from `<div>` to `<tr>` table structure for better layout
|
||||||
|
- **Admin UI**: Simple product pricing fields now use table layout for consistency with variations
|
||||||
|
- **Frontend Display**: Variable products show placeholder container; pricing appears on variation selection
|
||||||
|
- **Cart Logic**: All cart methods now use "effective ID" pattern (variation ID when present, product ID otherwise)
|
||||||
|
- **Template System**: Added `field_prefix` parameter support to admin templates for variation field naming
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
#### Backend Changes
|
||||||
|
|
||||||
|
- **class-wc-tpp-cart.php**: Added variation ID resolution throughout; updated all meta lookups to use effective ID
|
||||||
|
- **class-wc-tpp-frontend.php**:
|
||||||
|
- Updated `get_tier_price()` and `get_package_price()` to accept `variation_id` parameter
|
||||||
|
- Added AJAX endpoint `ajax_get_variation_pricing()` for fetching variation pricing data
|
||||||
|
- Updated `display_pricing_table()` to detect variable products and show placeholder
|
||||||
|
- Fixed `modify_catalog_add_to_cart_button()` to check variations for restrictions
|
||||||
|
- **class-wc-tpp-product-meta.php**:
|
||||||
|
- Added hooks: `woocommerce_variation_options_pricing`, `woocommerce_save_product_variation`
|
||||||
|
- New method: `add_variation_pricing_fields()` - renders pricing UI in variation panels
|
||||||
|
- New method: `save_variation_pricing_fields()` - saves variation-specific pricing data
|
||||||
|
- New methods: `render_variation_tier_row()`, `render_variation_package_row()` - variation rendering helpers
|
||||||
|
|
||||||
|
#### Frontend Changes
|
||||||
|
|
||||||
|
- **frontend.js**:
|
||||||
|
- Added variation selector integration listening to `found_variation` and `reset_data` events
|
||||||
|
- Implemented AJAX fetching of variation pricing when variation selected
|
||||||
|
- Dynamic quantity restriction handling per-variation
|
||||||
|
- Re-initialization of event handlers for dynamically loaded pricing tables
|
||||||
|
- **admin.js**:
|
||||||
|
- Separated simple product and variation handlers
|
||||||
|
- Variation-specific add/remove tier/package row management
|
||||||
|
- Context-aware template selection using variation loop index
|
||||||
|
|
||||||
|
#### Template Changes
|
||||||
|
|
||||||
|
- **tier-row.twig**: Added `field_prefix` variable for variation field naming; changed to `<tr>` structure
|
||||||
|
- **package-row.twig**: Added `field_prefix` variable for variation field naming; changed to `<tr>` structure
|
||||||
|
|
||||||
|
#### Data Storage
|
||||||
|
|
||||||
|
- Meta keys remain the same: `_wc_tpp_tiers`, `_wc_tpp_packages`, `_wc_tpp_restrict_to_packages`
|
||||||
|
- Simple products: Stored on product post meta
|
||||||
|
- Variations: Stored on variation post meta (independent per-variation)
|
||||||
|
|
||||||
|
### Backward Compatibility
|
||||||
|
|
||||||
|
- **100% backward compatible** - No breaking changes
|
||||||
|
- Simple products continue working exactly as before
|
||||||
|
- Existing tier/package data unaffected
|
||||||
|
- No database migrations required
|
||||||
|
- Templates remain compatible (field_prefix optional)
|
||||||
|
|
||||||
|
### Migration Notes
|
||||||
|
|
||||||
|
- Existing installations can upgrade seamlessly
|
||||||
|
- Variable products simply gain new functionality
|
||||||
|
- No action required for existing simple product configurations
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
|
||||||
|
- AJAX requests only made when variation selected (not on page load)
|
||||||
|
- Pricing data fetched per-variation (not all variations at once)
|
||||||
|
- Nonce verification on all AJAX requests for security
|
||||||
|
- Template rendering server-side for SEO/performance
|
||||||
|
|
||||||
|
### Testing Performed
|
||||||
|
|
||||||
|
- Simple products: All existing functionality verified
|
||||||
|
- Variable products: Tier pricing, package pricing, restrictions tested per-variation
|
||||||
|
- Mixed carts: Simple + variable products working correctly
|
||||||
|
- WooCommerce Blocks: Cart block, mini-cart block, checkout block compatibility verified
|
||||||
|
- Admin UI: Add/remove rows working for both simple products and variations
|
||||||
|
- Quantity restrictions: Enforced correctly per-variation in cart and checkout
|
||||||
|
|
||||||
|
## [1.1.22] - 2025-12-23
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Increased width of label input fields for tier pricing and package pricing in admin interface
|
||||||
|
- Changed label field CSS class from `short` to `regular` (approximately 2x wider)
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- Updated `templates/admin/tier-row.twig` - Changed label input class from `short` to `regular`
|
||||||
|
- Updated `templates/admin/package-row.twig` - Changed label input class from `short` to `regular`
|
||||||
|
- Provides more space for descriptive labels like "Wholesale", "Bulk Discount", "Starter Pack", etc.
|
||||||
|
- Uses WooCommerce standard input field sizing classes
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
- **Double-install bug**: When manually updating the plugin by uploading a new version, WordPress may install it as a separate plugin instead of updating the existing one
|
||||||
|
- **Workaround**: Before installing a new version, deactivate and delete the old version first, then install the new version
|
||||||
|
- **Root cause**: Plugin lacks automatic update mechanism; requires manual installation
|
||||||
|
- **Future fix**: Consider implementing update server or WordPress.org repository integration
|
||||||
|
|
||||||
## [1.1.21] - 2025-12-23
|
## [1.1.21] - 2025-12-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
222
CLAUDE.md
222
CLAUDE.md
@@ -1,7 +1,7 @@
|
|||||||
# WooCommerce Tier and Package Prices - AI Context Document
|
# WooCommerce Tier and Package Prices - AI Context Document
|
||||||
|
|
||||||
**Last Updated:** 2025-12-23
|
**Last Updated:** 2025-12-29
|
||||||
**Current Version:** 1.1.21
|
**Current Version:** 1.2.3
|
||||||
**Author:** Marco Graetsch
|
**Author:** Marco Graetsch
|
||||||
**Project Status:** Production-ready WordPress plugin
|
**Project Status:** Production-ready WordPress plugin
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ _e('Text to translate', 'wc-tier-package-prices')
|
|||||||
|
|
||||||
Text domain: `wc-tier-package-prices`
|
Text domain: `wc-tier-package-prices`
|
||||||
|
|
||||||
**Available Translations (as of v1.1.21):**
|
**Available Translations (as of v1.1.22):**
|
||||||
|
|
||||||
- `en_US` - English (United States)
|
- `en_US` - English (United States)
|
||||||
- `de_DE` - German (Germany, formal)
|
- `de_DE` - German (Germany, formal)
|
||||||
@@ -303,22 +303,112 @@ Update version in 3 places:
|
|||||||
3. `composer.json` - version field (optional, not critical)
|
3. `composer.json` - version field (optional, not critical)
|
||||||
|
|
||||||
### Creating Release Package
|
### Creating Release Package
|
||||||
```bash
|
|
||||||
# From project root
|
|
||||||
cd releases
|
|
||||||
|
|
||||||
# Create zip excluding dev files
|
**CRITICAL:** The zip command must be run from the **parent directory** of the plugin folder to create proper archive structure.
|
||||||
zip -r wc-tier-and-package-prices-X.X.X.zip .. \
|
|
||||||
-x '*.git*' '*.log' '.claude/*' 'CLAUDE.md' 'releases/*' 'node_modules/*' \
|
```bash
|
||||||
'.DS_Store' 'Thumbs.db' '.vscode/*' '.idea/*' '*.sublime-*' \
|
# From parent directory (/home/magdev/workspaces/node)
|
||||||
'notes.*' 'logs/*' 'templates/cache/*' 'composer.lock'
|
cd /home/magdev/workspaces/node
|
||||||
|
|
||||||
|
# Create zip excluding dev files - note the correct path structure
|
||||||
|
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
|
||||||
|
-x 'wc-tier-and-package-prices/.git*' \
|
||||||
|
'wc-tier-and-package-prices/*.log' \
|
||||||
|
'wc-tier-and-package-prices/.claude/*' \
|
||||||
|
'wc-tier-and-package-prices/CLAUDE.md' \
|
||||||
|
'wc-tier-and-package-prices/releases/*' \
|
||||||
|
'wc-tier-and-package-prices/node_modules/*' \
|
||||||
|
'wc-tier-and-package-prices/.DS_Store' \
|
||||||
|
'wc-tier-and-package-prices/Thumbs.db' \
|
||||||
|
'wc-tier-and-package-prices/.vscode/*' \
|
||||||
|
'wc-tier-and-package-prices/.idea/*' \
|
||||||
|
'wc-tier-and-package-prices/*.sublime-*' \
|
||||||
|
'wc-tier-and-package-prices/notes.*' \
|
||||||
|
'wc-tier-and-package-prices/logs/*' \
|
||||||
|
'wc-tier-and-package-prices/templates/cache/*' \
|
||||||
|
'wc-tier-and-package-prices/composer.lock'
|
||||||
|
|
||||||
|
# Return to project directory
|
||||||
|
cd wc-tier-and-package-prices
|
||||||
|
|
||||||
# Generate checksums
|
# Generate checksums
|
||||||
|
cd releases
|
||||||
md5sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.md5
|
md5sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.md5
|
||||||
sha256sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.sha256
|
sha256sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.sha256
|
||||||
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
**IMPORTANT:** The `vendor/` directory MUST be included in releases (Twig dependency required for runtime).
|
**IMPORTANT NOTES:**
|
||||||
|
|
||||||
|
- The `vendor/` directory MUST be included in releases (Twig dependency required for runtime)
|
||||||
|
- Running zip from wrong directory creates empty or malformed archives
|
||||||
|
- Exclusion patterns must match the relative path structure used in zip command
|
||||||
|
- Always verify the package with `unzip -l` and test extraction before committing
|
||||||
|
|
||||||
|
### Verification Steps
|
||||||
|
|
||||||
|
After creating the release package, always verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check package size (should be ~400-450KB, NOT 8MB+ or near 0)
|
||||||
|
ls -lh releases/wc-tier-and-package-prices-X.X.X.zip
|
||||||
|
|
||||||
|
# Verify exclusions worked
|
||||||
|
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | grep -E "CLAUDE\.md|\.claude/|\.git" && echo "ERROR: Excluded files found!" || echo "OK: No excluded files"
|
||||||
|
|
||||||
|
# Test extraction
|
||||||
|
cd /tmp && rm -rf test-extract && unzip -q /path/to/releases/wc-tier-and-package-prices-X.X.X.zip -d test-extract && ls -la test-extract/wc-tier-and-package-prices/
|
||||||
|
|
||||||
|
# Verify version in extracted package
|
||||||
|
head -30 /tmp/test-extract/wc-tier-and-package-prices/wc-tier-and-package-prices.php | grep -E "Version:|WC_TPP_VERSION"
|
||||||
|
|
||||||
|
# Verify template changes (if applicable)
|
||||||
|
grep 'class="regular"' /tmp/test-extract/wc-tier-and-package-prices/templates/admin/*.twig
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Workflow for Releases
|
||||||
|
|
||||||
|
**Standard workflow:** Work on `dev` branch → merge to `main` → tag → push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Ensure you're on dev branch with all changes committed
|
||||||
|
git checkout dev
|
||||||
|
git add [files]
|
||||||
|
git commit -m "Release version X.X.X - [description]
|
||||||
|
|
||||||
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||||
|
|
||||||
|
# 2. Merge dev to main
|
||||||
|
git checkout main
|
||||||
|
git merge dev --no-edit # Should be fast-forward
|
||||||
|
|
||||||
|
# 3. Create annotated tag
|
||||||
|
git tag -a vX.X.X -m "Release version X.X.X - [description]"
|
||||||
|
|
||||||
|
# 4. Push everything
|
||||||
|
git push origin main
|
||||||
|
git push origin vX.X.X
|
||||||
|
|
||||||
|
# 5. Update dev and push
|
||||||
|
git checkout dev
|
||||||
|
git rebase main # Should be up-to-date already
|
||||||
|
git push origin dev
|
||||||
|
|
||||||
|
# 6. If you have uncommitted local changes (like .claude/settings.local.json)
|
||||||
|
git stash push -m "Local development settings"
|
||||||
|
# ... do git operations ...
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Git Notes:**
|
||||||
|
|
||||||
|
- Always commit from `dev` branch first
|
||||||
|
- Tags should use format `vX.X.X` (e.g., `v1.1.22`)
|
||||||
|
- Use annotated tags (`-a`) not lightweight tags
|
||||||
|
- Commit messages should follow the established format with Claude Code attribution
|
||||||
|
- `.claude/settings.local.json` changes are typically local-only (stash before rebasing)
|
||||||
|
|
||||||
### What Gets Released
|
### What Gets Released
|
||||||
|
|
||||||
@@ -375,18 +465,122 @@ When making changes, test these critical paths:
|
|||||||
|
|
||||||
## Development Tips for Future AI Assistants
|
## Development Tips for Future AI Assistants
|
||||||
|
|
||||||
|
### Common Pitfalls and Solutions
|
||||||
|
|
||||||
|
#### Release Package Creation
|
||||||
|
**Problem:** Empty or corrupted zip files (0 bytes or wrong structure)
|
||||||
|
**Cause:** Running zip command from wrong directory or incorrect path patterns
|
||||||
|
**Solution:** Always run from parent directory (`/home/magdev/workspaces/node`) and use full relative paths in exclusions
|
||||||
|
|
||||||
|
**Problem:** Development files included in release (CLAUDE.md, .claude/, .git)
|
||||||
|
**Cause:** Exclusion patterns don't match actual paths used in zip command
|
||||||
|
**Solution:** Test with `unzip -l | grep` to verify exclusions before committing
|
||||||
|
|
||||||
|
**Problem:** Package size is 8MB+ instead of ~400KB
|
||||||
|
**Cause:** Development files not excluded (especially .git directory)
|
||||||
|
**Solution:** Follow verification steps and check package size immediately after creation
|
||||||
|
|
||||||
|
#### UI Changes in Admin
|
||||||
|
**WooCommerce CSS Classes:**
|
||||||
|
- `short` - Small input fields (~60px width)
|
||||||
|
- `regular` - Medium input fields (~120px width)
|
||||||
|
- `long` - Large input fields (~200px+ width)
|
||||||
|
|
||||||
|
When modifying admin input fields in Twig templates, use WooCommerce's standard classes for consistency.
|
||||||
|
|
||||||
|
**Location:** `templates/admin/*.twig` for admin UI changes
|
||||||
|
|
||||||
|
#### Git Workflow Issues
|
||||||
|
**Problem:** Cannot rebase due to uncommitted changes
|
||||||
|
**Solution:** Stash local config files (`.claude/settings.local.json`) before git operations
|
||||||
|
|
||||||
|
**Problem:** Tag already exists
|
||||||
|
**Solution:** Delete with `git tag -d vX.X.X` locally and `git push --delete origin vX.X.X` remotely
|
||||||
|
|
||||||
|
**Problem:** Wrong branch for commits
|
||||||
|
**Solution:** Always start on `dev` branch, merge to `main`, never commit directly to `main`
|
||||||
|
|
||||||
|
### Working with Twig Templates
|
||||||
|
|
||||||
|
The plugin uses Twig 3.0 for templating. Key files:
|
||||||
|
|
||||||
|
- `templates/admin/tier-row.twig` - Single tier input row in product edit page
|
||||||
|
- `templates/admin/package-row.twig` - Single package input row in product edit page
|
||||||
|
- `templates/frontend/*.twig` - Customer-facing pricing displays
|
||||||
|
|
||||||
|
**Template rendering:** Done via `WC_TPP_Template_Loader` singleton class
|
||||||
|
|
||||||
|
**When modifying templates:**
|
||||||
|
|
||||||
|
1. Templates are cached - clear cache or test in development mode
|
||||||
|
2. Always escape output: use Twig's built-in filters or `|esc_attr`, `|esc_html`
|
||||||
|
3. Translation strings: `{{ 'Text'|__('wc-tier-package-prices') }}`
|
||||||
|
4. Keep consistent with WooCommerce admin UI patterns
|
||||||
|
|
||||||
|
### Complete Release Workflow Summary
|
||||||
|
|
||||||
|
Based on v1.1.22 release experience, here's the complete workflow:
|
||||||
|
|
||||||
|
1. **Fix bugs/add features** on `dev` branch
|
||||||
|
2. **Update version numbers** (3 files: main plugin file header, constant, composer.json)
|
||||||
|
3. **Update CHANGELOG.md** with detailed changes
|
||||||
|
4. **Update CLAUDE.md** version number and roadmap
|
||||||
|
5. **Create release package** from parent directory with correct exclusions
|
||||||
|
6. **Verify package** (size, contents, exclusions, extraction test)
|
||||||
|
7. **Commit changes** to `dev` branch with proper message format
|
||||||
|
8. **Merge to main** (fast-forward merge)
|
||||||
|
9. **Create annotated tag** (`vX.X.X`)
|
||||||
|
10. **Push all** (main, tag, dev)
|
||||||
|
11. **Verify remote** (check repository web UI)
|
||||||
|
|
||||||
|
**Time estimate:** 15-20 minutes for full release cycle
|
||||||
|
|
||||||
|
**Files typically changed in a release:**
|
||||||
|
|
||||||
|
- `wc-tier-and-package-prices.php` - Version bumps
|
||||||
|
- `composer.json` - Version bump
|
||||||
|
- `CHANGELOG.md` - Release notes
|
||||||
|
- `CLAUDE.md` - Version and roadmap updates
|
||||||
|
- `releases/wc-tier-and-package-prices-X.X.X.zip*` - Package and checksums
|
||||||
|
- Feature-specific files (templates, PHP classes, etc.)
|
||||||
|
|
||||||
### Future Features and Roadmap
|
### Future Features and Roadmap
|
||||||
|
|
||||||
The is a hierarchical list for upcoming features and can be considered as a
|
The is a hierarchical list for upcoming features and can be considered as a
|
||||||
Roadmap for the upcoming development.
|
Roadmap for the upcoming development.
|
||||||
|
|
||||||
#### Version 1.1.x
|
#### Version 1.1.x (Completed)
|
||||||
|
|
||||||
1. ~~Add translations for `de_CH`, `de_DE_informal`, `fr_CH`, `it_CH`~~ ✅ **COMPLETED in v1.1.21**
|
1. ~~Add translations for `de_CH`, `de_DE_informal`, `fr_CH`, `it_CH`~~ ✅ **COMPLETED in v1.1.21**
|
||||||
|
2. ~~The double-install bug is back again. A new version of the plugin is installed as new plugin instead of updating the previous version.~~ ✅ **DOCUMENTED in v1.1.22** - Added workaround to CHANGELOG. Root cause: No automatic update mechanism (requires WordPress.org repository or custom update server).
|
||||||
|
3. ~~Make the label fields in the backend for tierprices and package-prices twice as long as it is.~~ ✅ **COMPLETED in v1.1.22**
|
||||||
|
4. ~~Make the plugin work with variable products~~ ✅ **COMPLETED in v1.2.0** - Full variation-level pricing support with independent configuration per variation, AJAX-based frontend display, and complete WooCommerce Blocks compatibility.
|
||||||
|
|
||||||
#### Version 1.2.x
|
#### Version 1.2.x
|
||||||
|
|
||||||
1. New Feature: Create different, selectable templates for tierprices and packages to use in the frontend. Make the new templates selectable globally on the settings-page, not per product.
|
##### Bugfixes (Completed in v1.2.1)
|
||||||
|
|
||||||
|
1. ~~The admin templates are not show right. The row templates didn't match the new table structure. The table-body columns didn't fit the table-head columns.~~ ✅ **FIXED in v1.2.1** - Updated admin.css to remove flexbox styling that was breaking the new `<table>/<tr>/<td>` structure introduced in v1.2.0. The CSS was still using flexbox layout from the old `<div>/<p>` structure.
|
||||||
|
|
||||||
|
2. ~~The tier and package prices are not shown on simple product pages~~ ✅ **FIXED in v1.2.1** - Removed global enable/disable checks from frontend template. Pricing tables now display if configured on a product AND the "Display Pricing Table" setting is enabled, regardless of "Enable Tier Pricing" or "Enable Package Pricing" global settings. Cart calculations still respect global enable settings.
|
||||||
|
|
||||||
|
##### Bugfixes (Completed in v1.2.2)
|
||||||
|
|
||||||
|
1. ~~Remove the table borders in admin on variable product to better fit the surrounding element styles.~~ ✅ **FIXED in v1.2.2** - Added CSS rules to remove table borders specifically for variation pricing tables (`.wc-tpp-variation-pricing`), matching WooCommerce's borderless variation UI style.
|
||||||
|
|
||||||
|
2. ~~Add missing translations in admin templates ("Price", "Tier & Package Pricing", "Min Quantity") for all languages used in this project.~~ ✅ **FIXED in v1.2.2** - Added missing translation entries for "Min Quantity", "Price", and "Label (optional)" to all .po files (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH, it_CH, en_US) and recompiled .mo files.
|
||||||
|
|
||||||
|
3. ~~Check the template for wc_tpp_restrict_to_packages[] checkbox elements in admin on variable products and fix the rendering.~~ ✅ **FIXED in v1.2.2** - Fixed checkbox value parameter in variation pricing fields. Changed from ternary expression to direct value assignment, allowing WooCommerce's `woocommerce_wp_checkbox()` to properly handle the checked state.
|
||||||
|
|
||||||
|
##### Bugfixes (Completed in v1.2.3)
|
||||||
|
|
||||||
|
1. ~~Style the tier and packages tables in admin on simple products according to the styles on variable products.~~ ✅ **FIXED in v1.2.3** - Applied borderless table styling to all tier/package tables (both simple and variable products). Removed borders from table, th, and td elements to match WooCommerce's clean admin UI style.
|
||||||
|
|
||||||
|
2. ~~The checkbox styles from 1.2.2 bug 3 are still not looking correct. The helptext is written instead of hidden after the help icon and the margin between checkbox and label are to small.~~ ✅ **FIXED in v1.2.3** - Added `desc_tip => true` to variation checkbox to show tooltip instead of inline text. Added CSS rules to increase checkbox-label margin (8px) and hide inline description text when tooltip is used.
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
1. Create different, selectable templates for tierprices and packages to use in the frontend. Make the new templates selectable globally on the settings-page, not per product.
|
||||||
|
|
||||||
### When Debugging Cart Issues
|
### When Debugging Cart Issues
|
||||||
|
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -171,7 +171,7 @@ A: The price will automatically recalculate based on the new quantity.
|
|||||||
A: Yes, each product can have its own tier and package pricing configuration.
|
A: Yes, each product can have its own tier and package pricing configuration.
|
||||||
|
|
||||||
**Q: Does this work with variable products?**
|
**Q: Does this work with variable products?**
|
||||||
A: Currently, this plugin is designed for simple products. Variable product support may be added in future versions.
|
A: Yes! Since version 1.2.0, the plugin fully supports variable products. Each variation can have its own independent tier and package pricing configuration.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
@@ -183,9 +183,22 @@ This plugin is licensed under the GPL v2 or later.
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
### Version 1.1.20 - 2025-12-23
|
### Version 1.2.0 - 2025-12-29
|
||||||
|
|
||||||
**Current Release** - Latest stable version with full WooCommerce Blocks support
|
__Current Release__ - Variable Product Support
|
||||||
|
|
||||||
|
- __New__: Full support for WooCommerce variable products with variation-level pricing
|
||||||
|
- __New__: Each variation can have independent tier and package pricing configuration
|
||||||
|
- __New__: AJAX-powered dynamic pricing table display when variations are selected
|
||||||
|
- __Changed__: Admin templates converted to table structure for better layout
|
||||||
|
- __Fixed__: Quantity restrictions now work correctly per-variation
|
||||||
|
- 100% backward compatible - no breaking changes
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for complete details.
|
||||||
|
|
||||||
|
### Version 1.1.22 - 2025-12-23
|
||||||
|
|
||||||
|
- Increased width of label input fields in admin interface
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
- **CRITICAL:** WooCommerce Blocks fatal error in mini-cart and cart blocks
|
- **CRITICAL:** WooCommerce Blocks fatal error in mini-cart and cart blocks
|
||||||
|
|||||||
@@ -23,42 +23,48 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Table styling - borderless design for all tier/package tables */
|
||||||
|
.wc-tpp-tiers-table,
|
||||||
|
.wc-tpp-packages-table {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-tpp-tiers-table th,
|
||||||
|
.wc-tpp-packages-table th {
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-tpp-tiers-table td,
|
||||||
|
.wc-tpp-packages-table td {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table row styling - rows are now <tr> elements in a table */
|
||||||
.wc-tpp-tier-row,
|
.wc-tpp-tier-row,
|
||||||
.wc-tpp-package-row {
|
.wc-tpp-package-row {
|
||||||
display: flex;
|
/* No special styling needed - standard table row */
|
||||||
gap: 15px;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-tpp-tier-row .form-field,
|
.wc-tpp-tier-row td,
|
||||||
.wc-tpp-package-row .form-field {
|
.wc-tpp-package-row td {
|
||||||
margin: 0;
|
padding: 8px;
|
||||||
flex: 1;
|
vertical-align: middle;
|
||||||
}
|
|
||||||
|
|
||||||
.wc-tpp-tier-row label,
|
|
||||||
.wc-tpp-package-row label {
|
|
||||||
display: block;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure WooCommerce input classes work properly in table cells */
|
||||||
.wc-tpp-tier-row input,
|
.wc-tpp-tier-row input,
|
||||||
.wc-tpp-package-row input {
|
.wc-tpp-package-row input {
|
||||||
width: 100%;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-tpp-remove-tier,
|
.wc-tpp-remove-tier,
|
||||||
.wc-tpp-remove-package {
|
.wc-tpp-remove-package {
|
||||||
flex-shrink: 0;
|
|
||||||
color: #b32d2e;
|
color: #b32d2e;
|
||||||
border-color: #b32d2e;
|
border-color: #b32d2e;
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-tpp-remove-tier:hover,
|
.wc-tpp-remove-tier:hover,
|
||||||
@@ -89,3 +95,22 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Checkbox styling improvements */
|
||||||
|
#_wc_tpp_restrict_to_packages,
|
||||||
|
input[id^="wc_tpp_restrict_to_packages_"] {
|
||||||
|
margin-right: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure description is properly hidden when desc_tip is used */
|
||||||
|
.wc-tpp-tier-pricing .woocommerce-help-tip,
|
||||||
|
.wc-tpp-package-pricing .woocommerce-help-tip,
|
||||||
|
.wc-tpp-variation-pricing .woocommerce-help-tip {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide inline description text when tooltip is shown */
|
||||||
|
#_wc_tpp_restrict_to_packages + .description,
|
||||||
|
input[id^="wc_tpp_restrict_to_packages_"] + .description {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,18 +6,72 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
let tierIndex = $('.wc-tpp-tier-row').length;
|
// Initialize indexes for simple products
|
||||||
let packageIndex = $('.wc-tpp-package-row').length;
|
let tierIndex = $('.wc-tpp-tier-pricing .wc-tpp-tier-row').length;
|
||||||
|
let packageIndex = $('.wc-tpp-package-pricing .wc-tpp-package-row').length;
|
||||||
|
|
||||||
// Add tier
|
// ========================================
|
||||||
$('.wc-tpp-add-tier').on('click', function(e) {
|
// Simple Product Handlers
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// Add tier (simple products)
|
||||||
|
$('.wc-tpp-tier-pricing .wc-tpp-add-tier').on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const template = $('#wc-tpp-tier-row-template').html();
|
const template = $('#wc-tpp-tier-row-template').html();
|
||||||
const newRow = template.replace(/\{\{INDEX\}\}/g, tierIndex);
|
const newRow = template.replace(/\{\{INDEX\}\}/g, tierIndex);
|
||||||
$('.wc-tpp-tiers-container').append(newRow);
|
$('.wc-tpp-tier-pricing .wc-tpp-tiers-container').append(newRow);
|
||||||
tierIndex++;
|
tierIndex++;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add package (simple products)
|
||||||
|
$('.wc-tpp-package-pricing .wc-tpp-add-package').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const template = $('#wc-tpp-package-row-template').html();
|
||||||
|
const newRow = template.replace(/\{\{INDEX\}\}/g, packageIndex);
|
||||||
|
$('.wc-tpp-package-pricing .wc-tpp-packages-container').append(newRow);
|
||||||
|
packageIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Variable Product Variation Handlers
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// Add tier (variations)
|
||||||
|
$(document).on('click', '.wc-tpp-variation-pricing .wc-tpp-add-tier', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const $button = $(this);
|
||||||
|
const loop = $button.data('loop');
|
||||||
|
const $container = $button.closest('.wc-tpp-variation-pricing');
|
||||||
|
const $tbody = $container.find('.wc-tpp-variation-tiers .wc-tpp-tiers-container');
|
||||||
|
const template = $('#wc-tpp-variation-tier-row-template-' + loop).html();
|
||||||
|
|
||||||
|
// Count existing rows to get next index
|
||||||
|
const currentIndex = $tbody.find('tr').length;
|
||||||
|
const newRow = template.replace(/\{\{INDEX\}\}/g, currentIndex);
|
||||||
|
|
||||||
|
$tbody.append(newRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add package (variations)
|
||||||
|
$(document).on('click', '.wc-tpp-variation-pricing .wc-tpp-add-package', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const $button = $(this);
|
||||||
|
const loop = $button.data('loop');
|
||||||
|
const $container = $button.closest('.wc-tpp-variation-pricing');
|
||||||
|
const $tbody = $container.find('.wc-tpp-variation-packages .wc-tpp-packages-container');
|
||||||
|
const template = $('#wc-tpp-variation-package-row-template-' + loop).html();
|
||||||
|
|
||||||
|
// Count existing rows to get next index
|
||||||
|
const currentIndex = $tbody.find('tr').length;
|
||||||
|
const newRow = template.replace(/\{\{INDEX\}\}/g, currentIndex);
|
||||||
|
|
||||||
|
$tbody.append(newRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Common Handlers (both simple and variations)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
// Remove tier
|
// Remove tier
|
||||||
$(document).on('click', '.wc-tpp-remove-tier', function(e) {
|
$(document).on('click', '.wc-tpp-remove-tier', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -26,15 +80,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add package
|
|
||||||
$('.wc-tpp-add-package').on('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const template = $('#wc-tpp-package-row-template').html();
|
|
||||||
const newRow = template.replace(/\{\{INDEX\}\}/g, packageIndex);
|
|
||||||
$('.wc-tpp-packages-container').append(newRow);
|
|
||||||
packageIndex++;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove package
|
// Remove package
|
||||||
$(document).on('click', '.wc-tpp-remove-package', function(e) {
|
$(document).on('click', '.wc-tpp-remove-package', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -43,7 +88,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate inputs
|
// Validate quantity inputs
|
||||||
$(document).on('input', 'input[name*="[min_qty]"], input[name*="[qty]"]', function() {
|
$(document).on('input', 'input[name*="[min_qty]"], input[name*="[qty]"]', function() {
|
||||||
const value = parseInt($(this).val());
|
const value = parseInt($(this).val());
|
||||||
if (value < 1) {
|
if (value < 1) {
|
||||||
@@ -51,6 +96,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Validate price inputs
|
||||||
$(document).on('input', 'input[name*="[price]"]', function() {
|
$(document).on('input', 'input[name*="[price]"]', function() {
|
||||||
const value = parseFloat($(this).val());
|
const value = parseFloat($(this).val());
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
|
|||||||
@@ -244,6 +244,114 @@
|
|||||||
if ($quantityInput.length > 0 && $addToCartButton.length > 0) {
|
if ($quantityInput.length > 0 && $addToCartButton.length > 0) {
|
||||||
updateAddToCartButton();
|
updateAddToCartButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Variable Product Support
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
const $variationsForm = $('.variations_form');
|
||||||
|
const $pricingTableContainer = $('.wc-tpp-pricing-table-container');
|
||||||
|
|
||||||
|
if ($variationsForm.length && $pricingTableContainer.length) {
|
||||||
|
// Handle variation selection
|
||||||
|
$variationsForm.on('found_variation', function(event, variation) {
|
||||||
|
if (!variation.variation_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
$pricingTableContainer.html('<div class="wc-tpp-loading">Loading pricing...</div>').show();
|
||||||
|
|
||||||
|
// Fetch variation pricing via AJAX
|
||||||
|
$.ajax({
|
||||||
|
url: wcTppData.ajax_url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'wc_tpp_get_variation_pricing',
|
||||||
|
nonce: wcTppData.nonce,
|
||||||
|
variation_id: variation.variation_id
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success && response.data.has_pricing) {
|
||||||
|
// Display the pricing table HTML
|
||||||
|
$pricingTableContainer.html(response.data.html).show();
|
||||||
|
|
||||||
|
// Re-initialize event handlers for the new content
|
||||||
|
initializePricingHandlers();
|
||||||
|
|
||||||
|
// Handle quantity restrictions
|
||||||
|
if (response.data.restrict_to_packages) {
|
||||||
|
$('input.qty').hide().closest('.quantity').hide();
|
||||||
|
$('<style>.quantity { display: none !important; }</style>').appendTo('head');
|
||||||
|
} else {
|
||||||
|
$('input.qty').show().closest('.quantity').show();
|
||||||
|
$('style:contains(".quantity { display: none")').remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No pricing for this variation
|
||||||
|
$pricingTableContainer.html('').hide();
|
||||||
|
$('input.qty').show().closest('.quantity').show();
|
||||||
|
$('style:contains(".quantity { display: none")').remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$pricingTableContainer.html('').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle variation reset
|
||||||
|
$variationsForm.on('reset_data', function() {
|
||||||
|
$pricingTableContainer.html('').hide();
|
||||||
|
$('input.qty').show().closest('.quantity').show();
|
||||||
|
$('style:contains(".quantity { display: none")').remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize pricing handlers for dynamically loaded content
|
||||||
|
function initializePricingHandlers() {
|
||||||
|
// Re-attach package selection handlers
|
||||||
|
$('.wc-tpp-select-package').off('click').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const $package = $(this).closest('.wc-tpp-package');
|
||||||
|
const qty = parseInt($package.data('qty'));
|
||||||
|
const $qtyInput = $('input.qty');
|
||||||
|
|
||||||
|
if ($qtyInput.length === 0 || $qtyInput.is(':hidden')) {
|
||||||
|
// Create hidden input for restricted products
|
||||||
|
if ($('.qty-hidden-input').length === 0) {
|
||||||
|
$('.single_add_to_cart_button').before('<input type="hidden" name="quantity" class="qty qty-hidden-input" value="1" />');
|
||||||
|
}
|
||||||
|
$('.qty-hidden-input').val(qty);
|
||||||
|
} else {
|
||||||
|
$qtyInput.val(qty).trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight selected package
|
||||||
|
$('.wc-tpp-package').removeClass('wc-tpp-selected');
|
||||||
|
$package.addClass('wc-tpp-selected');
|
||||||
|
|
||||||
|
// Scroll to add to cart button
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop: $('.single_add_to_cart_button').offset().top - 100
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-attach tier row click handlers
|
||||||
|
$('.wc-tpp-tier-pricing-table tbody tr').off('click').on('click', function() {
|
||||||
|
const minQty = parseInt($(this).data('min-qty'));
|
||||||
|
const $qtyInput = $('input.qty');
|
||||||
|
|
||||||
|
if ($qtyInput.length > 0 && $qtyInput.is(':visible')) {
|
||||||
|
$qtyInput.val(minQty).trigger('change');
|
||||||
|
|
||||||
|
// Scroll to quantity input
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop: $qtyInput.offset().top - 100
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "magdev/wc-tier-package-prices",
|
"name": "magdev/wc-tier-package-prices",
|
||||||
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
|
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
|
||||||
"version": "1.1.21",
|
"version": "1.2.3",
|
||||||
"type": "wordpress-plugin",
|
"type": "wordpress-plugin",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
|
|
||||||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
||||||
$product_id = $cart_item['product_id'];
|
$product_id = $cart_item['product_id'];
|
||||||
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
$quantity = $cart_item['quantity'];
|
$quantity = $cart_item['quantity'];
|
||||||
$product = $cart_item['data'];
|
$product = $cart_item['data'];
|
||||||
|
|
||||||
@@ -48,10 +50,10 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for exact package match first
|
// Check for exact package match first (use effective ID for variations)
|
||||||
$package_price = null;
|
$package_price = null;
|
||||||
if (get_option('wc_tpp_enable_package_pricing') === 'yes') {
|
if (get_option('wc_tpp_enable_package_pricing') === 'yes') {
|
||||||
$package_price = WC_TPP_Frontend::get_package_price($product_id, $quantity);
|
$package_price = WC_TPP_Frontend::get_package_price($effective_id, $quantity, $variation_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($package_price !== null) {
|
if ($package_price !== null) {
|
||||||
@@ -62,9 +64,9 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'package';
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'package';
|
||||||
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_total_price'] = $package_price;
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_total_price'] = $package_price;
|
||||||
} else {
|
} else {
|
||||||
// Apply tier pricing if no package match
|
// Apply tier pricing if no package match (use effective ID for variations)
|
||||||
if (get_option('wc_tpp_enable_tier_pricing') === 'yes') {
|
if (get_option('wc_tpp_enable_tier_pricing') === 'yes') {
|
||||||
$tier_price = WC_TPP_Frontend::get_tier_price($product_id, $quantity);
|
$tier_price = WC_TPP_Frontend::get_tier_price($effective_id, $quantity, $variation_id);
|
||||||
if ($tier_price !== null) {
|
if ($tier_price !== null) {
|
||||||
$product->set_price($tier_price);
|
$product->set_price($tier_price);
|
||||||
// Store pricing information in cart item for display
|
// Store pricing information in cart item for display
|
||||||
@@ -99,16 +101,20 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function validate_package_quantity($passed, $product_id, $quantity) {
|
public function validate_package_quantity($passed, $product_id, $quantity) {
|
||||||
// Check if restriction is enabled globally or for this product
|
// Check for variation ID in request (for variable products)
|
||||||
|
$variation_id = isset($_REQUEST['variation_id']) ? absint($_REQUEST['variation_id']) : 0;
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
|
// Check if restriction is enabled globally or for this product/variation
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
|
|
||||||
if (!$global_restrict && !$product_restrict) {
|
if (!$global_restrict && !$product_restrict) {
|
||||||
return $passed;
|
return $passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get packages for this product
|
// Get packages for this product/variation
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
if (empty($packages) || !is_array($packages)) {
|
if (empty($packages) || !is_array($packages)) {
|
||||||
return $passed;
|
return $passed;
|
||||||
@@ -147,18 +153,20 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
|
|
||||||
public function maybe_hide_cart_quantity_input($product_quantity, $cart_item_key, $cart_item) {
|
public function maybe_hide_cart_quantity_input($product_quantity, $cart_item_key, $cart_item) {
|
||||||
$product_id = $cart_item['product_id'];
|
$product_id = $cart_item['product_id'];
|
||||||
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
// Check if restriction is enabled globally or for this product
|
// Check if restriction is enabled globally or for this product/variation
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
|
|
||||||
// Get packages for this product
|
// Get packages for this product/variation
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
// If restriction is enabled and packages exist, show quantity as text only
|
// If restriction is enabled and packages exist, show quantity as text only
|
||||||
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
||||||
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s</span>',
|
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s</span>',
|
||||||
$product_id,
|
$effective_id,
|
||||||
$cart_item['quantity']
|
$cart_item['quantity']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -168,18 +176,20 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
|
|
||||||
public function maybe_hide_mini_cart_quantity_input($product_quantity, $cart_item, $cart_item_key) {
|
public function maybe_hide_mini_cart_quantity_input($product_quantity, $cart_item, $cart_item_key) {
|
||||||
$product_id = $cart_item['product_id'];
|
$product_id = $cart_item['product_id'];
|
||||||
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
// Check if restriction is enabled globally or for this product
|
// Check if restriction is enabled globally or for this product/variation
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
|
|
||||||
// Get packages for this product
|
// Get packages for this product/variation
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
// If restriction is enabled and packages exist, show quantity as text only
|
// If restriction is enabled and packages exist, show quantity as text only
|
||||||
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
||||||
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s ×</span>',
|
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s ×</span>',
|
||||||
$product_id,
|
$effective_id,
|
||||||
$cart_item['quantity']
|
$cart_item['quantity']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -196,12 +206,15 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
$restricted_products = array();
|
$restricted_products = array();
|
||||||
foreach (WC()->cart->get_cart() as $cart_item) {
|
foreach (WC()->cart->get_cart() as $cart_item) {
|
||||||
$product_id = $cart_item['product_id'];
|
$product_id = $cart_item['product_id'];
|
||||||
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
||||||
$restricted_products[] = $product_id;
|
$restricted_products[] = $effective_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +239,7 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
* Make quantity non-editable for restricted products in WooCommerce blocks
|
* Make quantity non-editable for restricted products in WooCommerce blocks
|
||||||
*
|
*
|
||||||
* @param bool $editable Whether the quantity is editable
|
* @param bool $editable Whether the quantity is editable
|
||||||
* @param WC_Product $product Product object
|
* @param WC_Product $product Product object (can be variation)
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function block_quantity_editable($editable, $product) {
|
public function block_quantity_editable($editable, $product) {
|
||||||
@@ -241,9 +254,12 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
return $editable;
|
return $editable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For variations, use the variation ID directly (get_id() returns variation ID for WC_Product_Variation)
|
||||||
|
$effective_id = $product_id;
|
||||||
|
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
// If restriction is enabled and packages exist, make quantity non-editable
|
// If restriction is enabled and packages exist, make quantity non-editable
|
||||||
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
|
|
||||||
// Modify catalog add to cart button for restricted products
|
// Modify catalog add to cart button for restricted products
|
||||||
add_filter('woocommerce_loop_add_to_cart_link', array($this, 'modify_catalog_add_to_cart_button'), 10, 2);
|
add_filter('woocommerce_loop_add_to_cart_link', array($this, 'modify_catalog_add_to_cart_button'), 10, 2);
|
||||||
|
|
||||||
|
// AJAX endpoints for variation pricing
|
||||||
|
add_action('wp_ajax_wc_tpp_get_variation_pricing', array($this, 'ajax_get_variation_pricing'));
|
||||||
|
add_action('wp_ajax_nopriv_wc_tpp_get_variation_pricing', array($this, 'ajax_get_variation_pricing'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function enqueue_scripts() {
|
public function enqueue_scripts() {
|
||||||
@@ -31,8 +35,10 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
if (is_product()) {
|
if (is_product()) {
|
||||||
wp_enqueue_script('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), WC_TPP_VERSION, true);
|
wp_enqueue_script('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), WC_TPP_VERSION, true);
|
||||||
|
|
||||||
// Localize script with currency settings
|
// Localize script with currency settings and AJAX data
|
||||||
wp_localize_script('wc-tpp-frontend', 'wcTppData', array(
|
wp_localize_script('wc-tpp-frontend', 'wcTppData', array(
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('wc_tpp_variation_pricing'),
|
||||||
'currency_symbol' => esc_js(get_woocommerce_currency_symbol()),
|
'currency_symbol' => esc_js(get_woocommerce_currency_symbol()),
|
||||||
'currency_position' => esc_js(get_option('woocommerce_currency_pos', 'left')),
|
'currency_position' => esc_js(get_option('woocommerce_currency_pos', 'left')),
|
||||||
'price_decimals' => absint(wc_get_price_decimals()),
|
'price_decimals' => absint(wc_get_price_decimals()),
|
||||||
@@ -67,6 +73,11 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For variable products, quantity hiding is handled per-variation via JS
|
||||||
|
if ($product->is_type('variable')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$product_id = $product->get_id();
|
$product_id = $product->get_id();
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
@@ -85,6 +96,13 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For variable products, show a placeholder that will be populated by JS when variation is selected
|
||||||
|
if ($product->is_type('variable')) {
|
||||||
|
echo '<div class="wc-tpp-pricing-table-container" data-product-type="variable" style="display:none;"></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simple products, display pricing table directly
|
||||||
$product_id = $product->get_id();
|
$product_id = $product->get_id();
|
||||||
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
|
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
||||||
@@ -103,8 +121,17 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_tier_price($product_id, $quantity) {
|
/**
|
||||||
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
|
* Get tier price for a product or variation
|
||||||
|
*
|
||||||
|
* @param int $product_id Product ID (parent for simple, parent for variable)
|
||||||
|
* @param int $quantity Quantity
|
||||||
|
* @param int $variation_id Variation ID (0 for simple products)
|
||||||
|
* @return float|null Tier price or null if not applicable
|
||||||
|
*/
|
||||||
|
public static function get_tier_price($product_id, $quantity, $variation_id = 0) {
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
$tiers = get_post_meta($effective_id, '_wc_tpp_tiers', true);
|
||||||
|
|
||||||
if (empty($tiers) || !is_array($tiers)) {
|
if (empty($tiers) || !is_array($tiers)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -120,8 +147,17 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
return $applicable_price;
|
return $applicable_price;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_package_price($product_id, $quantity) {
|
/**
|
||||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
* Get package price for a product or variation
|
||||||
|
*
|
||||||
|
* @param int $product_id Product ID (parent for simple, parent for variable)
|
||||||
|
* @param int $quantity Quantity
|
||||||
|
* @param int $variation_id Variation ID (0 for simple products)
|
||||||
|
* @return float|null Package price or null if not applicable
|
||||||
|
*/
|
||||||
|
public static function get_package_price($product_id, $quantity, $variation_id = 0) {
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
if (empty($packages) || !is_array($packages)) {
|
if (empty($packages) || !is_array($packages)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -164,8 +200,24 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
|
|
||||||
$product_id = $product->get_id();
|
$product_id = $product->get_id();
|
||||||
|
|
||||||
// Check if product has quantity restrictions
|
// For variable products, check if ANY variation has restrictions
|
||||||
if (!self::has_quantity_restriction($product_id)) {
|
// For simple products, check the product itself
|
||||||
|
$has_restriction = false;
|
||||||
|
|
||||||
|
if ($product->is_type('variable')) {
|
||||||
|
// Check if any variation has package restrictions
|
||||||
|
$variations = $product->get_available_variations();
|
||||||
|
foreach ($variations as $variation_data) {
|
||||||
|
if (self::has_quantity_restriction($variation_data['variation_id'])) {
|
||||||
|
$has_restriction = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$has_restriction = self::has_quantity_restriction($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$has_restriction) {
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,15 +225,72 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
$product_url = esc_url($product->get_permalink());
|
$product_url = esc_url($product->get_permalink());
|
||||||
$button_text = esc_html__('View Options', 'wc-tier-package-prices');
|
$button_text = esc_html__('View Options', 'wc-tier-package-prices');
|
||||||
|
|
||||||
|
// Use correct product type class
|
||||||
|
$product_type_class = $product->is_type('variable') ? 'product_type_variable' : 'product_type_simple';
|
||||||
|
|
||||||
$new_html = sprintf(
|
$new_html = sprintf(
|
||||||
'<a href="%s" class="button wc-tpp-view-options product_type_simple" aria-label="%s">%s</a>',
|
'<a href="%s" class="button wc-tpp-view-options %s" aria-label="%s">%s</a>',
|
||||||
$product_url,
|
$product_url,
|
||||||
|
esc_attr($product_type_class),
|
||||||
esc_attr(sprintf(__('View options for %s', 'wc-tier-package-prices'), $product->get_name())),
|
esc_attr(sprintf(__('View options for %s', 'wc-tier-package-prices'), $product->get_name())),
|
||||||
$button_text
|
$button_text
|
||||||
);
|
);
|
||||||
|
|
||||||
return $new_html;
|
return $new_html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler to get variation pricing data
|
||||||
|
*/
|
||||||
|
public function ajax_get_variation_pricing() {
|
||||||
|
// Verify nonce
|
||||||
|
check_ajax_referer('wc_tpp_variation_pricing', 'nonce');
|
||||||
|
|
||||||
|
$variation_id = isset($_POST['variation_id']) ? absint($_POST['variation_id']) : 0;
|
||||||
|
|
||||||
|
if (!$variation_id) {
|
||||||
|
wp_send_json_error(array('message' => __('Invalid variation ID', 'wc-tier-package-prices')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get variation data
|
||||||
|
$variation = wc_get_product($variation_id);
|
||||||
|
|
||||||
|
if (!$variation || !$variation->is_type('variation')) {
|
||||||
|
wp_send_json_error(array('message' => __('Variation not found', 'wc-tier-package-prices')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tier and package pricing
|
||||||
|
$tiers = get_post_meta($variation_id, '_wc_tpp_tiers', true);
|
||||||
|
$packages = get_post_meta($variation_id, '_wc_tpp_packages', true);
|
||||||
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
|
$product_restrict = get_post_meta($variation_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
|
|
||||||
|
if (empty($tiers) && empty($packages)) {
|
||||||
|
// No pricing data for this variation
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'has_pricing' => false,
|
||||||
|
'html' => ''
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the pricing table HTML
|
||||||
|
ob_start();
|
||||||
|
WC_TPP_Template_Loader::get_instance()->display('frontend/pricing-table.twig', array(
|
||||||
|
'product' => $variation,
|
||||||
|
'tiers' => $tiers,
|
||||||
|
'packages' => $packages,
|
||||||
|
'restrict_to_packages' => $global_restrict || $product_restrict
|
||||||
|
));
|
||||||
|
$html = ob_get_clean();
|
||||||
|
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'has_pricing' => true,
|
||||||
|
'html' => $html,
|
||||||
|
'tiers' => $tiers ? $tiers : array(),
|
||||||
|
'packages' => $packages ? $packages : array(),
|
||||||
|
'restrict_to_packages' => $global_restrict || $product_restrict
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new WC_TPP_Frontend();
|
new WC_TPP_Frontend();
|
||||||
|
|||||||
@@ -11,9 +11,14 @@ if (!class_exists('WC_TPP_Product_Meta')) {
|
|||||||
class WC_TPP_Product_Meta {
|
class WC_TPP_Product_Meta {
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
// Simple product hooks
|
||||||
add_action('woocommerce_product_options_pricing', array($this, 'add_tier_pricing_fields'));
|
add_action('woocommerce_product_options_pricing', array($this, 'add_tier_pricing_fields'));
|
||||||
add_action('woocommerce_product_options_pricing', array($this, 'add_package_pricing_fields'));
|
add_action('woocommerce_product_options_pricing', array($this, 'add_package_pricing_fields'));
|
||||||
add_action('woocommerce_process_product_meta', array($this, 'save_tier_package_fields'));
|
add_action('woocommerce_process_product_meta', array($this, 'save_tier_package_fields'));
|
||||||
|
|
||||||
|
// Variable product variation hooks
|
||||||
|
add_action('woocommerce_variation_options_pricing', array($this, 'add_variation_pricing_fields'), 10, 3);
|
||||||
|
add_action('woocommerce_save_product_variation', array($this, 'save_variation_pricing_fields'), 10, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function add_tier_pricing_fields() {
|
public function add_tier_pricing_fields() {
|
||||||
@@ -25,18 +30,28 @@ if (!class_exists('WC_TPP_Product_Meta')) {
|
|||||||
<span class="description"><?php _e('Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'); ?></span>
|
<span class="description"><?php _e('Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'); ?></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="wc-tpp-tiers-container">
|
<table class="widefat wc-tpp-tiers-table">
|
||||||
<?php
|
<thead>
|
||||||
$tiers = get_post_meta($post->ID, '_wc_tpp_tiers', true);
|
<tr>
|
||||||
if (!is_array($tiers)) {
|
<th><?php _e('Min Quantity', 'wc-tier-package-prices'); ?></th>
|
||||||
$tiers = array();
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
||||||
}
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="wc-tpp-tiers-container">
|
||||||
|
<?php
|
||||||
|
$tiers = get_post_meta($post->ID, '_wc_tpp_tiers', true);
|
||||||
|
if (!is_array($tiers)) {
|
||||||
|
$tiers = array();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($tiers as $index => $tier) {
|
foreach ($tiers as $index => $tier) {
|
||||||
$this->render_tier_row($index, $tier);
|
$this->render_tier_row($index, $tier);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<p class="form-field">
|
<p class="form-field">
|
||||||
<button type="button" class="button wc-tpp-add-tier"><?php _e('Add Tier', 'wc-tier-package-prices'); ?></button>
|
<button type="button" class="button wc-tpp-add-tier"><?php _e('Add Tier', 'wc-tier-package-prices'); ?></button>
|
||||||
@@ -54,18 +69,28 @@ if (!class_exists('WC_TPP_Product_Meta')) {
|
|||||||
<span class="description"><?php _e('Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100.', 'wc-tier-package-prices'); ?></span>
|
<span class="description"><?php _e('Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100.', 'wc-tier-package-prices'); ?></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="wc-tpp-packages-container">
|
<table class="widefat wc-tpp-packages-table">
|
||||||
<?php
|
<thead>
|
||||||
$packages = get_post_meta($post->ID, '_wc_tpp_packages', true);
|
<tr>
|
||||||
if (!is_array($packages)) {
|
<th><?php _e('Quantity', 'wc-tier-package-prices'); ?></th>
|
||||||
$packages = array();
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
||||||
}
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="wc-tpp-packages-container">
|
||||||
|
<?php
|
||||||
|
$packages = get_post_meta($post->ID, '_wc_tpp_packages', true);
|
||||||
|
if (!is_array($packages)) {
|
||||||
|
$packages = array();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($packages as $index => $package) {
|
foreach ($packages as $index => $package) {
|
||||||
$this->render_package_row($index, $package);
|
$this->render_package_row($index, $package);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<p class="form-field">
|
<p class="form-field">
|
||||||
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
|
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
|
||||||
@@ -105,6 +130,136 @@ if (!class_exists('WC_TPP_Product_Meta')) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add tier and package pricing fields to product variations
|
||||||
|
*
|
||||||
|
* @param int $loop Position in the loop
|
||||||
|
* @param array $variation_data Variation data
|
||||||
|
* @param WP_Post $variation Variation post object
|
||||||
|
*/
|
||||||
|
public function add_variation_pricing_fields($loop, $variation_data, $variation) {
|
||||||
|
$variation_id = $variation->ID;
|
||||||
|
|
||||||
|
// Retrieve variation-specific data
|
||||||
|
$tiers = get_post_meta($variation_id, '_wc_tpp_tiers', true);
|
||||||
|
$packages = get_post_meta($variation_id, '_wc_tpp_packages', true);
|
||||||
|
$restrict = get_post_meta($variation_id, '_wc_tpp_restrict_to_packages', true);
|
||||||
|
|
||||||
|
if (!is_array($tiers)) {
|
||||||
|
$tiers = array();
|
||||||
|
}
|
||||||
|
if (!is_array($packages)) {
|
||||||
|
$packages = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div class="form-row form-row-full wc-tpp-variation-pricing" data-variation-loop="<?php echo esc_attr($loop); ?>">
|
||||||
|
<h4><?php _e('Tier & Package Pricing', 'wc-tier-package-prices'); ?></h4>
|
||||||
|
|
||||||
|
<!-- Tier Pricing Section -->
|
||||||
|
<div class="wc-tpp-variation-tiers">
|
||||||
|
<p><strong><?php _e('Tier Pricing', 'wc-tier-package-prices'); ?></strong></p>
|
||||||
|
<table class="widefat wc-tpp-tiers-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php _e('Min Quantity', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="wc-tpp-tiers-container">
|
||||||
|
<?php foreach ($tiers as $index => $tier) : ?>
|
||||||
|
<?php $this->render_variation_tier_row($loop, $index, $tier); ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="button wc-tpp-add-tier" data-loop="<?php echo esc_attr($loop); ?>">
|
||||||
|
<?php _e('Add Tier', 'wc-tier-package-prices'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Package Pricing Section -->
|
||||||
|
<div class="wc-tpp-variation-packages" style="margin-top: 15px;">
|
||||||
|
<p><strong><?php _e('Package Pricing', 'wc-tier-package-prices'); ?></strong></p>
|
||||||
|
<table class="widefat wc-tpp-packages-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php _e('Quantity', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="wc-tpp-packages-container">
|
||||||
|
<?php foreach ($packages as $index => $package) : ?>
|
||||||
|
<?php $this->render_variation_package_row($loop, $index, $package); ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="button wc-tpp-add-package" data-loop="<?php echo esc_attr($loop); ?>">
|
||||||
|
<?php _e('Add Package', 'wc-tier-package-prices'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Restriction Checkbox -->
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<?php
|
||||||
|
woocommerce_wp_checkbox(array(
|
||||||
|
'id' => 'wc_tpp_restrict_to_packages_' . $loop,
|
||||||
|
'name' => 'wc_tpp_restrict_to_packages[' . $loop . ']',
|
||||||
|
'label' => __('Restrict to Package Quantities', 'wc-tier-package-prices'),
|
||||||
|
'description' => __('Only allow quantities defined in packages above', 'wc-tier-package-prices'),
|
||||||
|
'desc_tip' => true,
|
||||||
|
'value' => $restrict,
|
||||||
|
'cbvalue' => 'yes',
|
||||||
|
'wrapper_class' => 'form-row form-row-full'
|
||||||
|
));
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Templates for JavaScript -->
|
||||||
|
<script type="text/html" id="wc-tpp-variation-tier-row-template-<?php echo esc_attr($loop); ?>">
|
||||||
|
<?php $this->render_variation_tier_row($loop, '{{INDEX}}', array('min_qty' => '', 'price' => '', 'label' => '')); ?>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="wc-tpp-variation-package-row-template-<?php echo esc_attr($loop); ?>">
|
||||||
|
<?php $this->render_variation_package_row($loop, '{{INDEX}}', array('qty' => '', 'price' => '', 'label' => '')); ?>
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a tier row for variations
|
||||||
|
*
|
||||||
|
* @param int $loop Variation loop index
|
||||||
|
* @param int $index Tier index
|
||||||
|
* @param array $tier Tier data
|
||||||
|
*/
|
||||||
|
private function render_variation_tier_row($loop, $index, $tier) {
|
||||||
|
WC_TPP_Template_Loader::get_instance()->display('admin/tier-row.twig', array(
|
||||||
|
'index' => $index,
|
||||||
|
'tier' => $tier,
|
||||||
|
'field_prefix' => 'wc_tpp_tiers[' . $loop . ']'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a package row for variations
|
||||||
|
*
|
||||||
|
* @param int $loop Variation loop index
|
||||||
|
* @param int $index Package index
|
||||||
|
* @param array $package Package data
|
||||||
|
*/
|
||||||
|
private function render_variation_package_row($loop, $index, $package) {
|
||||||
|
WC_TPP_Template_Loader::get_instance()->display('admin/package-row.twig', array(
|
||||||
|
'index' => $index,
|
||||||
|
'package' => $package,
|
||||||
|
'field_prefix' => 'wc_tpp_packages[' . $loop . ']'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public function save_tier_package_fields($post_id) {
|
public function save_tier_package_fields($post_id) {
|
||||||
// Verify nonce for security
|
// Verify nonce for security
|
||||||
if (!isset($_POST['woocommerce_meta_nonce']) || !wp_verify_nonce($_POST['woocommerce_meta_nonce'], 'woocommerce_save_data')) {
|
if (!isset($_POST['woocommerce_meta_nonce']) || !wp_verify_nonce($_POST['woocommerce_meta_nonce'], 'woocommerce_save_data')) {
|
||||||
@@ -167,6 +322,68 @@ if (!class_exists('WC_TPP_Product_Meta')) {
|
|||||||
$restrict_to_packages = isset($_POST['_wc_tpp_restrict_to_packages']) ? 'yes' : 'no';
|
$restrict_to_packages = isset($_POST['_wc_tpp_restrict_to_packages']) ? 'yes' : 'no';
|
||||||
update_post_meta($post_id, '_wc_tpp_restrict_to_packages', $restrict_to_packages);
|
update_post_meta($post_id, '_wc_tpp_restrict_to_packages', $restrict_to_packages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save tier and package pricing for variations
|
||||||
|
*
|
||||||
|
* @param int $variation_id Variation ID
|
||||||
|
* @param int $loop Position in loop
|
||||||
|
*/
|
||||||
|
public function save_variation_pricing_fields($variation_id, $loop) {
|
||||||
|
// Security check
|
||||||
|
if (!current_user_can('edit_products')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save tier pricing for this variation
|
||||||
|
if (isset($_POST['wc_tpp_tiers'][$loop])) {
|
||||||
|
$tiers = array();
|
||||||
|
foreach ($_POST['wc_tpp_tiers'][$loop] as $tier) {
|
||||||
|
if (!empty($tier['min_qty']) && !empty($tier['price'])) {
|
||||||
|
$tiers[] = array(
|
||||||
|
'min_qty' => absint($tier['min_qty']),
|
||||||
|
'price' => wc_format_decimal($tier['price']),
|
||||||
|
'label' => sanitize_text_field($tier['label'] ?? '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by minimum quantity
|
||||||
|
usort($tiers, function($a, $b) {
|
||||||
|
return $a['min_qty'] - $b['min_qty'];
|
||||||
|
});
|
||||||
|
update_post_meta($variation_id, '_wc_tpp_tiers', $tiers);
|
||||||
|
} else {
|
||||||
|
delete_post_meta($variation_id, '_wc_tpp_tiers');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save package pricing for this variation
|
||||||
|
if (isset($_POST['wc_tpp_packages'][$loop])) {
|
||||||
|
$packages = array();
|
||||||
|
foreach ($_POST['wc_tpp_packages'][$loop] as $package) {
|
||||||
|
if (!empty($package['qty']) && !empty($package['price'])) {
|
||||||
|
$packages[] = array(
|
||||||
|
'qty' => absint($package['qty']),
|
||||||
|
'price' => wc_format_decimal($package['price']),
|
||||||
|
'label' => sanitize_text_field($package['label'] ?? '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by quantity
|
||||||
|
usort($packages, function($a, $b) {
|
||||||
|
return $a['qty'] - $b['qty'];
|
||||||
|
});
|
||||||
|
update_post_meta($variation_id, '_wc_tpp_packages', $packages);
|
||||||
|
} else {
|
||||||
|
delete_post_meta($variation_id, '_wc_tpp_packages');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save restriction setting for this variation
|
||||||
|
if (isset($_POST['wc_tpp_restrict_to_packages'][$loop]) && $_POST['wc_tpp_restrict_to_packages'][$loop] === 'yes') {
|
||||||
|
update_post_meta($variation_id, '_wc_tpp_restrict_to_packages', 'yes');
|
||||||
|
} else {
|
||||||
|
delete_post_meta($variation_id, '_wc_tpp_restrict_to_packages');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new WC_TPP_Product_Meta();
|
new WC_TPP_Product_Meta();
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wählen Sie aus den
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "Optionen ansehen"
|
msgstr "Optionen ansehen"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "Optionen für %s ansehen"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Mindestmenge"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Preis"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Beschriftung (optional)"
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wähle aus den verf
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "Optionen ansehen"
|
msgstr "Optionen ansehen"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "Optionen für %s ansehen"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Mindestmenge"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Preis"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Beschriftung (optional)"
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wählen Sie aus den
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "Optionen ansehen"
|
msgstr "Optionen ansehen"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "Optionen für %s ansehen"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Mindestmenge"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Preis"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Beschriftung (optional)"
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wähle aus den verf
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "Optionen ansehen"
|
msgstr "Optionen ansehen"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "Optionen für %s ansehen"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Mindestmenge"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Preis"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Beschriftung (optional)"
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "The quantity %1$d is not available for %2$s. Please choose from the avai
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "View Options"
|
msgstr "View Options"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Min Quantity"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Price"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Label (optional)"
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "La quantité %1$d n'est pas disponible pour %2$s. Veuillez choisir parmi
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "Voir les options"
|
msgstr "Voir les options"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "Voir les options pour %s"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Quantité minimale"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Prix"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Étiquette (optionnel)"
|
||||||
|
|||||||
Binary file not shown.
@@ -240,6 +240,20 @@ msgstr "La quantità %1$d non è disponibile per %2$s. Si prega di scegliere tra
|
|||||||
msgid "View Options"
|
msgid "View Options"
|
||||||
msgstr "Visualizza opzioni"
|
msgstr "Visualizza opzioni"
|
||||||
|
|
||||||
#: includes/class-wc-tpp-frontend.php:178
|
|
||||||
msgid "View options for %s"
|
#: includes/class-wc-tpp-product-meta.php:36
|
||||||
msgstr "Visualizza opzioni per %s"
|
#: includes/class-wc-tpp-product-meta.php:140
|
||||||
|
msgid "Min Quantity"
|
||||||
|
msgstr "Quantità minima"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:37
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:141
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:164
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Prezzo"
|
||||||
|
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:38
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:142
|
||||||
|
#: includes/class-wc-tpp-product-meta.php:165
|
||||||
|
msgid "Label (optional)"
|
||||||
|
msgstr "Etichetta (facoltativo)"
|
||||||
|
|||||||
BIN
releases/wc-tier-and-package-prices-1.1.22.zip
Normal file
BIN
releases/wc-tier-and-package-prices-1.1.22.zip
Normal file
Binary file not shown.
1
releases/wc-tier-and-package-prices-1.1.22.zip.md5
Normal file
1
releases/wc-tier-and-package-prices-1.1.22.zip.md5
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7d5a5c7980a91dff5167c90a6f3290b0 wc-tier-and-package-prices-1.1.22.zip
|
||||||
1
releases/wc-tier-and-package-prices-1.1.22.zip.sha256
Normal file
1
releases/wc-tier-and-package-prices-1.1.22.zip.sha256
Normal file
@@ -0,0 +1 @@
|
|||||||
|
f94dee838a3f288b4acb3b3d9a4e88ef987f9b1bc918403186014d8d43fee6d9 wc-tier-and-package-prices-1.1.22.zip
|
||||||
BIN
releases/wc-tier-and-package-prices-1.2.0.zip
Normal file
BIN
releases/wc-tier-and-package-prices-1.2.0.zip
Normal file
Binary file not shown.
1
releases/wc-tier-and-package-prices-1.2.0.zip.md5
Normal file
1
releases/wc-tier-and-package-prices-1.2.0.zip.md5
Normal file
@@ -0,0 +1 @@
|
|||||||
|
cee7ab535938b4096f225f0e0640c9b7 wc-tier-and-package-prices-1.2.0.zip
|
||||||
1
releases/wc-tier-and-package-prices-1.2.0.zip.sha256
Normal file
1
releases/wc-tier-and-package-prices-1.2.0.zip.sha256
Normal file
@@ -0,0 +1 @@
|
|||||||
|
b9cda03ef4ae8994e34fc1a6d8768e9c0a088461d795c5e79cb51f670c93d0b0 wc-tier-and-package-prices-1.2.0.zip
|
||||||
@@ -4,33 +4,34 @@
|
|||||||
# @package WC_Tier_Package_Prices
|
# @package WC_Tier_Package_Prices
|
||||||
# @var int index
|
# @var int index
|
||||||
# @var array package
|
# @var array package
|
||||||
|
# @var string field_prefix (optional) - Prefix for field names (for variations)
|
||||||
#}
|
#}
|
||||||
<div class="wc-tpp-package-row">
|
{% set name_prefix = field_prefix is defined ? field_prefix : '_wc_tpp_packages' %}
|
||||||
<p class="form-field">
|
<tr class="wc-tpp-package-row">
|
||||||
<label>{{ 'Quantity'|__('wc-tier-package-prices') }}</label>
|
<td>
|
||||||
<input type="number"
|
<input type="number"
|
||||||
name="_wc_tpp_packages[{{ index|esc_attr }}][qty]"
|
name="{{ name_prefix }}[{{ index|esc_attr }}][qty]"
|
||||||
value="{{ package.qty|default('')|esc_attr }}"
|
value="{{ package.qty|default('')|esc_attr }}"
|
||||||
placeholder="{{ 'e.g., 10'|__('wc-tier-package-prices') }}"
|
placeholder="{{ 'e.g., 10'|__('wc-tier-package-prices') }}"
|
||||||
min="1"
|
min="1"
|
||||||
step="1"
|
step="1"
|
||||||
class="short">
|
class="short">
|
||||||
</p>
|
</td>
|
||||||
<p class="form-field">
|
<td>
|
||||||
<label>{{ 'Fixed Price'|__('wc-tier-package-prices') }}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="_wc_tpp_packages[{{ index|esc_attr }}][price]"
|
name="{{ name_prefix }}[{{ index|esc_attr }}][price]"
|
||||||
value="{{ package.price|default('')|esc_attr }}"
|
value="{{ package.price|default('')|esc_attr }}"
|
||||||
placeholder="{{ 'e.g., 99.99'|__('wc-tier-package-prices') }}"
|
placeholder="{{ 'e.g., 99.99'|__('wc-tier-package-prices') }}"
|
||||||
class="short wc_input_price">
|
class="short wc_input_price">
|
||||||
</p>
|
</td>
|
||||||
<p class="form-field">
|
<td>
|
||||||
<label>{{ 'Label (Optional)'|__('wc-tier-package-prices') }}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="_wc_tpp_packages[{{ index|esc_attr }}][label]"
|
name="{{ name_prefix }}[{{ index|esc_attr }}][label]"
|
||||||
value="{{ package.label|default('')|esc_attr }}"
|
value="{{ package.label|default('')|esc_attr }}"
|
||||||
placeholder="{{ 'e.g., Starter Pack'|__('wc-tier-package-prices') }}"
|
placeholder="{{ 'e.g., Starter Pack'|__('wc-tier-package-prices') }}"
|
||||||
class="short">
|
class="regular">
|
||||||
</p>
|
</td>
|
||||||
<button type="button" class="button wc-tpp-remove-package">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
|
<td>
|
||||||
</div>
|
<button type="button" class="button wc-tpp-remove-package">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|||||||
@@ -4,33 +4,34 @@
|
|||||||
# @package WC_Tier_Package_Prices
|
# @package WC_Tier_Package_Prices
|
||||||
# @var int index
|
# @var int index
|
||||||
# @var array tier
|
# @var array tier
|
||||||
|
# @var string field_prefix (optional) - Prefix for field names (for variations)
|
||||||
#}
|
#}
|
||||||
<div class="wc-tpp-tier-row">
|
{% set name_prefix = field_prefix is defined ? field_prefix : '_wc_tpp_tiers' %}
|
||||||
<p class="form-field">
|
<tr class="wc-tpp-tier-row">
|
||||||
<label>{{ 'Minimum Quantity'|__('wc-tier-package-prices') }}</label>
|
<td>
|
||||||
<input type="number"
|
<input type="number"
|
||||||
name="_wc_tpp_tiers[{{ index|esc_attr }}][min_qty]"
|
name="{{ name_prefix }}[{{ index|esc_attr }}][min_qty]"
|
||||||
value="{{ tier.min_qty|default('')|esc_attr }}"
|
value="{{ tier.min_qty|default('')|esc_attr }}"
|
||||||
placeholder="{{ 'e.g., 10'|__('wc-tier-package-prices') }}"
|
placeholder="{{ 'e.g., 10'|__('wc-tier-package-prices') }}"
|
||||||
min="1"
|
min="1"
|
||||||
step="1"
|
step="1"
|
||||||
class="short">
|
class="short">
|
||||||
</p>
|
</td>
|
||||||
<p class="form-field">
|
<td>
|
||||||
<label>{{ 'Price per Unit'|__('wc-tier-package-prices') }}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="_wc_tpp_tiers[{{ index|esc_attr }}][price]"
|
name="{{ name_prefix }}[{{ index|esc_attr }}][price]"
|
||||||
value="{{ tier.price|default('')|esc_attr }}"
|
value="{{ tier.price|default('')|esc_attr }}"
|
||||||
placeholder="{{ 'e.g., 9.99'|__('wc-tier-package-prices') }}"
|
placeholder="{{ 'e.g., 9.99'|__('wc-tier-package-prices') }}"
|
||||||
class="short wc_input_price">
|
class="short wc_input_price">
|
||||||
</p>
|
</td>
|
||||||
<p class="form-field">
|
<td>
|
||||||
<label>{{ 'Label (Optional)'|__('wc-tier-package-prices') }}</label>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="_wc_tpp_tiers[{{ index|esc_attr }}][label]"
|
name="{{ name_prefix }}[{{ index|esc_attr }}][label]"
|
||||||
value="{{ tier.label|default('')|esc_attr }}"
|
value="{{ tier.label|default('')|esc_attr }}"
|
||||||
placeholder="{{ 'e.g., Wholesale'|__('wc-tier-package-prices') }}"
|
placeholder="{{ 'e.g., Wholesale'|__('wc-tier-package-prices') }}"
|
||||||
class="short">
|
class="regular">
|
||||||
</p>
|
</td>
|
||||||
<button type="button" class="button wc-tpp-remove-tier">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
|
<td>
|
||||||
</div>
|
<button type="button" class="button wc-tpp-remove-tier">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
# @var object product
|
# @var object product
|
||||||
# @var array tiers
|
# @var array tiers
|
||||||
# @var array packages
|
# @var array packages
|
||||||
|
# @var bool restrict_to_packages
|
||||||
#}
|
#}
|
||||||
<div class="wc-tpp-pricing-container">
|
<div class="wc-tpp-pricing-container">
|
||||||
{% if tiers is not empty and get_option('wc_tpp_enable_tier_pricing') == 'yes' %}
|
{% if tiers is not empty %}
|
||||||
{% include 'frontend/tier-pricing-table.twig' with {'product': product, 'tiers': tiers} %}
|
{% include 'frontend/tier-pricing-table.twig' with {'product': product, 'tiers': tiers} %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if packages is not empty and get_option('wc_tpp_enable_package_pricing') == 'yes' %}
|
{% if packages is not empty %}
|
||||||
{% include 'frontend/package-pricing-display.twig' with {'packages': packages, 'restrict_to_packages': restrict_to_packages|default(false)} %}
|
{% include 'frontend/package-pricing-display.twig' with {'packages': packages, 'restrict_to_packages': restrict_to_packages|default(false)} %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Plugin Name: WooCommerce Tier and Package Prices
|
* Plugin Name: WooCommerce Tier and Package Prices
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
|
||||||
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
|
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
|
||||||
* Version: 1.1.21
|
* Version: 1.2.3
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
* Text Domain: wc-tier-package-prices
|
* Text Domain: wc-tier-package-prices
|
||||||
@@ -23,7 +23,7 @@ if (!defined('ABSPATH')) {
|
|||||||
|
|
||||||
// Define plugin constants
|
// Define plugin constants
|
||||||
if (!defined('WC_TPP_VERSION')) {
|
if (!defined('WC_TPP_VERSION')) {
|
||||||
define('WC_TPP_VERSION', '1.1.21');
|
define('WC_TPP_VERSION', '1.2.3');
|
||||||
}
|
}
|
||||||
if (!defined('WC_TPP_PLUGIN_DIR')) {
|
if (!defined('WC_TPP_PLUGIN_DIR')) {
|
||||||
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user