37 Commits

Author SHA1 Message Date
b2efb89d59 Update CLAUDE.md and add v1.2.9 release package
- Updated Current Version to 1.2.9
- Moved v1.2.8 bugs to 'Bugfixes (Completed in v1.2.9)' section
- Added detailed fix descriptions for both bugs:
  * Translation fix with esc_html__() and proper text domain
  * Placeholder encoding fix by removing translation filter
  * Variation save logic refactoring with defensive programming
- Updated roadmap section to v1.2.10+
- Added release package and checksums:
  * wc-tier-and-package-prices-1.2.9.zip (441KB, 383 files)
  * MD5: 073fbf114a32d99cd8ced683a1efa19c
  * SHA256: 00d2568e9acfd7fc05c88b9048a6a281d007b55dec9c4c7a4a55cb515e013e97

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 05:04:20 +01:00
0f5779dc56 Release version 1.2.9 - Translation and deletion fixes
## Bugfixes

1. **Price Header Not Translated**
   - Fixed translation function placement in printf statements
   - Changed from printf(__()) to printf(esc_html__())
   - Headers now display in administrator's configured language

2. **Placeholder HTML Entity Encoding**
   - Currency symbols were showing as HTML entities (e.g., &euro;)
   - Removed translation filter from concatenated placeholder strings
   - Placeholders are example values that should not be translated

3. **Variation Pricing Still Not Deletable (Regression)**
   - Despite v1.2.8 fix, edge cases remained due to conditional branching
   - Refactored save logic to be more defensive
   - Always initializes arrays, then unconditionally updates or deletes
   - Guarantees proper cleanup regardless of POST data structure

## Technical Details

- Updated all 6 table headers: printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), ...)
- Removed |__() filter from Twig placeholder concatenations
- Refactored save_variation_pricing_fields() with simplified logic:
  * Initialize arrays at start
  * Populate only if valid POST data exists
  * Always perform update (if !empty) or delete (if empty)
- Added is_array() check for extra safety

## Changed Files

- includes/class-wc-tpp-product-meta.php
- templates/admin/tier-row.twig
- templates/admin/package-row.twig

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 05:02:50 +01:00
d71f2c01dc fixed markdown syntax 2025-12-30 01:34:46 +01:00
82c8008fed added wp-core symlink 2025-12-30 01:33:24 +01:00
4871b7957d added skills to end sessions 2025-12-30 01:32:03 +01:00
f7508a3d9c Document v1.2.8 learnings in CLAUDE.md
Added two new critical sections documenting lessons learned during v1.2.8 development:

1. Currency Symbol Display
   - Proper pattern for displaying currency in table headers using printf()
   - How to pass currency_symbol to Twig templates
   - Correct concatenation in Twig placeholders
   - List of affected render methods

2. Post Meta Deletion vs. Empty Arrays
   - Explains WordPress distinction between deleted meta and empty arrays
   - Shows wrong pattern (saving empty arrays) vs correct pattern (deleting meta)
   - Why this matters for database cleanliness and bug prevention
   - Affected methods in save_tier_package_fields() and save_variation_pricing_fields()

These patterns prevent future bugs and ensure consistent implementation across the codebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 01:31:11 +01:00
23f81ce58c Add v1.2.8 release package and checksums
- Release package: wc-tier-and-package-prices-1.2.8.zip (440KB, 383 files)
- MD5: d412c85ef32b2e79e17227749265919f
- SHA256: 0c47e2a966c485b377beaffb56703a130158a15e7ffe9d95b43dea84f9229e9c

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 01:29:10 +01:00
9e96ff3321 Merge branch 'dev' 2025-12-30 01:28:09 +01:00
cf11cb5bd1 Update CLAUDE.md for v1.2.8 release
- Updated Current Version to 1.2.8
- Moved v1.2.7 bugs to 'Bugfixes (Completed in v1.2.8)' section
- Added detailed fix descriptions for both bugs
- Updated roadmap section to v1.2.9+

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 01:26:48 +01:00
f26574aa4b Release version 1.2.8 - Currency display and data deletion fixes
Fixed two important bugs reported in v1.2.7:

Bug 1: Currency Symbol Missing in Admin Headers and Placeholders
- Table headers now show "Price (CURRENCY)" instead of just "Price"
- Input placeholders include currency symbol (e.g., "9.99 $")
- Better UX for multi-currency stores

Bug 2: Variation Pricing Data Not Deleted Properly (Critical)
- Empty pricing arrays were being saved instead of deleted
- Fixed save logic to check if arrays are empty after filtering
- Properly deletes post meta when all entries are removed
- Affects simple products, variable parents, and variations

Technical changes:
- Updated all table headers with currency symbol display
- Modified all render methods to pass currency_symbol to templates
- Updated Twig templates to use currency in placeholders
- Fixed save_tier_package_fields() and save_variation_pricing_fields()
- Added !empty() checks before update_post_meta() calls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 01:23:55 +01:00
348158050e added new permitted shell command 2025-12-30 01:06:31 +01:00
d7a61f29b4 Merge branch 'dev' 2025-12-30 01:05:21 +01:00
10a1f94a31 Update CLAUDE.md with v1.2.7 learnings and roadmap
Updated documentation to reflect:
- Current version is now 1.2.7
- v1.2.5 issues (table headers, parent pricing forms) are FIXED in v1.2.6/v1.2.7
- Translation updates completed in v1.2.7
- Added critical section on WooCommerce product type-specific hooks

Key learnings documented:
- woocommerce_product_options_pricing only fires for simple products
- woocommerce_product_options_general_product_data fires for all product types
- Proper hook selection is critical for variable product parent UI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 01:04:48 +01:00
ae946683b3 Update translation files with new strings from v1.2.6 and v1.2.7
Added translations for variable product parent pricing features:
- "Default Tier & Package Pricing for All Variations"
- "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
- "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
- "Restrict to Package Quantities (Default)"
- "Default restriction setting for all variations. Only allow quantities defined in packages above."

Updated all available language files:
- wc-tier-package-prices.pot (v1.2.7)
- de_CH, de_CH_informal, de_DE, de_DE_informal (German)
- fr_CH (French, Switzerland)
- it_CH (Italian, Switzerland)
- en_US (English, US)

Compiled all .po files to .mo for production use.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 01:01:49 +01:00
71ea40598f Merge branch 'dev' 2025-12-30 00:48:46 +01:00
e5aca708cc Release version 1.2.7 - Fix v1.2.6 regressions (both issues still not working)
Fixed both critical issues that were not resolved in v1.2.6:

1. Variable Product Forms Still Not Showing (Critical)
   - v1.2.6 used wrong hook (woocommerce_product_options_pricing)
   - That hook only fires for simple products, not variable products
   - Changed to woocommerce_product_options_general_product_data
   - This hook fires for all product types after general tab
   - Forms now appear correctly for variable product parents

2. Table Headers Still Visible When Empty (Critical)
   - CSS :has() pseudo-class wasn't working reliably
   - Implemented JavaScript + CSS class approach instead
   - Added updateTableHeaders() function that toggles has-rows class
   - Headers hide by default, show only when table has rows
   - Function called on page load and after all add/remove operations
   - Works across all browsers without modern CSS requirements

Changed files:
- includes/class-wc-tpp-product-meta.php - Fixed WooCommerce hook
- assets/css/admin.css - Class-based header visibility
- assets/js/admin.js - Added updateTableHeaders() and parent handlers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:48:46 +01:00
a73ff4926f Merge branch 'dev' 2025-12-30 00:38:33 +01:00
78101baf88 Release version 1.2.6 - Critical bugfixes for v1.2.5 features
Fixed two critical issues that prevented v1.2.5 features from working:

1. Parent Product Pricing Forms Not Visible (Critical)
   - Variable products were missing admin UI for parent pricing configuration
   - v1.2.5 implemented backend logic but forgot the admin forms
   - Added add_variable_parent_pricing_fields() method
   - Modified existing methods to skip variable products (simple only)
   - Parent pricing now fully functional with matching UI

2. Table Headers Not Hiding When Empty
   - CSS sibling selector ~ doesn't work when thead comes before tbody
   - Removed incorrect selector, kept only :has() pseudo-class
   - Added !important flag for proper specificity
   - Modern browser support (Chrome 105+, Firefox 121+, Safari 15.4+)

Changed files:
- includes/class-wc-tpp-product-meta.php - Added parent pricing forms
- assets/css/admin.css - Fixed header hiding CSS
- wc-tier-and-package-prices.php - Version 1.2.6
- composer.json - Version 1.2.6
- CHANGELOG.md - Detailed release notes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:38:29 +01:00
d99ece71e4 Update CLAUDE.md with v1.2.5 implementation learnings
- Marked both v1.2.5 enhancements as completed
- Added implementation details for CSS :has() pseudo-class for hiding empty table headers
- Documented parent product default pricing fallback pattern
- Added helper method documentation (get_packages_with_fallback, is_restriction_enabled)
- Reorganized roadmap section (completed v1.2.5 enhancements moved from planned)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:28:00 +01:00
e4747130e4 Add release package for version 1.2.5
Release package includes:
- Parent product default pricing for variable products
- Hide empty table headers in admin area
- Package size: 433KB (383 files)
- Checksums: MD5 and SHA256 generated

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:25:14 +01:00
e4ecc2c0be Release version 1.2.5 - Parent product default pricing and UI enhancements
Added:
- Parent product default pricing for variable products - set tier/package pricing once on parent, variations inherit unless overridden
- Hide empty table headers in admin until pricing rules are defined

Technical:
- Added parent fallback logic to get_tier_price() and get_package_price() methods
- Created helper methods get_packages_with_fallback() and is_restriction_enabled() in cart class
- Updated all cart methods to support parent product defaults
- Added CSS :has() selectors to hide table headers when tbody is empty
- Fixed cart pricing calls to pass correct product ID for fallback resolution

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:23:56 +01:00
e2e40538df removed /finish-session in favor of the global /end-session 2025-12-30 00:16:09 +01:00
67e11d3030 permitted more commands 2025-12-30 00:15:42 +01:00
2de6a92784 Update CLAUDE.md with v1.2.4 learnings and CSS troubleshooting guide
Added comprehensive CSS specificity documentation from v1.2.4 bugfixes:
- CSS Specificity Issues section documenting table border and help-tip positioning problems
- Detailed troubleshooting guide for WooCommerce CSS overrides
- Solution patterns for table styling and float-based layouts
- General rules and diagnostic steps for CSS issues

Updated:
- Last Updated date to 2025-12-30
- Roadmap section to mark v1.2.4 bugs as completed
- Moved future enhancements to v1.2.5+ section

Key learnings documented:
- WooCommerce core CSS requires !important flags for overrides
- Float-based layouts should be replaced with flexbox for precise control
- Table borders require comprehensive targeting of all structural elements
- border-collapse: collapse is essential for borderless tables

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:14:16 +01:00
47f2ed771b Add release package for version 1.2.4
Release package includes:
- Fixed admin table borders with !important flags
- Fixed checkbox and help icon layout with flexbox
- Package size: 432KB (383 files)
- Checksums: MD5 and SHA256 generated

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:04:19 +01:00
880983a879 Merge branch 'dev' 2025-12-30 00:02:38 +01:00
937e80fce3 Release version 1.2.4 - Fix admin table borders and checkbox layout
Fixed:
- Admin table borders still visible despite v1.2.3 fix - added !important flags
- Help icon positioning at right edge instead of next to label - changed to flexbox layout
- Increased checkbox margin from 8px to 12px for better spacing

Technical:
- Added border-collapse: collapse !important to force borderless tables
- Changed label layout from float to inline-flex for proper help-tip positioning
- Added comprehensive border removal with !important on all table elements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 00:02:34 +01:00
9c5e3c85ae Add release packages for v1.2.1, v1.2.2, and v1.2.3
Release packages with checksums:
- v1.2.1: Admin UI fixes and frontend display improvements
- v1.2.2: Variation UI styling, translations, checkbox rendering
- v1.2.3: Borderless tables and checkbox tooltip improvements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 21:09:45 +01:00
3d47ee63d8 Update CLAUDE.md with v1.2.2 and v1.2.3 release learnings
Added detailed documentation for release package creation process:
- Critical exclusion patterns (especially wordpress symlink)
- Expected package size and verification steps
- Complete zip command with all necessary exclusions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 21:09:31 +01:00
04eba21521 Release version 1.2.3 - Admin UI styling improvements
Fixed two admin UI bugs:
- Applied borderless table styling to all tier/package tables for consistency
- Fixed checkbox tooltip display and improved checkbox-label spacing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 21:06:25 +01:00
ef314e36bc Release version 1.2.2 - Admin UI bugfixes for variations
Fixed three bugs in variation pricing interface:
- Removed table borders for variation pricing to match WooCommerce UI style
- Added missing translations (Min Quantity, Price, Label) to all language files
- Fixed restrict_to_packages checkbox rendering in variation fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 20:53:23 +01:00
415f39e826 Update .gitignore for local development files
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 20:28:45 +01:00
6733ca5f98 Release version 1.2.1 - Critical bugfixes for v1.2.0
Fixed two critical bugs introduced in v1.2.0:

1. Admin UI table structure mismatch - CSS still had flexbox styling from
   old template structure, breaking new table layout. Updated admin.css to
   properly style table rows with standard table cell padding.

2. Frontend pricing display not showing - Template was checking global
   enable settings before display. Removed those checks so pricing shows
   if configured on product AND display setting is enabled.

Files changed:
- wc-tier-and-package-prices.php - Version bump to 1.2.1
- composer.json - Version bump to 1.2.1
- CLAUDE.md - Updated version and documented fixes
- CHANGELOG.md - Added detailed v1.2.1 entry with root cause analysis
- assets/css/admin.css - Fixed table styling (removed flexbox)
- templates/frontend/pricing-table.twig - Removed global enable checks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 20:28:28 +01:00
00c5b87aac Add wordpress symlink to gitignore
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 20:02:58 +01:00
87784f467a Release version 1.2.0 - Add complete variable product support
This major feature release adds full support for WooCommerce variable products with variation-level pricing configuration.

## Key Features
- Each product variation can have independent tier and package pricing
- AJAX-based dynamic pricing table loading on variation selection
- Admin UI integrated into WooCommerce variation panels
- Full backward compatibility with existing simple product functionality
- WooCommerce Blocks compatibility maintained

## Implementation Highlights
- Effective ID pattern throughout codebase for variation handling
- Variation-specific meta boxes with field prefix support
- Template system updated to support both simple and variation products
- JavaScript enhancements for variation selector integration
- Cart logic updated to handle variation pricing correctly

## Files Changed
- Core: wc-tier-and-package-prices.php (version 1.2.0), composer.json
- Cart: includes/class-wc-tpp-cart.php (effective ID logic)
- Frontend: includes/class-wc-tpp-frontend.php (AJAX endpoint, variation detection)
- Admin: includes/class-wc-tpp-product-meta.php (variation hooks and methods)
- Templates: templates/admin/*.twig (field prefix support, table structure)
- JavaScript: assets/js/*.js (variation support)
- Documentation: CHANGELOG.md, README.md, CLAUDE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 20:02:03 +01:00
45a89fc693 Add Claude Code workflow improvements and git permissions
- Added finish-session command to guide end-of-session workflow
- Updated settings.local.json with git checkout and rebase permissions
- The finish-session command instructs AI to update CLAUDE.md with learnings
- Added git workflow commands to auto-approval list for smoother operations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 17:16:11 +01:00
f530b37285 Update CLAUDE.md with v1.1.22 release learnings and best practices
- Added detailed release package creation instructions
- Documented correct zip command execution from parent directory
- Added comprehensive verification steps for release packages
- Included complete Git workflow for releases (dev → main → tag → push)
- Added common pitfalls and solutions section
- Documented WooCommerce CSS classes for admin UI
- Added Twig template modification guidelines
- Included complete release workflow summary with time estimates

These updates will help future AI assistants avoid common mistakes
when creating releases, especially around package creation and Git workflows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 17:09:46 +01:00
62 changed files with 2207 additions and 215 deletions

0
.claude/commands/.gitignore vendored Normal file
View File

View File

@@ -19,7 +19,30 @@
"Bash(git commit:*)",
"Bash(node -c:*)",
"Bash(php -l:*)",
"Bash(git push:*)"
"Bash(git push:*)",
"Bash(git checkout:*)",
"Bash(git rebase:*)",
"Bash(git merge:*)",
"Bash(git stash:*)",
"Bash(for po in languages/*.po)",
"Bash('wc-tier-and-package-prices/*.log' )",
"Bash('wc-tier-and-package-prices/.claude/*' )",
"Bash('wc-tier-and-package-prices/CLAUDE.md' )",
"Bash('wc-tier-and-package-prices/releases/*' )",
"Bash('wc-tier-and-package-prices/node_modules/*' )",
"Bash('wc-tier-and-package-prices/.DS_Store' )",
"Bash('wc-tier-and-package-prices/Thumbs.db' )",
"Bash('wc-tier-and-package-prices/.vscode/*' )",
"Bash('wc-tier-and-package-prices/.idea/*' )",
"Bash('wc-tier-and-package-prices/*.sublime-*' )",
"Bash('wc-tier-and-package-prices/notes.*' )",
"Bash('wc-tier-and-package-prices/logs/*' )",
"Bash('wc-tier-and-package-prices/templates/cache/*' )",
"Bash('wc-tier-and-package-prices/composer.lock' )",
"Bash('*/wordpress/*')",
"Bash(echo:*)",
"Skill(fix-session)",
"Skill(fix-session:*)"
]
}
}

4
.gitignore vendored
View File

@@ -29,3 +29,7 @@ notes.*
# OS
.DS_Store
._*
# local code
wordpress
core

View File

@@ -5,6 +5,379 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.9] - 2025-12-30
### Fixed
- **Price Header Not Translated**: The "Price (%s)" header in admin tables was not being properly translated because the translation function was placed incorrectly within the printf statement. Changed from `printf(__('Price (%s)', ...), ...)` to `printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), ...)` to ensure proper translation while maintaining the currency placeholder functionality.
- **Placeholder HTML Entity Encoding Issue**: Currency symbols in price input placeholders were being displayed as HTML entities (e.g., "&euro;" instead of "€") because the concatenated string was being passed through the translation filter which was encoding special characters. Removed the unnecessary translation filter from concatenated placeholder strings since they are example values that should not be translated.
- **Variation Pricing Still Not Deletable (Regression from v1.2.8)**: Despite the fix in v1.2.8, variation pricing data was still not being properly deleted in all scenarios. The issue was with the conditional logic structure - the code had separate `if/else` branches that could fail in edge cases. Restructured the save logic to be more defensive: initialize arrays at the start, populate only if valid POST data exists, then unconditionally perform either update (if not empty) or delete (if empty). This guarantees proper cleanup regardless of POST data structure.
### Technical Details
**Translation Fix**:
- Changed all 6 instances of `printf(__('Price (%s)', 'wc-tier-package-prices'), ...)`
- To: `printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), ...)`
- The `__()` function now receives the text domain parameter correctly
- Added `esc_html` for proper output escaping
**Placeholder Encoding Fix**:
- Changed tier-row.twig placeholder from: `{{ ('e.g., 9.99 ' ~ currency_symbol)|__('wc-tier-package-prices') }}`
- To: `{{ 'e.g., 9.99 ' ~ currency_symbol }}`
- Changed package-row.twig placeholder from: `{{ ('e.g., 99.99 ' ~ currency_symbol)|__('wc-tier-package-prices') }}`
- To: `{{ 'e.g., 99.99 ' ~ currency_symbol }}`
- Removed translation filter from concatenated example values to prevent HTML entity encoding
**Variation Save Logic Refactor**:
```php
// Old pattern (v1.2.8):
if (isset($_POST['wc_tpp_tiers'][$loop])) {
$tiers = array();
// ... populate tiers ...
if (!empty($tiers)) {
update_post_meta(...);
} else {
delete_post_meta(...);
}
} else {
delete_post_meta(...);
}
// New pattern (v1.2.9):
$tiers = array();
if (isset($_POST['wc_tpp_tiers'][$loop]) && is_array($_POST['wc_tpp_tiers'][$loop])) {
// ... populate tiers ...
}
// Always perform update or delete based on final state
if (!empty($tiers)) {
update_post_meta(...);
} else {
delete_post_meta(...);
}
```
- Eliminated conditional branching that could miss edge cases
- Added explicit `is_array()` check for extra safety
- Guaranteed that one of update_post_meta() or delete_post_meta() is always called
- Applied to both `save_variation_pricing_fields()` for tier and package pricing
**User Impact**:
- Price headers now display in the administrator's configured language
- Currency symbols display correctly without HTML encoding in placeholders
- Variation pricing deletion now works reliably in all scenarios
- Database remains clean with no orphaned empty arrays
### Changed Files
- `includes/class-wc-tpp-product-meta.php` - Fixed translation function calls in 6 table headers; refactored save_variation_pricing_fields() logic for tiers and packages
- `templates/admin/tier-row.twig` - Removed translation filter from placeholder concatenation
- `templates/admin/package-row.twig` - Removed translation filter from placeholder concatenation
## [1.2.8] - 2025-12-30
### Fixed
- **Currency Symbol Missing in Admin Headers and Placeholders**: Table headers in the admin pricing configuration now display "Price (CURRENCY)" instead of just "Price", making it immediately clear which currency is being used. Price input placeholders now show currency symbol (e.g., "e.g., 9.99 $" instead of "e.g., 9.99"), providing better UX for administrators configuring pricing in different currencies.
- **Variation Pricing Data Not Deleted Properly (Critical)**: When administrators deleted all tier or package pricing entries from a variation (or simple/parent product) and saved, the empty pricing data was still stored in the database instead of being deleted. This caused variations to retain deleted pricing rules. The save logic now properly detects when the filtered pricing arrays are empty after removing invalid entries and deletes the post meta instead of saving empty arrays.
### Technical Details
**Currency Symbol Enhancement**:
- Updated all table headers to use `printf(__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol())`
- Modified `render_tier_row()` and `render_package_row()` methods to pass `currency_symbol` to Twig templates
- Updated `render_variation_tier_row()` and `render_variation_package_row()` with same currency symbol parameter
- Changed Twig template placeholders from `'e.g., 9.99'` to `('e.g., 9.99 ' ~ currency_symbol)`
- Affects all pricing contexts: simple products, variable product parents, and variations
**Pricing Deletion Fix**:
- Modified `save_tier_package_fields()` method (simple/parent products) to check `if (!empty($tiers))` before saving
- Modified `save_variation_pricing_fields()` method (variations) with same empty check logic
- Changed logic from "save on isset, delete otherwise" to "filter entries, then save if not empty, delete if empty"
- Applies to both tier pricing and package pricing for all product types
- Root cause was filtering out empty entries but still calling `update_post_meta()` with an empty array
**User Impact**:
- Administrators see currency symbol in all pricing configuration interfaces
- Clear indication of which currency prices should be entered in
- Deleting all pricing rules now properly removes them from the database
- No orphaned pricing data remains after deletion
- Works correctly for simple products, variable product parents, and variations
### Changed Files
- `includes/class-wc-tpp-product-meta.php` - Added currency symbol to all table headers; updated all render methods to pass currency symbol; fixed empty array deletion logic in both save methods
- `templates/admin/tier-row.twig` - Updated placeholder to include currency symbol
- `templates/admin/package-row.twig` - Updated placeholder to include currency symbol
## [1.2.7] - 2025-12-30
### Fixed
- **Variable Product Forms Still Not Showing (Critical)**: The v1.2.6 fix used the wrong WooCommerce hook. The `woocommerce_product_options_pricing` hook only fires for simple products, not variable products. Changed to use `woocommerce_product_options_general_product_data` hook which fires for all product types after the general tab, allowing the code to check product type and conditionally display the parent pricing fields.
- **Table Headers Still Visible When Empty (Critical)**: The CSS `:has()` pseudo-class approach from v1.2.6 wasn't working reliably across all browsers. Implemented a JavaScript-based solution that adds/removes a `has-rows` class on tables based on whether they contain pricing rules. Headers now hide by default and show only when the table has rows, with JavaScript updating the state when rows are added or removed.
### Technical Details
**Variable Product Hook Fix**:
- Changed from `woocommerce_product_options_pricing` to `woocommerce_product_options_general_product_data`
- The general product data hook fires for all product types
- Method still checks `$product->is_type('variable')` to only show for variable products
- This ensures forms appear in the correct location in the WordPress admin
**Table Header Visibility Fix**:
- Replaced CSS-only `:has()` solution with JavaScript + CSS class approach
- CSS now uses `.wc-tpp-tiers-table.has-rows thead` to show headers
- Added `updateTableHeaders()` JavaScript function that checks row count and toggles class
- Function is called on page load and after any add/remove row operation
- Works reliably across all browsers without requiring modern CSS features
**User Impact**:
- Variable product parent pricing forms now actually appear in the WordPress admin
- Table headers properly hide when empty and show when populated
- No browser compatibility issues - works in all modern browsers
### Changed Files
- `includes/class-wc-tpp-product-meta.php` - Changed hook from `woocommerce_product_options_pricing` to `woocommerce_product_options_general_product_data`
- `assets/css/admin.css` - Replaced `:has()` pseudo-class with class-based approach
- `assets/js/admin.js` - Added `updateTableHeaders()` function and calls after all row operations; added handlers for variable product parent forms
## [1.2.6] - 2025-12-30
### Fixed
- **Parent Product Pricing Forms Not Visible (Critical)**: Variable products were missing the pricing configuration forms on the parent product edit page. The v1.2.5 feature for parent product default pricing was implemented in the backend logic (cart calculations and frontend display) but the admin UI to configure these defaults was not added. Now variable product parents have a dedicated "Default Tier & Package Pricing for All Variations" section in the product edit page where administrators can configure default pricing that applies to all variations unless a specific variation overrides it.
- **Table Headers Not Hiding When Empty**: The CSS selector for hiding table headers when no pricing rules exist was using an incorrect approach. The sibling selector `~` doesn't work when `<thead>` comes before `<tbody>` in the HTML structure. Removed the sibling selector and kept only the `:has()` pseudo-class approach with `!important` flag for proper specificity.
### Technical Details
**Parent Product Forms Fix**:
- Added new method `add_variable_parent_pricing_fields()` in `WC_TPP_Product_Meta` class
- Hooked to `woocommerce_product_options_pricing` action but only displays for variable products
- Modified existing `add_tier_pricing_fields()` and `add_package_pricing_fields()` to skip variable products (they now only show for simple products)
- Parent product forms use same field names as simple products (`_wc_tpp_tiers`, `_wc_tpp_packages`, `_wc_tpp_restrict_to_packages`)
- Data is saved to parent product post meta using existing `save_tier_package_fields()` method
- Backend fallback logic from v1.2.5 now has matching admin UI for configuration
**CSS Selector Fix**:
- Removed incorrect `.wc-tpp-tiers-container:empty ~ thead` selector (sibling selector can't target previous elements)
- Kept only `.wc-tpp-tiers-table:has(tbody.wc-tpp-tiers-container:empty) thead` with `!important` flag
- `:has()` pseudo-class is supported in modern browsers (Chrome 105+, Firefox 121+, Safari 15.4+)
**User Impact**:
- Administrators can now configure default tier/package pricing on variable product parents (feature was non-functional in v1.2.5)
- Table headers properly hide when no pricing rules exist, creating cleaner admin interface
- No data migration needed - existing configurations remain intact
### Changed Files
- `includes/class-wc-tpp-product-meta.php` - Added `add_variable_parent_pricing_fields()` method; modified `add_tier_pricing_fields()` and `add_package_pricing_fields()` to skip variable products
- `assets/css/admin.css` - Fixed table header hiding CSS selector; removed incorrect sibling selector; added `!important` flag
## [1.2.5] - 2025-12-30
### Added
- **Parent Product Default Pricing**: Variable products can now define tier and package pricing at the parent product level that applies as defaults to all variations. Individual variations can override these defaults with their own specific pricing. This makes it much easier to set up pricing for products with many variations - set defaults once on the parent, then only customize the variations that need different pricing.
- **Hide Empty Table Headers**: Table headers for tier and package pricing in the admin area now automatically hide when no pricing rules are defined. This creates a cleaner interface when starting to configure a product, showing only the helpful empty state message and "Add" button.
### Technical Details
**Parent Fallback Implementation**:
- Modified `WC_TPP_Frontend::get_tier_price()` and `WC_TPP_Frontend::get_package_price()` to fall back to parent product pricing when variation doesn't have its own pricing
- Updated `WC_TPP_Cart` to use helper methods `get_packages_with_fallback()` and `is_restriction_enabled()` for consistent parent fallback behavior
- All cart validation, quantity restriction, and display methods now support parent product defaults
- Fixed cart pricing calls to pass parent `$product_id` instead of `$effective_id` for proper fallback resolution
**CSS Enhancement**:
- Added `:has()` pseudo-class selectors to hide table headers when tbody is empty
- Leverages existing empty state message styling for consistent UX
**Backward Compatibility**:
- 100% backward compatible - existing products continue working as before
- No database migrations required
- Variations with specific pricing take precedence over parent defaults
### Changed Files
- `includes/class-wc-tpp-frontend.php` - Added parent fallback logic to `get_tier_price()` and `get_package_price()` methods
- `includes/class-wc-tpp-cart.php` - Added helper methods `get_packages_with_fallback()` and `is_restriction_enabled()`; updated all cart methods to support parent fallback; fixed pricing calls to use correct product ID
- `assets/css/admin.css` - Added CSS rules to hide table headers when no pricing rules exist
## [1.2.4] - 2025-12-30
### Fixed
- **Admin Table Borders (Critical)**: Fixed table borders still appearing in v1.2.3 despite borderless styling attempt. WooCommerce's default CSS was overriding `border: none` declarations. Added `!important` flags to all border removal rules and `border-collapse: collapse !important` to force borderless styling. Now all tier/package pricing tables (simple and variable products) display correctly without borders, matching WooCommerce's clean admin UI.
- **Checkbox and Help Icon Layout**: Fixed help icon positioning and checkbox spacing issues from v1.2.3. The help icon was appearing at the right edge of the container instead of next to the label text. Increased checkbox-label margin from 8px to 12px for better spacing. Changed label layout from float positioning to flexbox (`inline-flex`) to keep help icon directly adjacent to label text. Added inline description hiding when tooltip is shown.
### Technical Details
**Root Cause - Table Borders**: WooCommerce's core admin CSS has higher specificity border rules that override simple `border: none` declarations. The solution required:
- Adding `!important` to all `border: none` rules targeting tables, th, td, thead, tbody, and tr elements
- Adding `border-collapse: collapse !important` to prevent cell borders from being visible
- Comprehensive targeting of all table structural elements for complete border removal
**Root Cause - Help Icon Position**: WooCommerce's default `.woocommerce-help-tip` styling uses `float: right` which positions the icon at the container's right edge. The fix:
- Removed float positioning with `float: none`
- Changed to `display: inline-block` with `vertical-align: middle`
- Wrapped label and help-tip in flexbox container (`display: inline-flex; align-items: center`)
- Controlled spacing with precise margins (checkbox: 12px right, help-tip: 6px left)
### Changed Files
- `assets/css/admin.css` - Added `!important` flags to all border removal rules; added `border-collapse: collapse`; increased checkbox margin to 12px; converted label layout to flexbox; positioned help-tip with inline-block; added inline description hiding
## [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

547
CLAUDE.md
View File

@@ -1,7 +1,7 @@
# WooCommerce Tier and Package Prices - AI Context Document
**Last Updated:** 2025-12-23
**Current Version:** 1.1.22
**Last Updated:** 2025-12-30
**Current Version:** 1.2.9
**Author:** Marco Graetsch
**Project Status:** Production-ready WordPress plugin
@@ -13,6 +13,7 @@ This is a WooCommerce plugin that adds flexible pricing capabilities to products
2. **Package Pricing (Fixed Bundles)**: Exact quantity packages at fixed prices (e.g., exactly 10 items for $95, exactly 25 for $200)
### Key Fact: 100% AI-Generated
This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance.
## Technical Stack
@@ -27,6 +28,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
- **Internationalization:** WordPress i18n (.pot/.po/.mo files)
### Dependencies
```json
{
"twig/twig": "^3.0",
@@ -38,7 +40,8 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
## Architecture
### Directory Structure
```
```txt
wc-tier-and-package-prices/
├── wc-tier-and-package-prices.php # Main plugin file (entry point)
├── includes/ # PHP classes
@@ -172,6 +175,7 @@ wc-tier-and-package-prices/
### Price Calculation Logic
**Package Pricing** (exact match):
```php
// In cart: if quantity == 10 and package exists for 10, use package price
if ($quantity == $package['qty']) {
@@ -181,6 +185,7 @@ if ($quantity == $package['qty']) {
```
**Tier Pricing** (range-based):
```php
// In cart: if quantity >= 10, use tier price for quantities 10+
foreach ($tiers as $tier) {
@@ -226,7 +231,9 @@ This metadata is used by display filters to show "(Package price)" or "(Volume d
## Common Patterns & Conventions
### Class Instantiation Pattern
All classes auto-instantiate at the end of their file:
```php
if (!class_exists('WC_TPP_Frontend')) {
class WC_TPP_Frontend {
@@ -247,7 +254,9 @@ new WC_TPP_Frontend(); // Auto-instantiate
- Direct file access prevention via `ABSPATH` check
### Translation Ready
All user-facing strings use:
```php
__('Text to translate', 'wc-tier-package-prices')
_e('Text to translate', 'wc-tier-package-prices')
@@ -270,6 +279,7 @@ Note: Swiss locales use CHF currency formatting in examples (e.g., "CHF 50.-")
## Known Issues & Historical Context
### Settings Page Duplication Saga (v1.1.15-1.1.19)
Multiple versions attempted to fix settings page appearing twice:
- **Root cause:** Settings file auto-instantiation + Composer autoloader
@@ -277,6 +287,7 @@ Multiple versions attempted to fix settings page appearing twice:
- **Prevention:** Singleton pattern + duplicate detection in array
### Class Redeclaration Issues (v1.1.8-1.1.14)
Plugin was completely non-functional:
- **Cause:** Incorrect initialization pattern without `class_exists()` guards
@@ -284,7 +295,8 @@ Plugin was completely non-functional:
- **Lesson:** Always wrap class declarations in `class_exists()` checks
### WooCommerce Blocks Fatal Error (v1.1.19 → v1.1.20)
```
```txt
Fatal error: Cannot use object of type WC_Product_Simple as array
Location: includes/class-wc-tpp-cart.php:233
```
@@ -293,9 +305,45 @@ Location: includes/class-wc-tpp-cart.php:233
- **Fix:** Changed method signature to accept `WC_Product $product` instead of `$cart_item` array
- **Status:** FIXED in v1.1.20
### CSS Specificity Issues (v1.2.3 → v1.2.4)
**Problem:** Admin table borders still visible despite `border: none` declarations in v1.2.3
```txt
Issue: WooCommerce's core admin CSS has higher specificity border rules
Location: assets/css/admin.css
Symptom: Tables still showing borders in product edit screens
```
- **Root Cause:** WooCommerce's default admin CSS uses highly specific selectors that override simple `border: none` declarations
- **Failed Approach (v1.2.3):** Adding `border: none` to table elements without `!important`
- **Successful Fix (v1.2.4):**
- Added `!important` flags to ALL border removal rules
- Added `border-collapse: collapse !important` to force borderless styling
- Targeted all table structural elements: `table`, `th`, `td`, `thead`, `tbody`, `tr`
- **Lesson:** When overriding WooCommerce core CSS, `!important` is often necessary due to high specificity in core styles
**Problem:** Help icon positioned at right edge instead of next to label text
```txt
Issue: WooCommerce help-tip uses float: right positioning
Location: assets/css/admin.css (checkbox/label layout)
Symptom: Help icon appearing far from label text at container edge
```
- **Root Cause:** WooCommerce's default `.woocommerce-help-tip` styling uses `float: right`
- **Failed Approach (v1.2.3):** Simple margin adjustments without changing positioning model
- **Successful Fix (v1.2.4):**
- Removed float positioning: `float: none`
- Changed to `display: inline-block` with `vertical-align: middle`
- Wrapped label and help-tip in flexbox container: `display: inline-flex; align-items: center`
- Controlled precise spacing with margins (checkbox: 12px, help-tip: 6px)
- **Lesson:** Overriding float-based layouts often requires changing to flexbox for proper control
## Release Process
### Version Bumping
Update version in 3 places:
1. `wc-tier-and-package-prices.php` - Plugin header comment (line 7)
@@ -303,22 +351,112 @@ Update version in 3 places:
3. `composer.json` - version field (optional, not critical)
### Creating Release Package
```bash
# From project root
cd releases
# Create zip excluding dev files
zip -r wc-tier-and-package-prices-X.X.X.zip .. \
-x '*.git*' '*.log' '.claude/*' 'CLAUDE.md' 'releases/*' 'node_modules/*' \
'.DS_Store' 'Thumbs.db' '.vscode/*' '.idea/*' '*.sublime-*' \
'notes.*' 'logs/*' 'templates/cache/*' 'composer.lock'
**CRITICAL:** The zip command must be run from the **parent directory** of the plugin folder to create proper archive structure.
```bash
# From parent directory (/home/magdev/workspaces/php)
cd /home/magdev/workspaces/php
# 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
cd releases
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
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
@@ -375,20 +513,289 @@ When making changes, test these critical paths:
## 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
#### CSS Specificity and WooCommerce Overrides
**CRITICAL LESSON from v1.2.4:** WooCommerce's core admin CSS uses high-specificity selectors that require `!important` to override.
**Problem Symptoms:**
- CSS rules not applying despite correct selectors
- Styles work in simple cases but fail with WooCommerce elements
- Browser DevTools shows rule crossed out or overridden
**Diagnostic Steps:**
1. Inspect element in browser DevTools
2. Check "Computed" tab to see which styles are actually applied
3. Look for crossed-out rules in "Styles" tab (indicates override)
4. Check selector specificity - WooCommerce often uses complex selectors
**Solution Patterns:**
**For Table Styling:**
```css
/* ❌ This will likely be overridden */
.wc-tpp-tiers-table {
border: none;
}
/* ✅ Use !important for core WooCommerce overrides */
.wc-tpp-tiers-table,
.wc-tpp-tiers-table th,
.wc-tpp-tiers-table td,
.wc-tpp-tiers-table thead,
.wc-tpp-tiers-table tbody,
.wc-tpp-tiers-table tr {
border: none !important;
}
/* ✅ Also use border-collapse to prevent cell borders */
.wc-tpp-tiers-table {
border-collapse: collapse !important;
}
```
**For Float-Based Layouts:**
```css
/* ❌ Float positioning is hard to control precisely */
.woocommerce-help-tip {
float: right;
margin-left: 10px;
}
/* ✅ Use flexbox for precise control */
label[for="_wc_tpp_restrict_to_packages"] {
display: inline-flex;
align-items: center;
gap: 0;
}
.woocommerce-help-tip {
float: none;
display: inline-block;
vertical-align: middle;
margin-left: 6px;
margin-right: 0;
}
```
**General Rules:**
1. **Always test in actual WordPress admin** - browser preview may not show WooCommerce's CSS
2. **Target all related elements** - tables require styling on `table`, `thead`, `tbody`, `tr`, `th`, `td`
3. **Use `!important` sparingly but don't fear it** - sometimes it's the only way to override WooCommerce core
4. **Prefer flexbox over floats** - gives better control over alignment and spacing
5. **Check across browsers** - table rendering can vary between Chrome/Firefox/Safari
**When Styles Don't Apply:**
- First verify selector is correct (DevTools should show rule, even if crossed out)
- If selector is correct but crossed out, increase specificity or add `!important`
- If selector doesn't appear at all, check file is enqueued and cache is cleared
- Use browser's "Inspect" right-click to see exact element structure
#### 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, v1.2.2, and v1.2.3 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.)
### Release Package Creation - Critical Notes
**IMPORTANT:** The zip command must be run from the **parent directory** to create proper archive structure.
**Correct command (from `/home/magdev/workspaces/php`):**
```bash
cd /home/magdev/workspaces/php
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
-x '*/\.git/*' '*/.git/*' 'wc-tier-and-package-prices/.git/*' \
'*.gitignore' '*.log' '*/.claude/*' '*/CLAUDE.md' \
'*/releases/*' '*/wordpress/*' '*/node_modules/*' \
'*/.DS_Store' '*/Thumbs.db' '*/.vscode/*' '*/.idea/*' \
'*.sublime-*' '*/notes.*' '*/logs/*' '*/templates/cache/*' \
'*/composer.lock'
```
**Critical Exclusions:**
- `*/wordpress/*` and `*/core/*` - MUST be excluded! The project has a symlink to WordPress installation that zip will follow, creating 129MB+ packages instead of ~430KB
- `.git/*` - All git metadata (multiple patterns needed for reliability)
- `.claude/*` and `CLAUDE.md` - Development documentation
- `releases/*` - Prevents including previous releases in new ones
- `composer.lock` - Not needed in production (vendor/ is included)
**Expected Package Size:** ~430-431KB (383 files)
**Package Size Alert:** If >1MB, exclusions failed (likely wordpress symlink included)
**Verification Steps:**
```bash
# 1. Check size (should be ~430KB)
ls -lh releases/wc-tier-and-package-prices-X.X.X.zip
# 2. Verify file count (should be 383 files)
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | tail -1
# 3. Check for excluded files
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | grep -E "CLAUDE\.md|\.claude/|\.git/|wordpress/"
# Should return nothing (exit code 1)
# 4. Verify version in package
unzip -p releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/wc-tier-and-package-prices.php | head -30 | grep -E "Version:|WC_TPP_VERSION"
```
### Future Features and Roadmap
The is a hierarchical list for upcoming features and can be considered as a
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**
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
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.
##### Bugfixes (Completed in v1.2.4)
1. ~~Bug 1 in v1.2.3 is not fixed. Now both table display have border again. they shouldn't have border.~~**FIXED in v1.2.4** - Added `!important` flags and `border-collapse: collapse` to table CSS to override WooCommerce's default table styling. Added comprehensive border removal for all table elements (table, thead, tbody, tr, th, td) to ensure truly borderless tables across all browsers.
2. ~~Bug 2 in v1.2.3: Increase the margin between checkbox and label and put the help icon right next to the label, not at the right border~~**FIXED in v1.2.4** - Increased checkbox right margin from 8px to 12px. Repositioned help tip icon to display inline right next to the label text using flexbox layout with `display: inline-flex`, removing float positioning that caused it to appear at the right edge.
##### Enhancements (Completed in v1.2.5)
1. ~~Hide the table-headers in admin area until a tier or respectivly a package price is defined.~~**COMPLETED in v1.2.5** - Added CSS `:has()` pseudo-class selectors to automatically hide table headers when tbody is empty. Creates a cleaner interface showing only the helpful empty state message and "Add" button when no pricing rules are configured.
2. ~~Make it possible to define tier or package prices on variable products in the parent product as a default for that product and all variants of it unless a variant has its own tier or package prices.~~**COMPLETED in v1.2.5** - Implemented parent product default pricing with automatic fallback. Variable products can define tier/package pricing once at parent level; variations inherit these defaults unless they have their own specific pricing. Added helper methods in cart class and updated all pricing/restriction checks to support parent fallback.
##### Bugfixes (Completed in v1.2.6 and v1.2.7)
1. ~~Table headers in admin are still visible when empty.~~**FIXED in v1.2.7** - The CSS `:has()` pseudo-class approach from v1.2.5/v1.2.6 wasn't working reliably across all browsers. Implemented JavaScript-based solution that adds/removes `has-rows` class on tables. Headers now hide by default (CSS) and show only when table has rows (JavaScript toggles class). Function `updateTableHeaders()` is called on page load and after all add/remove row operations.
2. ~~Parent product pricing forms not visible in admin.~~**FIXED in v1.2.6 and v1.2.7** - The backend fallback logic from v1.2.5 was implemented but the admin UI to configure it was missing. Added `add_variable_parent_pricing_fields()` method that displays pricing forms for variable product parents. Fixed hook issue in v1.2.7: changed from `woocommerce_product_options_pricing` (only fires for simple products) to `woocommerce_product_options_general_product_data` (fires for all product types). Variable product parents now have a "Default Tier & Package Pricing for All Variations" section where defaults can be configured.
##### Translation Updates (Completed in v1.2.7)
1.**COMPLETED** - Updated all translation files (.pot, .po, .mo) with new strings from v1.2.6 and v1.2.7 for variable product parent pricing features. All 7 language variants updated with translations for "Default Tier & Package Pricing for All Variations" and related strings.
##### Bugfixes (Completed in v1.2.8)
1. ~~Add a Suffix with the current configured default currency to the table-header and form placeholder. Use the common currency notation in placeholder~~**FIXED in v1.2.8** - Updated all table headers in admin to display "Price (€)" format using `printf(__('Price (%s)'), get_woocommerce_currency_symbol())`. Modified all template render methods (tier_row, package_row, variation_tier_row, variation_package_row) to pass currency_symbol to Twig templates. Updated admin/tier-row.twig and admin/package-row.twig to concatenate currency symbol in price input placeholders (e.g., "e.g., 9.99 €"). Applied to simple products, variable parent products, and all variations.
2. ~~Already stored tier and package prices on the children of a variable product are still available after deletion. Looks like the storage mechanism has an error. This occurs only on the child product, not on the parent product.~~**FIXED in v1.2.8** - Fixed save logic in both `save_tier_package_fields()` and `save_variation_pricing_fields()` methods. Root cause: Empty arrays were being saved via `update_post_meta()` instead of being deleted. Changed logic from "save on isset, delete otherwise" to "filter entries, then save if not empty, delete if empty". Added `if (!empty($tiers))` and `if (!empty($packages))` checks before calling `update_post_meta()`. Now properly calls `delete_post_meta()` when all pricing entries are removed, preventing empty arrays from persisting in database.
##### Bugfixes (Completed in v1.2.9)
1. ~~The Price header in admin tables while configuring tier and package prices is not translated. Also the placeholder on the form elements for prices has the wrong encoding, the special characters on the placeholder are show in html-entity encoding.~~**FIXED in v1.2.9** - Fixed translation function placement in printf statements by changing from `printf(__('Price (%s)', ...), ...)` to `printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), ...)`. This ensures the text domain is passed correctly to the translation function while maintaining the currency placeholder functionality. Also removed the translation filter from concatenated placeholder strings in Twig templates (changed from `{{ ('e.g., 9.99 ' ~ currency_symbol)|__('wc-tier-package-prices') }}` to `{{ 'e.g., 9.99 ' ~ currency_symbol }}`) because example values should not be translated and the filter was causing HTML entity encoding of special characters.
2. ~~The tier and package prices for children of a variable product are still not deletable. After storing the product, the previously deleted rows are back again.~~**FIXED in v1.2.9** - Despite the fix in v1.2.8, edge cases remained due to conditional branching structure. Refactored `save_variation_pricing_fields()` with more defensive logic: initialize arrays at the start ($tiers = array()), populate only if valid POST data exists (with added is_array() check), then unconditionally perform either update_post_meta() (if !empty) or delete_post_meta() (if empty). This guarantees proper cleanup regardless of POST data structure and eliminates the if/else branching that could miss edge cases.
##### Planned Enhancements for v1.2.10+
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
@@ -404,6 +811,112 @@ Roadmap for the upcoming development.
- Always check filter/action documentation for parameter types
- Don't assume cart item arrays everywhere - sometimes it's product objects!
#### CRITICAL: Product Type-Specific Hooks (Learned in v1.2.6/v1.2.7)
WooCommerce has different hooks for different product types in the admin product edit page:
- `woocommerce_product_options_pricing` - **ONLY fires for simple products**, NOT variable products
- `woocommerce_product_options_general_product_data` - Fires for ALL product types after the general tab
- `woocommerce_variation_options_pricing` - Fires for individual variations within variable products
**Lesson:** When adding admin UI for variable product parents, use `woocommerce_product_options_general_product_data` and check `$product->is_type('variable')` to conditionally display. Using `woocommerce_product_options_pricing` will cause forms to never appear for variable products (as discovered in v1.2.6 → v1.2.7 fix).
#### CRITICAL: Currency Symbol Display (Learned in v1.2.8)
When displaying currency symbols in admin interface table headers and input placeholders:
**Table Headers:**
```php
// ✅ Correct - Use printf with translation and WooCommerce currency function
<th><?php printf(__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></th>
// ❌ Wrong - Hard-coded or missing currency
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
<th><?php _e('Price (€)', 'wc-tier-package-prices'); ?></th>
```
**Twig Template Placeholders:**
```twig
{# ✅ Correct - Pass currency_symbol from PHP and concatenate in template #}
placeholder="{{ ('e.g., 9.99 ' ~ currency_symbol)|__('wc-tier-package-prices') }}"
{# ❌ Wrong - Hard-coded or missing currency #}
placeholder="{{ 'e.g., 9.99'|__('wc-tier-package-prices') }}"
placeholder="{{ 'e.g., 9.99 €'|__('wc-tier-package-prices') }}"
```
**Implementation Pattern:**
1. In PHP render methods, pass currency symbol to Twig: `'currency_symbol' => get_woocommerce_currency_symbol()`
2. In Twig templates, concatenate using `~` operator: `'text ' ~ currency_symbol`
3. Always use WooCommerce's `get_woocommerce_currency_symbol()` - never hard-code currency symbols
**Affected Methods:** All template render methods must pass `currency_symbol`:
- `render_tier_row()`
- `render_package_row()`
- `render_variation_tier_row()`
- `render_variation_package_row()`
#### CRITICAL: Post Meta Deletion vs. Empty Arrays (Learned in v1.2.8)
When saving product meta data, WordPress distinguishes between "no data" (deleted meta) and "empty data" (empty array saved as meta):
**Problem Pattern:**
```php
// ❌ WRONG - Saves empty array when all entries removed
if (isset($_POST['_wc_tpp_tiers'])) {
$tiers = array();
foreach ($_POST['_wc_tpp_tiers'] as $tier) {
if (!empty($tier['min_qty']) && !empty($tier['price'])) {
$tiers[] = array(...);
}
}
update_post_meta($post_id, '_wc_tpp_tiers', $tiers); // Saves [] if no valid entries
} else {
delete_post_meta($post_id, '_wc_tpp_tiers');
}
```
**Correct Pattern:**
```php
// ✅ CORRECT - Deletes meta when no valid entries exist
if (isset($_POST['_wc_tpp_tiers'])) {
$tiers = array();
foreach ($_POST['_wc_tpp_tiers'] as $tier) {
if (!empty($tier['min_qty']) && !empty($tier['price'])) {
$tiers[] = array(...);
}
}
// Only save if we have valid entries, otherwise delete
if (!empty($tiers)) {
update_post_meta($post_id, '_wc_tpp_tiers', $tiers);
} else {
delete_post_meta($post_id, '_wc_tpp_tiers');
}
} else {
delete_post_meta($post_id, '_wc_tpp_tiers');
}
```
**Why This Matters:**
- Empty arrays `[]` saved via `update_post_meta()` persist in database as serialized empty arrays
- Frontend/cart code checking `if ($tiers)` will evaluate `[]` as falsy, but meta still exists in database
- Database queries like `get_post_meta()` return `[]` instead of `false`, causing subtle bugs
- Properly deleting meta keeps database clean and prevents "ghost" configurations
**Affected Methods in v1.2.8:**
- `save_tier_package_fields()` - Simple and variable parent products
- `save_variation_pricing_fields()` - Individual variations
**Rule:** Always check `if (!empty($array))` before calling `update_post_meta()` for array data. If empty, call `delete_post_meta()` instead.
### When Adding Features
- Follow the existing pattern: add setting → add UI → add logic → add template
@@ -422,7 +935,7 @@ Roadmap for the upcoming development.
## File Locations Quick Reference
| Task | File(s) |
|------|---------|
| ------ | --------- |
| Change version | `wc-tier-and-package-prices.php` (2 places) |
| Add global setting | `includes/class-wc-tpp-settings.php` |
| Modify product meta box | `includes/class-wc-tpp-product-meta.php` + `templates/admin/*.twig` |
@@ -462,7 +975,7 @@ Roadmap for the upcoming development.
## Support & Resources
- **Repository:** https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
- **Repository:** <https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices>
- **Documentation:** See `README.md`, `QUICKSTART.md`, `USAGE_EXAMPLES.md`, `INSTALLATION.md`
- **Changelog:** `CHANGELOG.md` (detailed version history)
- **Issue Tracking:** Check fatal-errors-*.log files for production errors

View File

@@ -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.
**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
@@ -183,9 +183,22 @@ This plugin is licensed under the GPL v2 or later.
## 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
- **CRITICAL:** WooCommerce Blocks fatal error in mini-cart and cart blocks

View File

@@ -23,42 +23,63 @@
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 !important;
border-collapse: collapse !important;
}
.wc-tpp-tiers-table th,
.wc-tpp-packages-table th,
.wc-tpp-tiers-table td,
.wc-tpp-packages-table td {
border: none !important;
}
.wc-tpp-tiers-table th {
font-weight: 600;
text-align: left;
}
.wc-tpp-packages-table th {
font-weight: 600;
text-align: left;
}
.wc-tpp-tiers-table thead,
.wc-tpp-packages-table thead,
.wc-tpp-tiers-table tbody,
.wc-tpp-packages-table tbody,
.wc-tpp-tiers-table tr,
.wc-tpp-packages-table tr {
border: none !important;
}
/* Table row styling - rows are now <tr> elements in a table */
.wc-tpp-tier-row,
.wc-tpp-package-row {
display: flex;
gap: 15px;
align-items: flex-end;
padding: 15px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 10px;
/* No special styling needed - standard table row */
}
.wc-tpp-tier-row .form-field,
.wc-tpp-package-row .form-field {
margin: 0;
flex: 1;
}
.wc-tpp-tier-row label,
.wc-tpp-package-row label {
display: block;
font-weight: 600;
margin-bottom: 5px;
.wc-tpp-tier-row td,
.wc-tpp-package-row td {
padding: 8px;
vertical-align: middle;
}
/* Ensure WooCommerce input classes work properly in table cells */
.wc-tpp-tier-row input,
.wc-tpp-package-row input {
width: 100%;
margin: 0;
}
.wc-tpp-remove-tier,
.wc-tpp-remove-package {
flex-shrink: 0;
color: #b32d2e;
border-color: #b32d2e;
margin-bottom: 0;
}
.wc-tpp-remove-tier:hover,
@@ -89,3 +110,47 @@
color: #666;
font-style: italic;
}
/* Hide table headers when there are no pricing rules */
/* Default: hide headers initially, JavaScript will show them when rows are added */
.wc-tpp-tiers-table thead,
.wc-tpp-packages-table thead {
display: none;
}
/* Show headers when table has pricing rows */
.wc-tpp-tiers-table.has-rows thead,
.wc-tpp-packages-table.has-rows thead {
display: table-header-group !important;
}
/* Checkbox styling improvements */
#_wc_tpp_restrict_to_packages,
input[id^="wc_tpp_restrict_to_packages_"] {
margin-right: 12px !important;
}
/* Position help tip icon right next to the label text */
.wc-tpp-tier-pricing .woocommerce-help-tip,
.wc-tpp-package-pricing .woocommerce-help-tip,
.wc-tpp-variation-pricing .woocommerce-help-tip {
margin-left: 6px;
margin-right: 0;
float: none;
display: inline-block;
vertical-align: middle;
}
/* Fix WooCommerce checkbox label layout for our checkboxes */
label[for="_wc_tpp_restrict_to_packages"],
label[for^="wc_tpp_restrict_to_packages_"] {
display: inline-flex;
align-items: center;
gap: 0;
}
/* Hide inline description text when tooltip is shown */
#_wc_tpp_restrict_to_packages + .description,
input[id^="wc_tpp_restrict_to_packages_"] + .description {
display: none;
}

View File

@@ -6,44 +6,141 @@
'use strict';
$(document).ready(function() {
let tierIndex = $('.wc-tpp-tier-row').length;
let packageIndex = $('.wc-tpp-package-row').length;
// Initialize indexes for simple products
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) {
// Function to update table header visibility
function updateTableHeaders() {
// Check all tier tables
$('.wc-tpp-tiers-table').each(function() {
const $table = $(this);
const $tbody = $table.find('.wc-tpp-tiers-container');
const hasRows = $tbody.find('tr').length > 0;
$table.toggleClass('has-rows', hasRows);
});
// Check all package tables
$('.wc-tpp-packages-table').each(function() {
const $table = $(this);
const $tbody = $table.find('.wc-tpp-packages-container');
const hasRows = $tbody.find('tr').length > 0;
$table.toggleClass('has-rows', hasRows);
});
}
// Initialize table headers on page load
updateTableHeaders();
// ========================================
// Simple Product Handlers
// ========================================
// Add tier (simple products)
$('.wc-tpp-tier-pricing .wc-tpp-add-tier').on('click', function(e) {
e.preventDefault();
const template = $('#wc-tpp-tier-row-template').html();
const newRow = template.replace(/\{\{INDEX\}\}/g, tierIndex);
$('.wc-tpp-tiers-container').append(newRow);
$('.wc-tpp-tier-pricing .wc-tpp-tiers-container').append(newRow);
tierIndex++;
updateTableHeaders();
});
// 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++;
updateTableHeaders();
});
// ========================================
// Variable Product Parent Handlers
// ========================================
// Add tier (variable product parent default pricing)
$('.wc-tpp-variable-parent-pricing .wc-tpp-add-tier').on('click', function(e) {
e.preventDefault();
const template = $('#wc-tpp-tier-row-template').html();
const newRow = template.replace(/\{\{INDEX\}\}/g, tierIndex);
$('.wc-tpp-variable-parent-pricing .wc-tpp-tiers-container').append(newRow);
tierIndex++;
updateTableHeaders();
});
// Add package (variable product parent default pricing)
$('.wc-tpp-variable-parent-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-variable-parent-pricing .wc-tpp-packages-container').append(newRow);
packageIndex++;
updateTableHeaders();
});
// ========================================
// 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);
updateTableHeaders();
});
// 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);
updateTableHeaders();
});
// ========================================
// Common Handlers (both simple and variations)
// ========================================
// Remove tier
$(document).on('click', '.wc-tpp-remove-tier', function(e) {
e.preventDefault();
if (confirm('Are you sure you want to remove this tier?')) {
$(this).closest('.wc-tpp-tier-row').remove();
updateTableHeaders();
}
});
// 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
$(document).on('click', '.wc-tpp-remove-package', function(e) {
e.preventDefault();
if (confirm('Are you sure you want to remove this package?')) {
$(this).closest('.wc-tpp-package-row').remove();
updateTableHeaders();
}
});
// Validate inputs
// Validate quantity inputs
$(document).on('input', 'input[name*="[min_qty]"], input[name*="[qty]"]', function() {
const value = parseInt($(this).val());
if (value < 1) {
@@ -51,6 +148,7 @@
}
});
// Validate price inputs
$(document).on('input', 'input[name*="[price]"]', function() {
const value = parseFloat($(this).val());
if (value < 0) {

View File

@@ -244,6 +244,114 @@
if ($quantityInput.length > 0 && $addToCartButton.length > 0) {
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);

View File

@@ -1,7 +1,7 @@
{
"name": "magdev/wc-tier-package-prices",
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
"version": "1.1.22",
"version": "1.2.9",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"authors": [

View File

@@ -40,6 +40,8 @@ if (!class_exists('WC_TPP_Cart')) {
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
$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'];
$product = $cart_item['data'];
@@ -48,10 +50,10 @@ if (!class_exists('WC_TPP_Cart')) {
continue;
}
// Check for exact package match first
// Check for exact package match first (pass product_id for parent fallback support)
$package_price = null;
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($product_id, $quantity, $variation_id);
}
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_total_price'] = $package_price;
} else {
// Apply tier pricing if no package match
// Apply tier pricing if no package match (pass product_id for parent fallback support)
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($product_id, $quantity, $variation_id);
if ($tier_price !== null) {
$product->set_price($tier_price);
// Store pricing information in cart item for display
@@ -99,18 +101,18 @@ if (!class_exists('WC_TPP_Cart')) {
}
public function validate_package_quantity($passed, $product_id, $quantity) {
// Check if restriction is enabled globally or for this product
$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';
// Check for variation ID in request (for variable products)
$variation_id = isset($_REQUEST['variation_id']) ? absint($_REQUEST['variation_id']) : 0;
if (!$global_restrict && !$product_restrict) {
// Check if restriction is enabled (with parent fallback for variations)
if (!$this->is_restriction_enabled($product_id, $variation_id)) {
return $passed;
}
// Get packages for this product
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
// Get packages for this product/variation (with parent fallback)
$packages = $this->get_packages_with_fallback($product_id, $variation_id);
if (empty($packages) || !is_array($packages)) {
if (!$packages) {
return $passed;
}
@@ -147,18 +149,13 @@ if (!class_exists('WC_TPP_Cart')) {
public function maybe_hide_cart_quantity_input($product_quantity, $cart_item_key, $cart_item) {
$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
$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';
// Get packages for this product
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
// If restriction is enabled and packages exist, show quantity as text only
if (($global_restrict || $product_restrict) && !empty($packages)) {
// Check if restriction is enabled (with parent fallback) and packages exist
if ($this->is_restriction_enabled($product_id, $variation_id) && $this->get_packages_with_fallback($product_id, $variation_id)) {
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']
);
}
@@ -168,18 +165,13 @@ if (!class_exists('WC_TPP_Cart')) {
public function maybe_hide_mini_cart_quantity_input($product_quantity, $cart_item, $cart_item_key) {
$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
$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';
// Get packages for this product
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
// If restriction is enabled and packages exist, show quantity as text only
if (($global_restrict || $product_restrict) && !empty($packages)) {
// Check if restriction is enabled (with parent fallback) and packages exist
if ($this->is_restriction_enabled($product_id, $variation_id) && $this->get_packages_with_fallback($product_id, $variation_id)) {
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s &times;</span>',
$product_id,
$effective_id,
$cart_item['quantity']
);
}
@@ -196,12 +188,12 @@ if (!class_exists('WC_TPP_Cart')) {
$restricted_products = array();
foreach (WC()->cart->get_cart() as $cart_item) {
$product_id = $cart_item['product_id'];
$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';
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
if (($global_restrict || $product_restrict) && !empty($packages)) {
$restricted_products[] = $product_id;
// Check if restriction is enabled (with parent fallback) and packages exist
if ($this->is_restriction_enabled($product_id, $variation_id) && $this->get_packages_with_fallback($product_id, $variation_id)) {
$restricted_products[] = $effective_id;
}
}
@@ -226,7 +218,7 @@ if (!class_exists('WC_TPP_Cart')) {
* Make quantity non-editable for restricted products in WooCommerce blocks
*
* @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
*/
public function block_quantity_editable($editable, $product) {
@@ -241,17 +233,61 @@ if (!class_exists('WC_TPP_Cart')) {
return $editable;
}
$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';
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
// For variations, get parent product ID and variation ID
$variation_id = 0;
$parent_id = $product_id;
// If restriction is enabled and packages exist, make quantity non-editable
if (($global_restrict || $product_restrict) && !empty($packages)) {
if ($product->is_type('variation')) {
$variation_id = $product_id;
$parent_id = $product->get_parent_id();
}
// Check if restriction is enabled (with parent fallback) and packages exist
if ($this->is_restriction_enabled($parent_id, $variation_id) && $this->get_packages_with_fallback($parent_id, $variation_id)) {
return false;
}
return $editable;
}
/**
* Get packages with parent fallback for variations
*
* @param int $product_id Parent product ID
* @param int $variation_id Variation ID (0 for simple products)
* @return array|false Packages array or false if none found
*/
private function get_packages_with_fallback($product_id, $variation_id = 0) {
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
// Fall back to parent pricing if variation doesn't have its own pricing
if ((empty($packages) || !is_array($packages)) && $variation_id > 0) {
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
}
return (!empty($packages) && is_array($packages)) ? $packages : false;
}
/**
* Check if restriction is enabled for a product/variation with parent fallback
*
* @param int $product_id Parent product ID
* @param int $variation_id Variation ID (0 for simple products)
* @return bool Whether restriction is enabled
*/
private function is_restriction_enabled($product_id, $variation_id = 0) {
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
// Fall back to parent restriction setting if variation doesn't have its own
if (!$product_restrict && $variation_id > 0) {
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
}
return $global_restrict || $product_restrict;
}
}
new WC_TPP_Cart();

View File

@@ -19,6 +19,10 @@ if (!class_exists('WC_TPP_Frontend')) {
// 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);
// 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() {
@@ -31,8 +35,10 @@ if (!class_exists('WC_TPP_Frontend')) {
if (is_product()) {
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(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wc_tpp_variation_pricing'),
'currency_symbol' => esc_js(get_woocommerce_currency_symbol()),
'currency_position' => esc_js(get_option('woocommerce_currency_pos', 'left')),
'price_decimals' => absint(wc_get_price_decimals()),
@@ -67,6 +73,11 @@ if (!class_exists('WC_TPP_Frontend')) {
return;
}
// For variable products, quantity hiding is handled per-variation via JS
if ($product->is_type('variable')) {
return;
}
$product_id = $product->get_id();
$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';
@@ -85,6 +96,13 @@ if (!class_exists('WC_TPP_Frontend')) {
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();
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
@@ -103,8 +121,22 @@ if (!class_exists('WC_TPP_Frontend')) {
));
}
public static function get_tier_price($product_id, $quantity) {
/**
* 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);
// Fall back to parent pricing if variation doesn't have its own pricing
if ((empty($tiers) || !is_array($tiers)) && $variation_id > 0) {
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
}
if (empty($tiers) || !is_array($tiers)) {
return null;
@@ -120,8 +152,22 @@ if (!class_exists('WC_TPP_Frontend')) {
return $applicable_price;
}
public static function get_package_price($product_id, $quantity) {
/**
* 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);
// Fall back to parent pricing if variation doesn't have its own pricing
if ((empty($packages) || !is_array($packages)) && $variation_id > 0) {
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
}
if (empty($packages) || !is_array($packages)) {
return null;
@@ -164,8 +210,24 @@ if (!class_exists('WC_TPP_Frontend')) {
$product_id = $product->get_id();
// Check if product has quantity restrictions
if (!self::has_quantity_restriction($product_id)) {
// For variable products, check if ANY variation has restrictions
// 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;
}
@@ -173,15 +235,72 @@ if (!class_exists('WC_TPP_Frontend')) {
$product_url = esc_url($product->get_permalink());
$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(
'<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,
esc_attr($product_type_class),
esc_attr(sprintf(__('View options for %s', 'wc-tier-package-prices'), $product->get_name())),
$button_text
);
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();

View File

@@ -11,21 +11,56 @@ if (!class_exists('WC_TPP_Product_Meta')) {
class WC_TPP_Product_Meta {
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_package_pricing_fields'));
add_action('woocommerce_process_product_meta', array($this, 'save_tier_package_fields'));
// Variable product parent hooks (for default pricing)
// Use product_options_general_product_data which shows for all product types after the general tab
add_action('woocommerce_product_options_general_product_data', array($this, 'add_variable_parent_pricing_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() {
/**
* Add tier and package pricing fields for variable product parents
* These serve as defaults for all variations unless overridden
*/
public function add_variable_parent_pricing_fields() {
global $post;
// Only show for variable products, not simple products
$product = wc_get_product($post->ID);
if (!$product || !$product->is_type('variable')) {
return;
}
?>
<div class="options_group wc-tpp-tier-pricing">
<div class="options_group wc-tpp-variable-parent-pricing">
<p class="form-field">
<label><?php _e('Tier Pricing', 'wc-tier-package-prices'); ?></label>
<span class="description"><?php _e('Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'); ?></span>
<strong><?php _e('Default Tier & Package Pricing for All Variations', 'wc-tier-package-prices'); ?></strong>
<span class="woocommerce-help-tip" data-tip="<?php esc_attr_e('Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing.', 'wc-tier-package-prices'); ?>"></span>
</p>
<p class="description" style="margin-left: 12px; margin-bottom: 15px;">
<?php _e('Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing.', 'wc-tier-package-prices'); ?>
</p>
<div class="wc-tpp-tiers-container">
<!-- Tier Pricing Section -->
<div class="wc-tpp-parent-tier-pricing" style="margin-left: 12px;">
<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 printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></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)) {
@@ -36,25 +71,26 @@ if (!class_exists('WC_TPP_Product_Meta')) {
$this->render_tier_row($index, $tier);
}
?>
</div>
</tbody>
</table>
<p class="form-field">
<button type="button" class="button wc-tpp-add-tier"><?php _e('Add Tier', 'wc-tier-package-prices'); ?></button>
</p>
</div>
<?php
}
public function add_package_pricing_fields() {
global $post;
?>
<div class="options_group wc-tpp-package-pricing">
<p class="form-field">
<label><?php _e('Package Pricing', 'wc-tier-package-prices'); ?></label>
<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>
<div class="wc-tpp-packages-container">
<!-- Package Pricing Section -->
<div class="wc-tpp-parent-package-pricing" style="margin-left: 12px; margin-top: 20px;">
<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 printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></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)) {
@@ -65,8 +101,119 @@ if (!class_exists('WC_TPP_Product_Meta')) {
$this->render_package_row($index, $package);
}
?>
</tbody>
</table>
<p class="form-field">
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
</p>
<?php
woocommerce_wp_checkbox(array(
'id' => '_wc_tpp_restrict_to_packages',
'label' => __('Restrict to Package Quantities (Default)', 'wc-tier-package-prices'),
'description' => __('Default restriction setting for all variations. Only allow quantities defined in packages above.', 'wc-tier-package-prices'),
'desc_tip' => true,
));
?>
</div>
<!-- Templates for JavaScript (shared between simple and variable parent) -->
<script type="text/html" id="wc-tpp-tier-row-template">
<?php $this->render_tier_row('{{INDEX}}', array('min_qty' => '', 'price' => '', 'label' => '')); ?>
</script>
<script type="text/html" id="wc-tpp-package-row-template">
<?php $this->render_package_row('{{INDEX}}', array('qty' => '', 'price' => '', 'label' => '')); ?>
</script>
</div>
<?php
}
public function add_tier_pricing_fields() {
global $post;
// Only show for simple products (variable products use add_variable_parent_pricing_fields)
$product = wc_get_product($post->ID);
if ($product && $product->is_type('variable')) {
return;
}
?>
<div class="options_group wc-tpp-tier-pricing">
<p class="form-field">
<label><?php _e('Tier Pricing', 'wc-tier-package-prices'); ?></label>
<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>
<table class="widefat wc-tpp-tiers-table">
<thead>
<tr>
<th><?php _e('Min Quantity', 'wc-tier-package-prices'); ?></th>
<th><?php printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></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) {
$this->render_tier_row($index, $tier);
}
?>
</tbody>
</table>
<p class="form-field">
<button type="button" class="button wc-tpp-add-tier"><?php _e('Add Tier', 'wc-tier-package-prices'); ?></button>
</p>
</div>
<?php
}
public function add_package_pricing_fields() {
global $post;
// Only show for simple products (variable products use add_variable_parent_pricing_fields)
$product = wc_get_product($post->ID);
if ($product && $product->is_type('variable')) {
return;
}
?>
<div class="options_group wc-tpp-package-pricing">
<p class="form-field">
<label><?php _e('Package Pricing', 'wc-tier-package-prices'); ?></label>
<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>
<table class="widefat wc-tpp-packages-table">
<thead>
<tr>
<th><?php _e('Quantity', 'wc-tier-package-prices'); ?></th>
<th><?php printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></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) {
$this->render_package_row($index, $package);
}
?>
</tbody>
</table>
<p class="form-field">
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
</p>
@@ -94,14 +241,148 @@ if (!class_exists('WC_TPP_Product_Meta')) {
private function render_tier_row($index, $tier) {
WC_TPP_Template_Loader::get_instance()->display('admin/tier-row.twig', array(
'index' => $index,
'tier' => $tier
'tier' => $tier,
'currency_symbol' => get_woocommerce_currency_symbol()
));
}
private function render_package_row($index, $package) {
WC_TPP_Template_Loader::get_instance()->display('admin/package-row.twig', array(
'index' => $index,
'package' => $package
'package' => $package,
'currency_symbol' => get_woocommerce_currency_symbol()
));
}
/**
* 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 printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></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 printf(esc_html__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></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 . ']',
'currency_symbol' => get_woocommerce_currency_symbol()
));
}
/**
* 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 . ']',
'currency_symbol' => get_woocommerce_currency_symbol()
));
}
@@ -137,10 +418,15 @@ if (!class_exists('WC_TPP_Product_Meta')) {
usort($tiers, function($a, $b) {
return $a['min_qty'] - $b['min_qty'];
});
// Only save if we have valid tiers, otherwise delete
if (!empty($tiers)) {
update_post_meta($post_id, '_wc_tpp_tiers', $tiers);
} else {
delete_post_meta($post_id, '_wc_tpp_tiers');
}
} else {
delete_post_meta($post_id, '_wc_tpp_tiers');
}
// Save package pricing
if (isset($_POST['_wc_tpp_packages'])) {
@@ -158,15 +444,88 @@ if (!class_exists('WC_TPP_Product_Meta')) {
usort($packages, function($a, $b) {
return $a['qty'] - $b['qty'];
});
// Only save if we have valid packages, otherwise delete
if (!empty($packages)) {
update_post_meta($post_id, '_wc_tpp_packages', $packages);
} else {
delete_post_meta($post_id, '_wc_tpp_packages');
}
} else {
delete_post_meta($post_id, '_wc_tpp_packages');
}
// Save package quantity restriction setting
$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);
}
/**
* 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
$tiers = array();
if (isset($_POST['wc_tpp_tiers'][$loop]) && is_array($_POST['wc_tpp_tiers'][$loop])) {
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'];
});
}
// Always update or delete based on whether we have valid tiers
if (!empty($tiers)) {
update_post_meta($variation_id, '_wc_tpp_tiers', $tiers);
} else {
delete_post_meta($variation_id, '_wc_tpp_tiers');
}
// Save package pricing for this variation
$packages = array();
if (isset($_POST['wc_tpp_packages'][$loop]) && is_array($_POST['wc_tpp_packages'][$loop])) {
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'];
});
}
// Always update or delete based on whether we have valid packages
if (!empty($packages)) {
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();

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.21\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-23 00:00+0000\n"
"PO-Revision-Date: 2025-12-23 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Switzerland)\n"
"Language: de_CH\n"
@@ -152,6 +152,26 @@ msgstr "Paket hinzufügen"
msgid "Only allow quantities defined in packages above"
msgstr "Nur oben definierte Paketmengen zulassen"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Standard Staffel- & Paketpreise für alle Varianten"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Legen Sie Standardpreise für alle Varianten fest. Einzelne Varianten können diese Standardwerte mit ihren eigenen spezifischen Preisen überschreiben."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Konfigurieren Sie hier Standard-Staffel- und Paketpreise. Alle Varianten übernehmen diese Einstellungen, es sei denn, sie definieren ihre eigenen spezifischen Preise."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Auf Paketmengen beschränken (Standard)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Standard-Beschränkungseinstellung für alle Varianten. Nur oben definierte Paketmengen zulassen."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Mindestmenge"
@@ -240,6 +260,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wählen Sie aus den
msgid "View Options"
msgstr "Optionen ansehen"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "Optionen für %s ansehen"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.6\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-21 00:00+0000\n"
"PO-Revision-Date: 2025-12-21 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Switzerland)\n"
"Language: de_CH_informal\n"
@@ -152,6 +152,26 @@ msgstr "Paket hinzufügen"
msgid "Only allow quantities defined in packages above"
msgstr "Nur oben definierte Paketmengen zulassen"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Standard Staffel- & Paketpreise für alle Varianten"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Lege Standardpreise für alle Varianten fest. Einzelne Varianten können diese Standardwerte mit ihren eigenen spezifischen Preisen überschreiben."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Konfiguriere hier Standard-Staffel- und Paketpreise. Alle Varianten übernehmen diese Einstellungen, es sei denn, sie definieren ihre eigenen spezifischen Preise."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Auf Paketmengen beschränken (Standard)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Standard-Beschränkungseinstellung für alle Varianten. Nur oben definierte Paketmengen zulassen."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Mindestmenge"
@@ -240,6 +260,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wähle aus den verf
msgid "View Options"
msgstr "Optionen ansehen"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "Optionen für %s ansehen"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.6\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-21 00:00+0000\n"
"PO-Revision-Date: 2025-12-21 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -152,6 +152,26 @@ msgstr "Paket hinzufügen"
msgid "Only allow quantities defined in packages above"
msgstr "Nur oben definierte Paketmengen zulassen"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Standard Staffel- & Paketpreise für alle Varianten"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Legen Sie Standardpreise für alle Varianten fest. Einzelne Varianten können diese Standardwerte mit ihren eigenen spezifischen Preisen überschreiben."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Konfigurieren Sie hier Standard-Staffel- und Paketpreise. Alle Varianten übernehmen diese Einstellungen, es sei denn, sie definieren ihre eigenen spezifischen Preise."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Auf Paketmengen beschränken (Standard)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Standard-Beschränkungseinstellung für alle Varianten. Nur oben definierte Paketmengen zulassen."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Mindestmenge"
@@ -240,6 +260,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wählen Sie aus den
msgid "View Options"
msgstr "Optionen ansehen"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "Optionen für %s ansehen"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.21\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-23 00:00+0000\n"
"PO-Revision-Date: 2025-12-23 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Germany)\n"
"Language: de_DE_informal\n"
@@ -152,6 +152,26 @@ msgstr "Paket hinzufügen"
msgid "Only allow quantities defined in packages above"
msgstr "Nur oben definierte Paketmengen zulassen"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Standard Staffel- & Paketpreise für alle Varianten"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Lege Standardpreise für alle Varianten fest. Einzelne Varianten können diese Standardwerte mit ihren eigenen spezifischen Preisen überschreiben."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Konfiguriere hier Standard-Staffel- und Paketpreise. Alle Varianten übernehmen diese Einstellungen, es sei denn, sie definieren ihre eigenen spezifischen Preise."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Auf Paketmengen beschränken (Standard)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Standard-Beschränkungseinstellung für alle Varianten. Nur oben definierte Paketmengen zulassen."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Mindestmenge"
@@ -240,6 +260,20 @@ msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wähle aus den verf
msgid "View Options"
msgstr "Optionen ansehen"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "Optionen für %s ansehen"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.6\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-21 00:00+0000\n"
"PO-Revision-Date: 2025-12-21 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -152,6 +152,26 @@ msgstr "Add Package"
msgid "Only allow quantities defined in packages above"
msgstr "Only allow quantities defined in packages above"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Default Tier & Package Pricing for All Variations"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Restrict to Package Quantities (Default)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Default restriction setting for all variations. Only allow quantities defined in packages above."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Minimum Quantity"
@@ -240,6 +260,20 @@ msgstr "The quantity %1$d is not available for %2$s. Please choose from the avai
msgid "View Options"
msgstr "View Options"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "View options for %s"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.21\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-23 00:00+0000\n"
"PO-Revision-Date: 2025-12-23 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: French (Switzerland)\n"
"Language: fr_CH\n"
@@ -152,6 +152,26 @@ msgstr "Ajouter un forfait"
msgid "Only allow quantities defined in packages above"
msgstr "Autoriser uniquement les quantités définies dans les forfaits ci-dessus"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Tarification par paliers et forfaits par défaut pour toutes les variations"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Définir la tarification par défaut pour toutes les variations. Les variations individuelles peuvent remplacer ces valeurs par défaut par leur propre tarification spécifique."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Configurez ici la tarification par paliers et forfaits par défaut. Toutes les variations hériteront de ces paramètres sauf si elles définissent leur propre tarification spécifique."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Limiter aux quantités de forfaits (par défaut)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Paramètre de restriction par défaut pour toutes les variations. Autoriser uniquement les quantités définies dans les forfaits ci-dessus."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Quantité minimale"
@@ -240,6 +260,20 @@ msgstr "La quantité %1$d n'est pas disponible pour %2$s. Veuillez choisir parmi
msgid "View Options"
msgstr "Voir les options"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "Voir les options pour %s"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -3,10 +3,10 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.21\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-23 00:00+0000\n"
"PO-Revision-Date: 2025-12-23 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"PO-Revision-Date: 2025-12-30 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: Italian (Switzerland)\n"
"Language: it_CH\n"
@@ -152,6 +152,26 @@ msgstr "Aggiungi pacchetto"
msgid "Only allow quantities defined in packages above"
msgstr "Consenti solo le quantità definite nei pacchetti sopra"
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr "Prezzi a scaglioni e pacchetti predefiniti per tutte le variazioni"
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr "Imposta i prezzi predefiniti per tutte le variazioni. Le singole variazioni possono sovrascrivere questi valori predefiniti con i propri prezzi specifici."
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr "Configura qui i prezzi a scaglioni e pacchetti predefiniti. Tutte le variazioni erediteranno queste impostazioni a meno che non definiscano i propri prezzi specifici."
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr "Limita alle quantità dei pacchetti (predefinito)"
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr "Impostazione di restrizione predefinita per tutte le variazioni. Consenti solo le quantità definite nei pacchetti sopra."
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Quantità minima"
@@ -240,6 +260,20 @@ msgstr "La quantità %1$d non è disponibile per %2$s. Si prega di scegliere tra
msgid "View Options"
msgstr "Visualizza opzioni"
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr "Visualizza opzioni per %s"
#: includes/class-wc-tpp-product-meta.php:36
#: 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)"

View File

@@ -2,9 +2,9 @@
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.1.6\n"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-21 00:00+0000\n"
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -132,6 +132,26 @@ msgstr ""
msgid "Only allow quantities defined in packages above"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:42
msgid "Default Tier & Package Pricing for All Variations"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:43
msgid "Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing."
msgstr ""
#: includes/class-wc-tpp-product-meta.php:44
msgid "Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing."
msgstr ""
#: includes/class-wc-tpp-product-meta.php:87
msgid "Restrict to Package Quantities (Default)"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:88
msgid "Default restriction setting for all variations. Only allow quantities defined in packages above."
msgstr ""
#: templates/admin/tier-row.twig:9
msgid "Minimum Quantity"
msgstr ""

Binary file not shown.

View File

@@ -0,0 +1 @@
cee7ab535938b4096f225f0e0640c9b7 wc-tier-and-package-prices-1.2.0.zip

View File

@@ -0,0 +1 @@
b9cda03ef4ae8994e34fc1a6d8768e9c0a088461d795c5e79cb51f670c93d0b0 wc-tier-and-package-prices-1.2.0.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
470f3d87ce3ab9eb5a4ddc4a7262d282 wc-tier-and-package-prices-1.2.1.zip

View File

@@ -0,0 +1 @@
23e814583b71299f1d9e77c32ec0b9e88ce31c2ddf89a729282234c7f916ed2b wc-tier-and-package-prices-1.2.1.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
6c1bace109d1401832774668d85cf2c8 wc-tier-and-package-prices-1.2.2.zip

View File

@@ -0,0 +1 @@
a8674027621bd62ae63c2b63ec33da177f514816c35b45b9788d8dd5edd2c6ec wc-tier-and-package-prices-1.2.2.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
2ec8f0cbd01f1e77e0e61d84587025bd wc-tier-and-package-prices-1.2.3.zip

View File

@@ -0,0 +1 @@
ff4e7bd3db5de90e1481e3699cc2e69324046dd01d59c091e19540e3eb6cd0bd wc-tier-and-package-prices-1.2.3.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
abd9cb934494f7053c19a0a022d5717f wc-tier-and-package-prices-1.2.4.zip

View File

@@ -0,0 +1 @@
976bd9bf2cdc5bbf34a65160bc1b42f74cbff71d39c634a042efd59aa90a13d0 wc-tier-and-package-prices-1.2.4.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
61eafd6c2fdd1468c486efe0c6e3bfa4 wc-tier-and-package-prices-1.2.5.zip

View File

@@ -0,0 +1 @@
4f6d637713511db061dd2db5f948b846eb48c343d9b8823299b85038ccca6a6e wc-tier-and-package-prices-1.2.5.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
b2c862fbad94f42f0756fb496a1dd920 wc-tier-and-package-prices-1.2.6.zip

View File

@@ -0,0 +1 @@
19a00c7fc2f42326f52d2da063766abd1c0b47d1701b9050c018bfb2bafae6f6 wc-tier-and-package-prices-1.2.6.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
b52aec2e42c894a1949b5e5898105cf1 wc-tier-and-package-prices-1.2.7.zip

View File

@@ -0,0 +1 @@
7ae87ca85f7799ed716d3a81be8977908fadb33260dbc565f6b0d39188c8b025 wc-tier-and-package-prices-1.2.7.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
d412c85ef32b2e79e17227749265919f wc-tier-and-package-prices-1.2.8.zip

View File

@@ -0,0 +1 @@
0c47e2a966c485b377beaffb56703a130158a15e7ffe9d95b43dea84f9229e9c wc-tier-and-package-prices-1.2.8.zip

Binary file not shown.

View File

@@ -0,0 +1 @@
073fbf114a32d99cd8ced683a1efa19c wc-tier-and-package-prices-1.2.9.zip

View File

@@ -0,0 +1 @@
00d2568e9acfd7fc05c88b9048a6a281d007b55dec9c4c7a4a55cb515e013e97 wc-tier-and-package-prices-1.2.9.zip

View File

@@ -4,33 +4,34 @@
# @package WC_Tier_Package_Prices
# @var int index
# @var array package
# @var string field_prefix (optional) - Prefix for field names (for variations)
#}
<div class="wc-tpp-package-row">
<p class="form-field">
<label>{{ 'Quantity'|__('wc-tier-package-prices') }}</label>
{% set name_prefix = field_prefix is defined ? field_prefix : '_wc_tpp_packages' %}
<tr class="wc-tpp-package-row">
<td>
<input type="number"
name="_wc_tpp_packages[{{ index|esc_attr }}][qty]"
name="{{ name_prefix }}[{{ index|esc_attr }}][qty]"
value="{{ package.qty|default('')|esc_attr }}"
placeholder="{{ 'e.g., 10'|__('wc-tier-package-prices') }}"
min="1"
step="1"
class="short">
</p>
<p class="form-field">
<label>{{ 'Fixed Price'|__('wc-tier-package-prices') }}</label>
</td>
<td>
<input type="text"
name="_wc_tpp_packages[{{ index|esc_attr }}][price]"
name="{{ name_prefix }}[{{ index|esc_attr }}][price]"
value="{{ package.price|default('')|esc_attr }}"
placeholder="{{ 'e.g., 99.99'|__('wc-tier-package-prices') }}"
placeholder="{{ 'e.g., 99.99 ' ~ currency_symbol }}"
class="short wc_input_price">
</p>
<p class="form-field">
<label>{{ 'Label (Optional)'|__('wc-tier-package-prices') }}</label>
</td>
<td>
<input type="text"
name="_wc_tpp_packages[{{ index|esc_attr }}][label]"
name="{{ name_prefix }}[{{ index|esc_attr }}][label]"
value="{{ package.label|default('')|esc_attr }}"
placeholder="{{ 'e.g., Starter Pack'|__('wc-tier-package-prices') }}"
class="regular">
</p>
</td>
<td>
<button type="button" class="button wc-tpp-remove-package">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</div>
</td>
</tr>

View File

@@ -4,33 +4,34 @@
# @package WC_Tier_Package_Prices
# @var int index
# @var array tier
# @var string field_prefix (optional) - Prefix for field names (for variations)
#}
<div class="wc-tpp-tier-row">
<p class="form-field">
<label>{{ 'Minimum Quantity'|__('wc-tier-package-prices') }}</label>
{% set name_prefix = field_prefix is defined ? field_prefix : '_wc_tpp_tiers' %}
<tr class="wc-tpp-tier-row">
<td>
<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 }}"
placeholder="{{ 'e.g., 10'|__('wc-tier-package-prices') }}"
min="1"
step="1"
class="short">
</p>
<p class="form-field">
<label>{{ 'Price per Unit'|__('wc-tier-package-prices') }}</label>
</td>
<td>
<input type="text"
name="_wc_tpp_tiers[{{ index|esc_attr }}][price]"
name="{{ name_prefix }}[{{ index|esc_attr }}][price]"
value="{{ tier.price|default('')|esc_attr }}"
placeholder="{{ 'e.g., 9.99'|__('wc-tier-package-prices') }}"
placeholder="{{ 'e.g., 9.99 ' ~ currency_symbol }}"
class="short wc_input_price">
</p>
<p class="form-field">
<label>{{ 'Label (Optional)'|__('wc-tier-package-prices') }}</label>
</td>
<td>
<input type="text"
name="_wc_tpp_tiers[{{ index|esc_attr }}][label]"
name="{{ name_prefix }}[{{ index|esc_attr }}][label]"
value="{{ tier.label|default('')|esc_attr }}"
placeholder="{{ 'e.g., Wholesale'|__('wc-tier-package-prices') }}"
class="regular">
</p>
</td>
<td>
<button type="button" class="button wc-tpp-remove-tier">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</div>
</td>
</tr>

View File

@@ -5,13 +5,14 @@
# @var object product
# @var array tiers
# @var array packages
# @var bool restrict_to_packages
#}
<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} %}
{% 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)} %}
{% endif %}
</div>

View File

@@ -4,7 +4,7 @@
* Plugin Name: WooCommerce Tier and 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
* Version: 1.1.22
* Version: 1.2.9
* Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev
* Text Domain: wc-tier-package-prices
@@ -23,7 +23,7 @@ if (!defined('ABSPATH')) {
// Define plugin constants
if (!defined('WC_TPP_VERSION')) {
define('WC_TPP_VERSION', '1.1.22');
define('WC_TPP_VERSION', '1.2.9');
}
if (!defined('WC_TPP_PLUGIN_DIR')) {
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));