Release version 1.0.1

- Add Twig template engine integration
- Migrate all templates to Twig format
- Add German (Switzerland, Informal) translation
- Improve template organization and security
- Add Composer dependency management
- Create comprehensive changelog
This commit is contained in:
2025-12-21 04:56:50 +01:00
commit 902838b91f
32 changed files with 2987 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(unzip:*)",
"Bash(msgfmt:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(composer init:*)"
]
}
}

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# WordPress
.DS_Store
Thumbs.db
# Composer
/vendor/
composer.lock
# Twig cache
/templates/cache/
# IDE
.vscode/
.idea/
*.sublime-project
*.sublime-workspace
# Node
node_modules/
npm-debug.log
# Logs
*.log
# OS
.DS_Store
._*

55
CHANGELOG.md Normal file
View File

@@ -0,0 +1,55 @@
# Changelog
All notable changes to WooCommerce Tier and Package Prices will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.1] - 2025-12-21
### Added
- Twig template engine integration for all templates
- Template loader class with WordPress integration
- German (Switzerland, Informal) translation (de_CH_informal)
- Composer dependency management
- Comprehensive translation support in Twig templates
- Template caching support (disabled in debug mode)
### Changed
- Migrated all PHP templates to Twig format (.twig)
- Improved template organization and separation of concerns
- Enhanced security with automatic HTML escaping in templates
- Updated composer.json with complete package metadata
### Removed
- Old PHP template files (replaced with Twig)
### Technical
- Added Twig v3.22.2 dependency
- Created WC_TPP_Template_Loader class for centralized template rendering
- Integrated WordPress functions (__(), _e(), esc_*, wc_price()) into Twig
- Added Swiss German localization with informal address form
- Organized templates into admin/ and frontend/ directories
## [1.0.0] - 2025-12-21
### Added
- Initial release
- Tier pricing functionality (quantity-based discounts)
- Package pricing functionality (fixed-price bundles)
- Admin settings page for plugin configuration
- Product meta boxes for configuring tier and package prices
- Frontend pricing tables display
- Cart integration for automatic price calculation
- WooCommerce HPOS compatibility
- Multilingual support with text domain
- German (Germany) translation (de_DE)
- English (US) translation (en_US)
### Features
- Volume discounts based on quantity thresholds
- Fixed-price packages with custom labels
- Configurable display positions (before/after cart button, after price)
- Real-time price updates in cart
- Responsive pricing tables
- Admin-friendly interface for price management

188
INSTALLATION.md Normal file
View File

@@ -0,0 +1,188 @@
# Installation Guide
## Quick Installation
### Method 1: WordPress Admin Upload (Recommended)
1. Download `wc-tier-and-package-prices.zip` from the releases
2. Log in to your WordPress admin dashboard
3. Navigate to **Plugins > Add New**
4. Click **Upload Plugin** button at the top
5. Click **Choose File** and select `wc-tier-and-package-prices.zip`
6. Click **Install Now**
7. After installation, click **Activate Plugin**
8. You're done! The plugin is now active.
### Method 2: Manual FTP Upload
1. Download `wc-tier-and-package-prices.zip`
2. Extract the ZIP file to get the `wc-tier-and-package-prices` folder
3. Connect to your server via FTP
4. Upload the entire `wc-tier-and-package-prices` folder to `/wp-content/plugins/`
5. Go to your WordPress admin dashboard
6. Navigate to **Plugins**
7. Find "WooCommerce Tier and Package Prices" and click **Activate**
### Method 3: WP-CLI
If you have WP-CLI installed:
```bash
wp plugin install wc-tier-and-package-prices.zip --activate
```
## Post-Installation Setup
### 1. Verify Installation
After activation, you should see:
- A new menu item: **WooCommerce > Tier & Package Prices**
- No error messages
- Plugin listed in **Plugins** page
### 2. Configure Global Settings
1. Go to **WooCommerce > Tier & Package Prices**
2. Configure the following options:
- **Enable Tier Pricing**: ✓ (checked)
- **Enable Package Pricing**: ✓ (checked)
- **Display Pricing Table**: ✓ (checked)
- **Display Position**: Choose where to show pricing tables
- Before Add to Cart (recommended)
- After Add to Cart
- After Price
3. Click **Save Changes**
### 3. Configure Your First Product
1. Go to **Products** and edit any product (or create new one)
2. Scroll to **Product data** panel
3. Click the **General** tab
4. Scroll down to find the pricing sections
#### Add Tier Pricing (Volume Discounts)
1. Click **Add Tier** button
2. Example configuration:
- Min Quantity: `10`, Price: `9.50`
- Min Quantity: `25`, Price: `8.50`
- Min Quantity: `50`, Price: `7.50`
3. Click **Update** to save
#### Add Package Pricing (Fixed Packages)
1. Click **Add Package** button
2. Example configuration:
- Quantity: `12`, Price: `99.00`, Label: "Dozen Pack"
- Quantity: `24`, Price: `180.00`, Label: "Double Pack"
3. Click **Update** to save
### 4. Test on Frontend
1. Visit your product page on the frontend
2. You should see:
- Pricing table showing tiers and/or packages
- Price updates when changing quantity
- Package selection buttons (if packages configured)
3. Add to cart and verify correct pricing
## Requirements Check
Before installation, verify:
- ✓ WordPress 5.8 or higher
- ✓ WooCommerce 5.0 or higher installed and activated
- ✓ PHP 7.4 or higher
- ✓ Write permissions in `/wp-content/plugins/` directory
## Troubleshooting
### Plugin Won't Activate
**Error: "WooCommerce is required"**
- Solution: Install and activate WooCommerce first
**Error: "Parse error" or "Syntax error"**
- Solution: Your PHP version is too old. Upgrade to PHP 7.4 or higher
### Pricing Tables Not Showing
1. Check global settings are enabled (**WooCommerce > Tier & Package Prices**)
2. Verify you added tiers/packages to the specific product
3. Clear browser cache and reload the page
4. Check theme compatibility (try default WordPress theme)
### Prices Not Calculating Correctly
1. Make sure tiers are sorted by minimum quantity (plugin does this automatically)
2. Verify package quantities exactly match what customer selects
3. Clear WooCommerce caches (**WooCommerce > Status > Tools > Clear template cache**)
### Styling Issues
1. Clear browser cache
2. Check if theme has conflicting CSS
3. Try adding custom CSS in **Appearance > Customize > Additional CSS**
## Uninstallation
To completely remove the plugin:
1. Go to **Plugins** page
2. Deactivate "WooCommerce Tier and Package Prices"
3. Click **Delete**
4. Confirm deletion
**Note:** Product meta data (tiers and packages) will be preserved in database even after uninstallation. If you reinstall, your configurations will still be there.
To completely remove all data:
1. Before uninstalling, remove all tiers and packages from products
2. Then proceed with uninstallation
## File Structure After Installation
Your WordPress installation should have:
```
wp-content/
└── plugins/
└── wc-tier-and-package-prices/
├── wc-tier-and-package-prices.php (main file)
├── README.md
├── INSTALLATION.md
├── USAGE_EXAMPLES.md
├── includes/
│ ├── class-wc-tpp-admin.php
│ ├── class-wc-tpp-cart.php
│ ├── class-wc-tpp-frontend.php
│ └── class-wc-tpp-product-meta.php
└── assets/
├── css/
│ ├── admin.css
│ └── frontend.css
└── js/
├── admin.js
└── frontend.js
```
## Getting Help
If you encounter issues:
1. Check this installation guide
2. Review the [README.md](README.md) for feature documentation
3. Review [USAGE_EXAMPLES.md](USAGE_EXAMPLES.md) for configuration examples
4. Check WordPress error logs
5. Enable WP_DEBUG to see detailed errors
## Next Steps
After successful installation:
1. Read [USAGE_EXAMPLES.md](USAGE_EXAMPLES.md) for practical examples
2. Configure tier pricing on your products
3. Test the complete checkout flow
4. Customize display settings to match your theme
5. Consider creating package deals for popular quantities
Enjoy your new tier and package pricing capabilities!

97
QUICKSTART.md Normal file
View File

@@ -0,0 +1,97 @@
# Quick Start Guide
Get started with WooCommerce Tier and Package Prices in 5 minutes!
## Step 1: Install (2 minutes)
1. Go to **Plugins > Add New** in WordPress
2. Click **Upload Plugin**
3. Choose `wc-tier-and-package-prices.zip`
4. Click **Install Now** then **Activate**
✓ Done! Plugin is active.
## Step 2: Basic Setup (1 minute)
1. Go to **WooCommerce > Tier & Package Prices**
2. Make sure all checkboxes are checked:
- ✓ Enable Tier Pricing
- ✓ Enable Package Pricing
- ✓ Display Pricing Table
3. Set Display Position to **Before Add to Cart**
4. Click **Save Changes**
## Step 3: Add Pricing to a Product (2 minutes)
### Example: Selling T-Shirts
1. Edit a product (or create new one)
2. Set regular price: **$20.00**
#### Add Volume Discounts
Click **Add Tier** and create:
- Min Qty: `10` → Price: `$18.00` (10% off)
- Min Qty: `25` → Price: `$16.00` (20% off)
#### Add Package Deals
Click **Add Package** and create:
- Qty: `12` → Price: `$200` → Label: "Team Pack"
- Qty: `24` → Price: `$360` → Label: "Bulk Order"
5. Click **Update** to save
## Step 4: Test It!
1. Visit your product page
2. See the pricing table appear
3. Change quantity to `12` → price updates automatically
4. Click "Select Package" on Team Pack → quantity changes to 12
5. Add to cart → verify price is correct ($200)
## That's It!
Your tier and package pricing is now working!
## Common Use Cases
### Wholesale Store
```
Tier 1: 10+ items = $X each
Tier 2: 50+ items = $Y each
Tier 3: 100+ items = $Z each
```
### Bulk Packages
```
Package 1: 6 items = $XX
Package 2: 12 items = $YY
Package 3: 24 items = $ZZ
```
### Mixed Approach
Use tiers for flexible quantities (10+, 25+, 50+)
Use packages for common bundles (6-pack, dozen, case)
## Pro Tips
1. **Round Numbers**: Use 10, 25, 50, 100 for tiers
2. **Meaningful Savings**: Offer at least 10% off per tier
3. **Label Packages**: "Family Pack" sells better than "4-pack"
4. **Test Checkout**: Always complete a test order
5. **Mobile Check**: View on phone to verify responsiveness
## Need More Help?
- Full documentation: [README.md](README.md)
- Detailed examples: [USAGE_EXAMPLES.md](USAGE_EXAMPLES.md)
- Installation help: [INSTALLATION.md](INSTALLATION.md)
## Keyboard Shortcuts (Admin)
- Add new tier: Click "Add Tier" button
- Remove tier: Click "Remove" button on that tier
- Save: Press Ctrl+S (or Cmd+S on Mac) to save product
Happy selling!

201
README.md Normal file
View File

@@ -0,0 +1,201 @@
# WooCommerce Tier and Package Prices
A powerful WooCommerce plugin that adds tier pricing and package pricing functionality to your products with configurable quantities at fixed prices.
## Features
### Tier Pricing (Volume Discounts)
- Set quantity-based pricing tiers
- Automatic discounts when customers buy in larger quantities
- Display savings percentage and amount
- Dynamically updates price based on quantity selected
### Package Pricing
- Create fixed-price packages with specific quantities
- Example: 10 pieces for $50, 25 pieces for $100
- Custom labels for packages (e.g., "Starter Pack", "Value Bundle")
- One-click package selection on product pages
### Admin Features
- Easy-to-use product meta boxes for adding tiers and packages
- Global settings page under WooCommerce menu
- Configure display position (before/after add to cart, after price)
- Enable/disable tier or package pricing independently
- Sortable pricing rules
### Frontend Features
- Beautiful pricing tables on product pages
- Responsive design for mobile devices
- Visual highlighting of active tier/package
- Real-time price updates when quantity changes
- Cart integration with proper price calculations
- Clear pricing indicators in cart
## Installation
1. Upload the `wc-tier-and-package-prices` folder to the `/wp-content/plugins/` directory
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Make sure WooCommerce is installed and activated
## Configuration
### Global Settings
Navigate to **WooCommerce > Tier & Package Prices** to configure:
- **Enable Tier Pricing**: Turn on/off volume discounts
- **Enable Package Pricing**: Turn on/off fixed-price packages
- **Display Pricing Table**: Show/hide pricing tables on product pages
- **Display Position**: Choose where to show pricing tables
### Product Settings
When editing a product, scroll to the **Product data** panel:
1. Click on the **General** tab
2. Scroll down to find **Tier Pricing** and **Package Pricing** sections
#### Adding Tier Pricing
1. Click "Add Tier" button
2. Set minimum quantity (e.g., 10)
3. Set price per unit (e.g., 9.99)
4. Add more tiers as needed
5. Tiers are automatically sorted by quantity
**Example:**
- 1-9 items: $12.00 each (regular price)
- 10-24 items: $10.00 each (tier 1)
- 25-49 items: $8.50 each (tier 2)
- 50+ items: $7.00 each (tier 3)
#### Adding Package Pricing
1. Click "Add Package" button
2. Set exact quantity (e.g., 10)
3. Set fixed price for that quantity (e.g., 99.99)
4. Optionally add a custom label (e.g., "Starter Pack")
5. Add more packages as needed
**Example:**
- Package 1: 10 pieces for $95 (labeled "Small Bundle")
- Package 2: 25 pieces for $200 (labeled "Value Pack")
- Package 3: 50 pieces for $350 (labeled "Business Pack")
## How It Works
### Price Calculation Priority
1. **Package Price** (highest priority): If the exact quantity matches a package, the package price applies
2. **Tier Price**: If no package match, the highest applicable tier price is used
3. **Regular Price**: If no tier or package applies, the regular product price is used
### Frontend Display
- Pricing tables show all available tiers and packages
- Current tier/package is highlighted based on selected quantity
- Prices update dynamically when quantity changes
- Package buttons allow one-click quantity selection
### Cart Behavior
- Correct prices are applied in the cart
- Cart shows whether tier or package pricing is applied
- Subtotals are calculated correctly
- Compatible with WooCommerce checkout process
## File Structure
```
wc-tier-and-package-prices/
├── wc-tier-and-package-prices.php # Main plugin file
├── includes/
│ ├── class-wc-tpp-admin.php # Admin settings
│ ├── class-wc-tpp-product-meta.php # Product meta boxes
│ ├── class-wc-tpp-frontend.php # Frontend display
│ └── class-wc-tpp-cart.php # Cart price calculations
├── assets/
│ ├── css/
│ │ ├── admin.css # Admin styles
│ │ └── frontend.css # Frontend styles
│ └── js/
│ ├── admin.js # Admin JavaScript
│ └── frontend.js # Frontend JavaScript
└── README.md
```
## Requirements
- WordPress 6.0 or higher (tested up to 6.9.x)
- WooCommerce 8.0 or higher (tested up to 10.x)
- PHP 7.4 or higher
### Compatibility
-**WooCommerce 10.x**: Fully compatible with the latest WooCommerce release
-**WordPress 6.9.x**: Tested and verified with WordPress 6.9.x
-**HPOS (High-Performance Order Storage)**: Full support for WooCommerce Custom Order Tables
-**Block-based Themes**: Compatible with modern WordPress block themes
-**Multisite**: Works on WordPress multisite installations
## Frequently Asked Questions
**Q: Can I use both tier pricing and package pricing on the same product?**
A: Yes! Package pricing takes priority when the exact quantity matches.
**Q: What happens if a customer changes the quantity in the cart?**
A: The price will automatically recalculate based on the new quantity.
**Q: Can I set different tiers for different products?**
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.
## Support
For bug reports and feature requests, please use the plugin's support forum or contact the developer.
## License
This plugin is licensed under the GPL v2 or later.
## Changelog
### Version 1.0.0 - 2025-12-21
#### Compatibility Updates
- ✅ Updated for WooCommerce 10.x compatibility
- ✅ Updated for WordPress 6.9.x compatibility
- ✅ Added HPOS (High-Performance Order Storage) support
- ✅ Declared compatibility with WooCommerce Custom Order Tables
#### Security Enhancements
- ✅ Added nonce verification for product meta save operations
- ✅ Added capability checks for user permissions
- ✅ Enhanced data escaping and sanitization
- ✅ Implemented autosave prevention
#### Code Improvements
- ✅ Enhanced cart object validation
- ✅ Improved product object type checking
- ✅ Better error handling for edge cases
- ✅ Updated data storage methods for cart items
- ✅ Modernized JavaScript localization with proper escaping
#### Initial Features
- Initial release with tier pricing functionality
- Package pricing with fixed quantities
- Customizable pricing tables
- Global settings page
- Product-level configuration
- Cart integration with dynamic pricing
- Responsive frontend design
## Credits
Developed by Marco Graetsch for WooCommerce store owners who need flexible pricing options.

171
USAGE_EXAMPLES.md Normal file
View File

@@ -0,0 +1,171 @@
# Usage Examples
## Example 1: T-Shirt Store with Volume Discounts
### Tier Pricing Setup
For a t-shirt that normally costs $20:
| Min Quantity | Price per Unit | Customer Saves |
|--------------|----------------|----------------|
| 1 | $20.00 | - |
| 10 | $18.00 | $2.00 (10%) |
| 25 | $16.00 | $4.00 (20%) |
| 50 | $14.00 | $6.00 (30%) |
**How to configure:**
1. Edit your t-shirt product
2. Set regular price to $20.00
3. Add tier: Min Qty = 10, Price = $18.00
4. Add tier: Min Qty = 25, Price = $16.00
5. Add tier: Min Qty = 50, Price = $14.00
6. Save product
**Customer experience:**
- Buys 5 shirts: $20.00 each = $100.00 total
- Buys 15 shirts: $18.00 each = $270.00 total (saves $30)
- Buys 30 shirts: $16.00 each = $480.00 total (saves $120)
## Example 2: Office Supplies with Package Deals
### Package Pricing Setup
For pens that normally cost $2.00 each:
| Package | Quantity | Fixed Price | Price per Unit |
|--------------|----------|-------------|----------------|
| Single | 1 | $2.00 | $2.00 |
| Small Pack | 12 | $20.00 | $1.67 |
| Value Pack | 24 | $36.00 | $1.50 |
| Bulk Pack | 50 | $65.00 | $1.30 |
**How to configure:**
1. Edit your pen product
2. Set regular price to $2.00
3. Add package: Qty = 12, Price = $20.00, Label = "Small Pack"
4. Add package: Qty = 24, Price = $36.00, Label = "Value Pack"
5. Add package: Qty = 50, Price = $65.00, Label = "Bulk Pack"
6. Save product
**Customer experience:**
- Customer sees package cards on product page
- Clicks "Select Package" for "Value Pack"
- Quantity automatically set to 24
- Price shows $36.00 (not $48.00)
## Example 3: Combined Tier and Package Pricing
### Coffee Beans Store
For premium coffee beans at $15.00 per bag:
**Tier Pricing:**
- 1-4 bags: $15.00 each
- 5-9 bags: $14.00 each
- 10+ bags: $13.00 each
**Package Deals:**
- Monthly subscription: 4 bags for $50.00 ($12.50 each)
- Quarterly bundle: 12 bags for $140.00 ($11.67 each)
**How to configure:**
1. Set regular price: $15.00
2. Add tier: Min Qty = 5, Price = $14.00
3. Add tier: Min Qty = 10, Price = $13.00
4. Add package: Qty = 4, Price = $50.00, Label = "Monthly Box"
5. Add package: Qty = 12, Price = $140.00, Label = "Quarterly Bundle"
**Customer scenarios:**
- Buys 3 bags: $15.00 each = $45.00
- Buys 4 bags: Gets prompted for Monthly Box at $50.00
- Buys 6 bags: $14.00 each = $84.00 (tier pricing)
- Buys 12 bags: Gets Quarterly Bundle at $140.00 (package beats tier)
## Example 4: Promotional Packages
### Seasonal Gift Sets
Regular mug price: $10.00
**Package Deals:**
- Pair Pack: 2 mugs for $18.00 (10% off)
- Family Set: 4 mugs for $32.00 (20% off)
- Office Bundle: 10 mugs for $70.00 (30% off)
**Configuration:**
- Package 1: Qty = 2, Price = $18.00, Label = "Perfect Pair"
- Package 2: Qty = 4, Price = $32.00, Label = "Family Set"
- Package 3: Qty = 10, Price = $70.00, Label = "Office Bundle"
## Tips for Best Results
### Tier Pricing Best Practices
1. **Progressive Discounts**: Make higher quantities more attractive
- Good: 10 items = 10% off, 25 items = 20% off, 50 items = 30% off
- Bad: 10 items = 20% off, 25 items = 15% off
2. **Clear Break Points**: Use round numbers for quantities
- Good: 10, 25, 50, 100
- Avoid: 7, 23, 47, 93
3. **Meaningful Savings**: Ensure discounts are worth the extra purchase
- Minimum 5-10% per tier level
- Higher tiers should have progressively better deals
### Package Pricing Best Practices
1. **Strategic Quantities**: Match common use cases
- 12-pack (dozen)
- 24-pack (two dozen)
- 6-pack (half dozen)
2. **Compelling Labels**: Make packages attractive
- "Starter Pack" instead of "10-piece"
- "Family Bundle" instead of "4-pack"
- "Business Value Pack" instead of "50-piece"
3. **Sweet Spot Pricing**: Price just below psychological barriers
- $99.99 instead of $100.00
- $49.95 instead of $50.00
### Combining Both Strategies
1. Use **packages** for common quantities (6, 12, 24)
2. Use **tiers** for flexible volume discounts (10+, 25+, 50+)
3. Packages take priority when exact match exists
4. Tiers fill the gaps between packages
## Display Position Recommendations
- **Before Add to Cart**: Best for products where pricing is the main decision factor
- **After Add to Cart**: Good for impulse purchases and upsells
- **After Price**: Ideal for clean product pages, keeps info near the price
## Testing Your Setup
1. Add a product to your store
2. Configure tier/package pricing
3. View the product page in an incognito window
4. Test different quantities
5. Verify prices update correctly
6. Add to cart and check cart totals
7. Complete a test checkout
## Common Scenarios
### Wholesale Store
- Tier 1: 10+ items = wholesale price tier 1
- Tier 2: 50+ items = wholesale price tier 2
- Tier 3: 100+ items = wholesale price tier 3
### Subscription Box
- Package 1: 1 month (4 items) = $40
- Package 2: 3 months (12 items) = $108 (10% off)
- Package 3: 6 months (24 items) = $192 (20% off)
### Event Supplies
- Package 1: Small party (10 pieces) = $50
- Package 2: Medium party (25 pieces) = $110
- Package 3: Large party (50 pieces) = $200
- Package 4: Event package (100 pieces) = $350

91
assets/css/admin.css Normal file
View File

@@ -0,0 +1,91 @@
/**
* Admin styles for WooCommerce Tier and Package Prices
*/
.wc-tpp-tier-pricing,
.wc-tpp-package-pricing {
border-top: 1px solid #eee;
padding-top: 15px;
margin-top: 15px;
}
.wc-tpp-tier-pricing > p:first-child,
.wc-tpp-package-pricing > p:first-child {
font-weight: 600;
margin-bottom: 10px;
}
.wc-tpp-tier-pricing .description,
.wc-tpp-package-pricing .description {
display: block;
margin-top: 5px;
font-style: italic;
color: #666;
}
.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;
}
.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 input,
.wc-tpp-package-row input {
width: 100%;
}
.wc-tpp-remove-tier,
.wc-tpp-remove-package {
flex-shrink: 0;
color: #b32d2e;
border-color: #b32d2e;
margin-bottom: 0;
}
.wc-tpp-remove-tier:hover,
.wc-tpp-remove-package:hover {
background: #b32d2e;
color: #fff;
}
.wc-tpp-add-tier,
.wc-tpp-add-package {
margin-top: 10px;
}
.wc-tpp-tiers-container,
.wc-tpp-packages-container {
margin-bottom: 15px;
}
.wc-tpp-tiers-container:empty::before,
.wc-tpp-packages-container:empty::before {
content: "No items added yet. Click 'Add' button to create pricing rules.";
display: block;
padding: 20px;
background: #f0f0f1;
border: 1px dashed #ccc;
border-radius: 4px;
text-align: center;
color: #666;
font-style: italic;
}

173
assets/css/frontend.css Normal file
View File

@@ -0,0 +1,173 @@
/**
* Frontend styles for WooCommerce Tier and Package Prices
*/
.wc-tpp-pricing-container {
margin: 20px 0;
padding: 20px;
background: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.wc-tpp-tier-pricing-table,
.wc-tpp-package-pricing-table {
margin-bottom: 30px;
}
.wc-tpp-tier-pricing-table:last-child,
.wc-tpp-package-pricing-table:last-child {
margin-bottom: 0;
}
.wc-tpp-tier-pricing-table h3,
.wc-tpp-package-pricing-table h3 {
margin: 0 0 15px 0;
font-size: 1.2em;
color: #333;
}
/* Tier pricing table */
.wc-tpp-table {
width: 100%;
border-collapse: collapse;
background: #fff;
border-radius: 4px;
overflow: hidden;
}
.wc-tpp-table thead {
background: #333;
color: #fff;
}
.wc-tpp-table th {
padding: 12px;
text-align: left;
font-weight: 600;
font-size: 0.9em;
}
.wc-tpp-table tbody tr {
border-bottom: 1px solid #e0e0e0;
transition: background-color 0.2s;
}
.wc-tpp-table tbody tr:hover {
background: #f5f5f5;
}
.wc-tpp-table tbody tr.wc-tpp-active-tier {
background: #e7f3e7;
font-weight: 600;
}
.wc-tpp-table td {
padding: 12px;
font-size: 0.95em;
}
/* Package pricing */
.wc-tpp-packages {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.wc-tpp-package {
background: #fff;
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s;
cursor: pointer;
}
.wc-tpp-package:hover {
border-color: #333;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.wc-tpp-package.wc-tpp-selected {
border-color: #4CAF50;
background: #f0f8f0;
}
.wc-tpp-package-header h4 {
margin: 0 0 15px 0;
font-size: 1.1em;
color: #333;
}
.wc-tpp-package-details {
margin-bottom: 15px;
}
.wc-tpp-package-qty {
font-size: 1.4em;
margin-bottom: 10px;
}
.wc-tpp-package-qty strong {
color: #333;
}
.wc-tpp-package-price {
font-size: 1.6em;
font-weight: 700;
color: #4CAF50;
margin-bottom: 5px;
}
.wc-tpp-package-unit-price {
font-size: 0.85em;
color: #666;
}
.wc-tpp-select-package {
width: 100%;
padding: 10px 20px;
background: #333;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.95em;
transition: background 0.3s;
}
.wc-tpp-select-package:hover {
background: #555;
}
.wc-tpp-package.wc-tpp-selected .wc-tpp-select-package {
background: #4CAF50;
}
.wc-tpp-package.wc-tpp-selected .wc-tpp-select-package:hover {
background: #45a049;
}
/* Cart notices */
.wc-tpp-notice {
color: #4CAF50;
font-style: italic;
}
/* Responsive design */
@media (max-width: 768px) {
.wc-tpp-packages {
grid-template-columns: 1fr;
}
.wc-tpp-table {
font-size: 0.9em;
}
.wc-tpp-table th,
.wc-tpp-table td {
padding: 8px;
}
}

62
assets/js/admin.js Normal file
View File

@@ -0,0 +1,62 @@
/**
* Admin JavaScript for WooCommerce Tier and Package Prices
*/
(function($) {
'use strict';
$(document).ready(function() {
let tierIndex = $('.wc-tpp-tier-row').length;
let packageIndex = $('.wc-tpp-package-row').length;
// Add tier
$('.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);
tierIndex++;
});
// 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();
}
});
// 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();
}
});
// Validate inputs
$(document).on('input', 'input[name*="[min_qty]"], input[name*="[qty]"]', function() {
const value = parseInt($(this).val());
if (value < 1) {
$(this).val(1);
}
});
$(document).on('input', 'input[name*="[price]"]', function() {
const value = parseFloat($(this).val());
if (value < 0) {
$(this).val(0);
}
});
});
})(jQuery);

179
assets/js/frontend.js Normal file
View File

@@ -0,0 +1,179 @@
/**
* Frontend JavaScript for WooCommerce Tier and Package Prices
*/
(function($) {
'use strict';
$(document).ready(function() {
const $quantityInput = $('input.qty');
const $priceDisplay = $('.woocommerce-Price-amount.amount').first();
if ($quantityInput.length === 0) {
return;
}
// Get tiers and packages data
const tiers = [];
const packages = [];
$('.wc-tpp-tier-pricing-table tbody tr').each(function() {
tiers.push({
minQty: parseInt($(this).data('min-qty')),
price: parseFloat($(this).data('price'))
});
});
$('.wc-tpp-package').each(function() {
packages.push({
qty: parseInt($(this).data('qty')),
price: parseFloat($(this).data('price'))
});
});
// Update price display based on quantity
function updatePriceDisplay() {
const quantity = parseInt($quantityInput.val()) || 1;
// Check for exact package match
let matchedPackage = null;
packages.forEach(function(pkg) {
if (pkg.qty === quantity) {
matchedPackage = pkg;
}
});
if (matchedPackage) {
const totalPrice = matchedPackage.price;
const unitPrice = totalPrice / quantity;
updatePrice(unitPrice, 'Package price: ' + formatPrice(totalPrice) + ' total');
highlightPackage(quantity);
return;
}
// Check for tier pricing
let applicableTier = null;
tiers.forEach(function(tier) {
if (quantity >= tier.minQty) {
applicableTier = tier;
}
});
if (applicableTier) {
updatePrice(applicableTier.price, 'Volume discount applied');
highlightTier(quantity);
removePackageHighlight();
} else {
removeAllHighlights();
}
}
// Update price in the product page
function updatePrice(price, message) {
if ($priceDisplay.length) {
const formattedPrice = formatPrice(price);
$priceDisplay.html(formattedPrice);
// Add message if provided
if (message) {
if (!$('.wc-tpp-price-message').length) {
$priceDisplay.after('<div class="wc-tpp-price-message"></div>');
}
$('.wc-tpp-price-message').html('<small>' + message + '</small>');
}
}
}
// Format price according to WooCommerce settings
function formatPrice(price) {
const decimals = wcTppData.price_decimals || 2;
const decimalSep = wcTppData.price_decimal_separator || '.';
const thousandSep = wcTppData.price_thousand_separator || ',';
const symbol = wcTppData.currency_symbol || '$';
const position = wcTppData.currency_position || 'left';
let priceStr = price.toFixed(decimals);
const parts = priceStr.split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSep);
priceStr = parts.join(decimalSep);
switch (position) {
case 'left':
return symbol + priceStr;
case 'right':
return priceStr + symbol;
case 'left_space':
return symbol + ' ' + priceStr;
case 'right_space':
return priceStr + ' ' + symbol;
default:
return symbol + priceStr;
}
}
// Highlight active tier
function highlightTier(quantity) {
$('.wc-tpp-table tbody tr').removeClass('wc-tpp-active-tier');
let activeRow = null;
$('.wc-tpp-table tbody tr').each(function() {
const minQty = parseInt($(this).data('min-qty'));
if (quantity >= minQty) {
activeRow = $(this);
}
});
if (activeRow) {
activeRow.addClass('wc-tpp-active-tier');
}
}
// Highlight selected package
function highlightPackage(quantity) {
$('.wc-tpp-package').removeClass('wc-tpp-selected');
$('.wc-tpp-table tbody tr').removeClass('wc-tpp-active-tier');
$('.wc-tpp-package').each(function() {
const pkgQty = parseInt($(this).data('qty'));
if (pkgQty === quantity) {
$(this).addClass('wc-tpp-selected');
}
});
}
// Remove package highlight
function removePackageHighlight() {
$('.wc-tpp-package').removeClass('wc-tpp-selected');
}
// Remove all highlights
function removeAllHighlights() {
$('.wc-tpp-package').removeClass('wc-tpp-selected');
$('.wc-tpp-table tbody tr').removeClass('wc-tpp-active-tier');
$('.wc-tpp-price-message').remove();
}
// Handle quantity input changes
$quantityInput.on('input change', function() {
updatePriceDisplay();
});
// Handle package selection
$('.wc-tpp-select-package').on('click', function(e) {
e.preventDefault();
const $package = $(this).closest('.wc-tpp-package');
const qty = parseInt($package.data('qty'));
$quantityInput.val(qty).trigger('change');
// Scroll to add to cart button
$('html, body').animate({
scrollTop: $('.single_add_to_cart_button').offset().top - 100
}, 500);
});
// Initial update
updatePriceDisplay();
});
})(jQuery);

22
composer.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "magdev/wc-tier-package-prices",
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
"version": "1.0.1",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Marco Graetsch",
"homepage": "https://src.bundespruefstelle.ch/magdev"
}
],
"require": {
"php": ">=7.4",
"twig/twig": "^3.0"
},
"autoload": {
"classmap": [
"includes/"
]
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Admin settings and configuration
*/
if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Admin {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
public function add_admin_menu() {
add_submenu_page(
'woocommerce',
__('Tier & Package Prices', 'wc-tier-package-prices'),
__('Tier & Package Prices', 'wc-tier-package-prices'),
'manage_woocommerce',
'wc-tier-package-prices',
array($this, 'settings_page')
);
}
public function register_settings() {
register_setting('wc_tpp_settings', 'wc_tpp_enable_tier_pricing');
register_setting('wc_tpp_settings', 'wc_tpp_enable_package_pricing');
register_setting('wc_tpp_settings', 'wc_tpp_display_table');
register_setting('wc_tpp_settings', 'wc_tpp_display_position');
}
public function enqueue_admin_scripts($hook) {
if ('woocommerce_page_wc-tier-package-prices' === $hook || 'post.php' === $hook || 'post-new.php' === $hook) {
wp_enqueue_style('wc-tpp-admin', WC_TPP_PLUGIN_URL . 'assets/css/admin.css', array(), WC_TPP_VERSION);
wp_enqueue_script('wc-tpp-admin', WC_TPP_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), WC_TPP_VERSION, true);
}
}
public function settings_page() {
WC_TPP_Template_Loader::get_instance()->display('admin/settings-page.twig');
}
}
new WC_TPP_Admin();

View File

@@ -0,0 +1,94 @@
<?php
/**
* Cart price calculation for tier and package pricing
*/
if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Cart {
public function __construct() {
add_action('woocommerce_before_calculate_totals', array($this, 'apply_tier_package_pricing'), 10, 1);
add_filter('woocommerce_cart_item_price', array($this, 'display_cart_item_price'), 10, 3);
add_filter('woocommerce_cart_item_subtotal', array($this, 'display_cart_item_subtotal'), 10, 3);
}
public function apply_tier_package_pricing($cart) {
if (is_admin() && !defined('DOING_AJAX')) {
return;
}
// Prevent infinite loops
if (did_action('woocommerce_before_calculate_totals') >= 2) {
return;
}
// Check if cart object is valid
if (!$cart || !is_a($cart, 'WC_Cart')) {
return;
}
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
$product_id = $cart_item['product_id'];
$quantity = $cart_item['quantity'];
$product = $cart_item['data'];
// Validate product object
if (!$product || !is_a($product, 'WC_Product')) {
continue;
}
// Check for exact package match first
$package_price = null;
if (get_option('wc_tpp_enable_package_pricing') === 'yes') {
$package_price = WC_TPP_Frontend::get_package_price($product_id, $quantity);
}
if ($package_price !== null) {
// Apply package pricing (total price divided by quantity)
$unit_price = $package_price / $quantity;
$product->set_price($unit_price);
// Store pricing information in cart item for display
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
if (get_option('wc_tpp_enable_tier_pricing') === 'yes') {
$tier_price = WC_TPP_Frontend::get_tier_price($product_id, $quantity);
if ($tier_price !== null) {
$product->set_price($tier_price);
// Store pricing information in cart item for display
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'tier';
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_unit_price'] = $tier_price;
}
}
}
}
}
public function display_cart_item_price($price, $cart_item, $cart_item_key) {
if (isset($cart_item['wc_tpp_pricing_type'])) {
if ($cart_item['wc_tpp_pricing_type'] === 'package') {
$total_price = isset($cart_item['wc_tpp_total_price']) ? $cart_item['wc_tpp_total_price'] : $cart_item['line_total'];
$unit_price = $total_price / $cart_item['quantity'];
return wc_price($unit_price) . ' <small class="wc-tpp-notice">(' . __('Package price', 'wc-tier-package-prices') . ')</small>';
} elseif ($cart_item['wc_tpp_pricing_type'] === 'tier') {
$unit_price = isset($cart_item['wc_tpp_unit_price']) ? $cart_item['wc_tpp_unit_price'] : $cart_item['data']->get_price();
return wc_price($unit_price) . ' <small class="wc-tpp-notice">(' . __('Volume discount', 'wc-tier-package-prices') . ')</small>';
}
}
return $price;
}
public function display_cart_item_subtotal($subtotal, $cart_item, $cart_item_key) {
if (isset($cart_item['wc_tpp_pricing_type']) && $cart_item['wc_tpp_pricing_type'] === 'package') {
$total_price = isset($cart_item['wc_tpp_total_price']) ? $cart_item['wc_tpp_total_price'] : $cart_item['line_total'];
return wc_price($total_price);
}
return $subtotal;
}
}
new WC_TPP_Cart();

View File

@@ -0,0 +1,109 @@
<?php
/**
* Frontend display for tier and package pricing
*/
if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Frontend {
public function __construct() {
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('woocommerce_before_add_to_cart_button', array($this, 'display_pricing_table_before'), 20);
add_action('woocommerce_after_add_to_cart_button', array($this, 'display_pricing_table_after'), 10);
add_action('woocommerce_single_product_summary', array($this, 'display_pricing_table_after_price'), 15);
}
public function enqueue_scripts() {
if (is_product()) {
wp_enqueue_style('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/css/frontend.css', array(), WC_TPP_VERSION);
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
wp_localize_script('wc-tpp-frontend', 'wcTppData', array(
'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()),
'price_decimal_separator' => esc_js(wc_get_price_decimal_separator()),
'price_thousand_separator' => esc_js(wc_get_price_thousand_separator())
));
}
}
public function display_pricing_table_before() {
if (get_option('wc_tpp_display_position') === 'before_add_to_cart') {
$this->display_pricing_table();
}
}
public function display_pricing_table_after() {
if (get_option('wc_tpp_display_position') === 'after_add_to_cart') {
$this->display_pricing_table();
}
}
public function display_pricing_table_after_price() {
if (get_option('wc_tpp_display_position') === 'after_price') {
$this->display_pricing_table();
}
}
public function display_pricing_table() {
global $product;
if (!$product || !is_a($product, 'WC_Product') || get_option('wc_tpp_display_table') !== 'yes') {
return;
}
$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);
if (empty($tiers) && empty($packages)) {
return;
}
WC_TPP_Template_Loader::get_instance()->display('frontend/pricing-table.twig', array(
'product' => $product,
'tiers' => $tiers,
'packages' => $packages
));
}
public static function get_tier_price($product_id, $quantity) {
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
if (empty($tiers) || !is_array($tiers)) {
return null;
}
$applicable_price = null;
foreach ($tiers as $tier) {
if ($quantity >= $tier['min_qty']) {
$applicable_price = $tier['price'];
}
}
return $applicable_price;
}
public static function get_package_price($product_id, $quantity) {
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
if (empty($packages) || !is_array($packages)) {
return null;
}
foreach ($packages as $package) {
if ($quantity == $package['qty']) {
return $package['price'];
}
}
return null;
}
}
new WC_TPP_Frontend();

View File

@@ -0,0 +1,157 @@
<?php
/**
* Product meta boxes for tier and package pricing
*/
if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Product_Meta {
public function __construct() {
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'));
}
public function add_tier_pricing_fields() {
global $post;
?>
<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>
<div 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);
}
?>
</div>
<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">
<?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);
}
?>
</div>
<p class="form-field">
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
</p>
</div>
<script type="text/html" id="wc-tpp-tier-row-template">
<?php $this->render_tier_row('{{INDEX}}', array('min_qty' => '', 'price' => '')); ?>
</script>
<script type="text/html" id="wc-tpp-package-row-template">
<?php $this->render_package_row('{{INDEX}}', array('qty' => '', 'price' => '', 'label' => '')); ?>
</script>
<?php
}
private function render_tier_row($index, $tier) {
WC_TPP_Template_Loader::get_instance()->display('admin/tier-row.twig', array(
'index' => $index,
'tier' => $tier
));
}
private function render_package_row($index, $package) {
WC_TPP_Template_Loader::get_instance()->display('admin/package-row.twig', array(
'index' => $index,
'package' => $package
));
}
public function save_tier_package_fields($post_id) {
// Verify nonce for security
if (!isset($_POST['woocommerce_meta_nonce']) || !wp_verify_nonce($_POST['woocommerce_meta_nonce'], 'woocommerce_save_data')) {
return;
}
// Check user permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Avoid auto-save
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Save tier pricing
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(
'min_qty' => absint($tier['min_qty']),
'price' => wc_format_decimal($tier['price'])
);
}
}
// Sort by minimum quantity
usort($tiers, function($a, $b) {
return $a['min_qty'] - $b['min_qty'];
});
update_post_meta($post_id, '_wc_tpp_tiers', $tiers);
} else {
delete_post_meta($post_id, '_wc_tpp_tiers');
}
// Save package pricing
if (isset($_POST['_wc_tpp_packages'])) {
$packages = array();
foreach ($_POST['_wc_tpp_packages'] as $package) {
if (!empty($package['qty']) && !empty($package['price'])) {
$packages[] = array(
'qty' => absint($package['qty']),
'price' => wc_format_decimal($package['price']),
'label' => sanitize_text_field($package['label'])
);
}
}
// Sort by quantity
usort($packages, function($a, $b) {
return $a['qty'] - $b['qty'];
});
update_post_meta($post_id, '_wc_tpp_packages', $packages);
} else {
delete_post_meta($post_id, '_wc_tpp_packages');
}
}
}
new WC_TPP_Product_Meta();

View File

@@ -0,0 +1,103 @@
<?php
/**
* Twig Template Loader for WC Tier Package Prices
*
* @package WC_Tier_Package_Prices
*/
if (!defined('ABSPATH')) {
exit;
}
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;
use Twig\TwigFunction;
class WC_TPP_Template_Loader {
private static $instance = null;
private $twig;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_twig();
}
private function init_twig() {
$loader = new FilesystemLoader(WC_TPP_PLUGIN_DIR . 'templates');
$this->twig = new Environment($loader, array(
'cache' => WP_DEBUG ? false : WC_TPP_PLUGIN_DIR . 'templates/cache',
'auto_reload' => true,
'autoescape' => 'html',
));
// Add WordPress translation filter
$this->twig->addFilter(new TwigFilter('__', function ($string, $domain = 'wc-tier-package-prices') {
return __($string, $domain);
}));
$this->twig->addFilter(new TwigFilter('_e', function ($string, $domain = 'wc-tier-package-prices') {
_e($string, $domain);
}));
$this->twig->addFilter(new TwigFilter('esc_html', 'esc_html'));
$this->twig->addFilter(new TwigFilter('esc_attr', 'esc_attr'));
$this->twig->addFilter(new TwigFilter('esc_url', 'esc_url'));
// Add WordPress functions
$this->twig->addFunction(new TwigFunction('get_option', 'get_option'));
$this->twig->addFunction(new TwigFunction('checked', function($checked, $current = true, $echo = false) {
return checked($checked, $current, $echo);
}));
$this->twig->addFunction(new TwigFunction('selected', function($selected, $current = true, $echo = false) {
return selected($selected, $current, $echo);
}));
$this->twig->addFunction(new TwigFunction('settings_fields', function($option_group) {
settings_fields($option_group);
}));
$this->twig->addFunction(new TwigFunction('submit_button', function($text = null) {
submit_button($text);
}));
$this->twig->addFunction(new TwigFunction('get_admin_page_title', 'get_admin_page_title'));
$this->twig->addFunction(new TwigFunction('wc_price', 'wc_price'));
}
/**
* Render a Twig template
*
* @param string $template Template file name (e.g., 'admin/settings-page.twig')
* @param array $context Variables to pass to the template
* @return string Rendered template
*/
public function render($template, $context = array()) {
try {
return $this->twig->render($template, $context);
} catch (Exception $e) {
if (WP_DEBUG) {
return sprintf(
'<div class="error"><p>Twig Error: %s</p></div>',
esc_html($e->getMessage())
);
}
return '';
}
}
/**
* Display a Twig template
*
* @param string $template Template file name
* @param array $context Variables to pass to the template
*/
public function display($template, $context = array()) {
echo $this->render($template, $context);
}
}

223
languages/README.md Normal file
View File

@@ -0,0 +1,223 @@
# WooCommerce Tier and Package Prices - Translations
This directory contains translation files for the WooCommerce Tier and Package Prices plugin.
## Available Languages
- **English (US)** - `en_US` - Default language
- **German (Germany)** - `de_DE` - Deutsch
## File Structure
```
languages/
├── README.md # This file
├── wc-tier-package-prices.pot # Translation template (3.1 KB)
├── wc-tier-package-prices-en_US.po # English source file
├── wc-tier-package-prices-en_US.mo # English compiled file (2.9 KB)
├── wc-tier-package-prices-de_DE.po # German source file
└── wc-tier-package-prices-de_DE.mo # German compiled file (3.0 KB)
```
## File Types
- **`.pot`** - Portable Object Template - Master template file for creating new translations
- **`.po`** - Portable Object - Human-readable translation source file
- **`.mo`** - Machine Object - Compiled binary file used by WordPress
## Adding a New Translation
### Method 1: Using Poedit (Recommended)
1. Download and install [Poedit](https://poedit.net/)
2. Open Poedit and click "Create new translation"
3. Select the `wc-tier-package-prices.pot` file
4. Choose your language
5. Translate all strings
6. Save the file (this creates both `.po` and `.mo` files automatically)
7. Name the files as `wc-tier-package-prices-{locale}.po` and `wc-tier-package-prices-{locale}.mo`
- Example for French: `wc-tier-package-prices-fr_FR.po` and `wc-tier-package-prices-fr_FR.mo`
### Method 2: Using Command Line
1. Copy the POT file to create a new PO file:
```bash
cp wc-tier-package-prices.pot wc-tier-package-prices-fr_FR.po
```
2. Edit the PO file header:
```
"Language: fr_FR\n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
```
3. Translate all `msgstr` entries in the PO file
4. Compile the PO file to MO:
```bash
msgfmt -o wc-tier-package-prices-fr_FR.mo wc-tier-package-prices-fr_FR.po
```
## WordPress Locale Codes
Common locale codes for WordPress:
- `en_US` - English (United States)
- `en_GB` - English (United Kingdom)
- `de_DE` - German (Germany)
- `de_CH` - German (Switzerland)
- `fr_FR` - French (France)
- `fr_CH` - French (Switzerland)
- `es_ES` - Spanish (Spain)
- `it_IT` - Italian (Italy)
- `nl_NL` - Dutch (Netherlands)
- `pt_BR` - Portuguese (Brazil)
- `ru_RU` - Russian
- `zh_CN` - Chinese (Simplified)
- `ja` - Japanese
For a complete list, see: https://translate.wordpress.org/
## Translation Statistics
### English (en_US)
- **Total strings**: 30
- **Translated**: 30 (100%)
- **Status**: ✅ Complete
### German (de_DE)
- **Total strings**: 30
- **Translated**: 30 (100%)
- **Status**: ✅ Complete
## Translatable Strings
The plugin contains translations for:
### Admin Interface
- Settings page titles and descriptions
- Product meta box labels
- Field labels and placeholders
- Button text
### Frontend
- Pricing table headers
- Package selection buttons
- Cart notifications
- Discount displays
## Testing Translations
### 1. Install Translation Files
Upload the `.mo` file to one of these locations:
- Plugin directory: `wp-content/plugins/wc-tier-and-package-prices/languages/`
- WordPress languages directory: `wp-content/languages/plugins/`
### 2. Change WordPress Language
1. Go to **Settings > General**
2. Set **Site Language** to your desired language
3. Save changes
### 3. Verify Translation
1. Navigate to **WooCommerce > Tier & Package Prices**
2. Edit a product and check the meta boxes
3. View a product on the frontend
4. Check the cart page
All text should appear in the selected language.
## Updating Existing Translations
When the plugin is updated with new strings:
1. Update the POT template file
2. Open your PO file in Poedit
3. Click **Catalog > Update from POT file**
4. Select the new `wc-tier-package-prices.pot` file
5. Translate any new strings
6. Save (automatically compiles to MO)
## Command Line Compilation
If you prefer command-line tools:
```bash
# Compile a single language
msgfmt -o wc-tier-package-prices-de_DE.mo wc-tier-package-prices-de_DE.po
# Compile all languages at once
for po in *.po; do
msgfmt -o "${po%.po}.mo" "$po"
done
# Verify MO file
msgunfmt wc-tier-package-prices-de_DE.mo | less
```
## Translation Guidelines
### Style Guide
1. **Consistency**: Use consistent terminology throughout
2. **Context**: Consider the context where text appears (admin vs frontend)
3. **Length**: Keep translations similar in length to the original
4. **Formality**: Maintain appropriate level of formality for your language
5. **Technical Terms**: Don't translate technical terms like "WooCommerce"
### German Translation Notes
- Uses formal "Sie" form (not informal "du")
- Currency examples adjusted (€ instead of $)
- Decimal separator uses comma (9,99 instead of 9.99)
- Date format: DD.MM.YYYY
### Best Practices
- ✅ Translate user-facing strings
- ✅ Keep HTML tags intact
- ✅ Preserve placeholders like `%s`, `%d`
- ✅ Maintain the same number of placeholders
- ❌ Don't translate variable names
- ❌ Don't translate code or CSS classes
- ❌ Don't remove or add HTML tags
## Contributing Translations
To contribute a new translation:
1. Create the PO and MO files following the guidelines above
2. Test thoroughly in a WordPress installation
3. Submit via pull request or contact the developer
4. Include:
- Both PO and MO files
- Your name/credits for attribution
- Screenshot showing the translation in use
## Support
For translation-related questions or to request a new language:
- **Plugin URI**: https://src.bundespruefstelle.ch/wc-tier-package-prices
- **Author**: Marco Graetsch
- **Author URI**: https://src.bundespruefstelle.ch/magdev
## Credits
### Translators
- **English (en_US)**: Marco Graetsch
- **German (de_DE)**: Marco Graetsch
### Translation Tools
- [Poedit](https://poedit.net/) - Cross-platform PO editor
- [Loco Translate](https://wordpress.org/plugins/loco-translate/) - WordPress plugin for translations
- [GNU gettext](https://www.gnu.org/software/gettext/) - Command-line tools
## License
Translation files are distributed under the same GPL v2 or later license as the plugin.

Binary file not shown.

View File

@@ -0,0 +1,163 @@
# German (Switzerland, Informal) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2025 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.0.0\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"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Switzerland)\n"
"Language: de_CH_informal\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0\n"
"X-Domain: wc-tier-package-prices\n"
#: wc-tier-and-package-prices.php:41
msgid "WooCommerce Tier and Package Prices requires WooCommerce to be installed and active."
msgstr "WooCommerce Staffel- und Paketpreise benötigt eine installierte und aktive WooCommerce-Installation."
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
msgid "Tier & Package Prices"
msgstr "Staffel- & Paketpreise"
#: includes/class-wc-tpp-admin.php:54
msgid "Enable Tier Pricing"
msgstr "Staffelpreise aktivieren"
#: includes/class-wc-tpp-admin.php:58
msgid "Enable tier pricing for products"
msgstr "Staffelpreise für Produkte aktivieren"
#: includes/class-wc-tpp-admin.php:63
msgid "Enable Package Pricing"
msgstr "Paketpreise aktivieren"
#: includes/class-wc-tpp-admin.php:67
msgid "Enable fixed-price packages for products"
msgstr "Festpreis-Pakete für Produkte aktivieren"
#: includes/class-wc-tpp-admin.php:72
msgid "Display Pricing Table"
msgstr "Preistabelle anzeigen"
#: includes/class-wc-tpp-admin.php:76
msgid "Show tier and package pricing table on product pages"
msgstr "Staffel- und Paketpreis-Tabelle auf Produktseiten anzeigen"
#: includes/class-wc-tpp-admin.php:81
msgid "Display Position"
msgstr "Anzeigeposition"
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Vor \"In den Warenkorb\""
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
msgstr "Nach \"In den Warenkorb\""
#: includes/class-wc-tpp-admin.php:87
msgid "After Price"
msgstr "Nach dem Preis"
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Staffelpreise"
#: includes/class-wc-tpp-product-meta.php:24
msgid "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Mengenbasierte Preisstaffeln festlegen. Kunden erhalten günstigere Preise beim Kauf grösserer Mengen."
#: includes/class-wc-tpp-product-meta.php:41
msgid "Add Tier"
msgstr "Staffel hinzufügen"
#: includes/class-wc-tpp-product-meta.php:52
msgid "Package Pricing"
msgstr "Paketpreise"
#: includes/class-wc-tpp-product-meta.php:53
msgid "Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Festpreis-Pakete mit bestimmten Mengen festlegen. Zum Beispiel: 10 Stück für CHF 50.-, 25 Stück für CHF 100.-."
#: includes/class-wc-tpp-product-meta.php:70
msgid "Add Package"
msgstr "Paket hinzufügen"
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Mindestmenge"
#: includes/class-wc-tpp-product-meta.php:91
msgid "e.g., 10"
msgstr "z.B. 10"
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
msgid "e.g., 9.99"
msgstr "z.B. 9.90"
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
msgid "Remove"
msgstr "Entfernen"
#: includes/class-wc-tpp-product-meta.php:109
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr "Menge"
#: includes/class-wc-tpp-product-meta.php:113
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/class-wc-tpp-product-meta.php:117
msgid "Label (Optional)"
msgstr "Bezeichnung (Optional)"
#: includes/class-wc-tpp-product-meta.php:118
msgid "e.g., Starter Pack"
msgstr "z.B. Starter-Paket"
#: includes/class-wc-tpp-frontend.php:71
msgid "Volume Discounts"
msgstr "Mengenrabatte"
#: includes/class-wc-tpp-product-meta.php:94
#: includes/class-wc-tpp-frontend.php:76
msgid "Price per Unit"
msgstr "Preis pro Einheit"
#: includes/class-wc-tpp-frontend.php:77
msgid "You Save"
msgstr "Du sparst"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Paketangebote"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "Stück"
#: includes/class-wc-tpp-frontend.php:129
msgid "per unit"
msgstr "pro Einheit"
#: includes/class-wc-tpp-frontend.php:133
msgid "Select Package"
msgstr "Paket auswählen"
#: includes/class-wc-tpp-cart.php:63
msgid "Package price"
msgstr "Paketpreis"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Mengenrabatt"

Binary file not shown.

View File

@@ -0,0 +1,163 @@
# German (Germany) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2025 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.0.0\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"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0\n"
"X-Domain: wc-tier-package-prices\n"
#: wc-tier-and-package-prices.php:41
msgid "WooCommerce Tier and Package Prices requires WooCommerce to be installed and active."
msgstr "WooCommerce Staffel- und Paketpreise erfordert, dass WooCommerce installiert und aktiviert ist."
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
msgid "Tier & Package Prices"
msgstr "Staffel- & Paketpreise"
#: includes/class-wc-tpp-admin.php:54
msgid "Enable Tier Pricing"
msgstr "Staffelpreise aktivieren"
#: includes/class-wc-tpp-admin.php:58
msgid "Enable tier pricing for products"
msgstr "Staffelpreise für Produkte aktivieren"
#: includes/class-wc-tpp-admin.php:63
msgid "Enable Package Pricing"
msgstr "Paketpreise aktivieren"
#: includes/class-wc-tpp-admin.php:67
msgid "Enable fixed-price packages for products"
msgstr "Festpreis-Pakete für Produkte aktivieren"
#: includes/class-wc-tpp-admin.php:72
msgid "Display Pricing Table"
msgstr "Preistabelle anzeigen"
#: includes/class-wc-tpp-admin.php:76
msgid "Show tier and package pricing table on product pages"
msgstr "Staffel- und Paketpreis-Tabelle auf Produktseiten anzeigen"
#: includes/class-wc-tpp-admin.php:81
msgid "Display Position"
msgstr "Anzeigeposition"
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Vor \"In den Warenkorb\""
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
msgstr "Nach \"In den Warenkorb\""
#: includes/class-wc-tpp-admin.php:87
msgid "After Price"
msgstr "Nach dem Preis"
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Staffelpreise"
#: includes/class-wc-tpp-product-meta.php:24
msgid "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Mengenbasierte Preisstaffeln festlegen. Kunden erhalten Rabatte beim Kauf größerer Mengen."
#: includes/class-wc-tpp-product-meta.php:41
msgid "Add Tier"
msgstr "Staffel hinzufügen"
#: includes/class-wc-tpp-product-meta.php:52
msgid "Package Pricing"
msgstr "Paketpreise"
#: includes/class-wc-tpp-product-meta.php:53
msgid "Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Festpreis-Pakete mit bestimmten Mengen festlegen. Zum Beispiel: 10 Stück für 50€, 25 Stück für 100€."
#: includes/class-wc-tpp-product-meta.php:70
msgid "Add Package"
msgstr "Paket hinzufügen"
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Mindestmenge"
#: includes/class-wc-tpp-product-meta.php:91
msgid "e.g., 10"
msgstr "z.B. 10"
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
msgid "e.g., 9.99"
msgstr "z.B. 9,99"
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
msgid "Remove"
msgstr "Entfernen"
#: includes/class-wc-tpp-product-meta.php:109
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr "Menge"
#: includes/class-wc-tpp-product-meta.php:113
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/class-wc-tpp-product-meta.php:117
msgid "Label (Optional)"
msgstr "Bezeichnung (Optional)"
#: includes/class-wc-tpp-product-meta.php:118
msgid "e.g., Starter Pack"
msgstr "z.B. Starter-Paket"
#: includes/class-wc-tpp-frontend.php:71
msgid "Volume Discounts"
msgstr "Mengenrabatte"
#: includes/class-wc-tpp-product-meta.php:94
#: includes/class-wc-tpp-frontend.php:76
msgid "Price per Unit"
msgstr "Preis pro Einheit"
#: includes/class-wc-tpp-frontend.php:77
msgid "You Save"
msgstr "Sie sparen"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Paketangebote"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "Stück"
#: includes/class-wc-tpp-frontend.php:129
msgid "per unit"
msgstr "pro Einheit"
#: includes/class-wc-tpp-frontend.php:133
msgid "Select Package"
msgstr "Paket auswählen"
#: includes/class-wc-tpp-cart.php:63
msgid "Package price"
msgstr "Paketpreis"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Mengenrabatt"

Binary file not shown.

View File

@@ -0,0 +1,163 @@
# English (US) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2025 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.0.0\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"
"Last-Translator: Marco Graetsch\n"
"Language-Team: English\n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0\n"
"X-Domain: wc-tier-package-prices\n"
#: wc-tier-and-package-prices.php:41
msgid "WooCommerce Tier and Package Prices requires WooCommerce to be installed and active."
msgstr "WooCommerce Tier and Package Prices requires WooCommerce to be installed and active."
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
msgid "Tier & Package Prices"
msgstr "Tier & Package Prices"
#: includes/class-wc-tpp-admin.php:54
msgid "Enable Tier Pricing"
msgstr "Enable Tier Pricing"
#: includes/class-wc-tpp-admin.php:58
msgid "Enable tier pricing for products"
msgstr "Enable tier pricing for products"
#: includes/class-wc-tpp-admin.php:63
msgid "Enable Package Pricing"
msgstr "Enable Package Pricing"
#: includes/class-wc-tpp-admin.php:67
msgid "Enable fixed-price packages for products"
msgstr "Enable fixed-price packages for products"
#: includes/class-wc-tpp-admin.php:72
msgid "Display Pricing Table"
msgstr "Display Pricing Table"
#: includes/class-wc-tpp-admin.php:76
msgid "Show tier and package pricing table on product pages"
msgstr "Show tier and package pricing table on product pages"
#: includes/class-wc-tpp-admin.php:81
msgid "Display Position"
msgstr "Display Position"
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Before Add to Cart"
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
msgstr "After Add to Cart"
#: includes/class-wc-tpp-admin.php:87
msgid "After Price"
msgstr "After Price"
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Tier Pricing"
#: includes/class-wc-tpp-product-meta.php:24
msgid "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
#: includes/class-wc-tpp-product-meta.php:41
msgid "Add Tier"
msgstr "Add Tier"
#: includes/class-wc-tpp-product-meta.php:52
msgid "Package Pricing"
msgstr "Package Pricing"
#: includes/class-wc-tpp-product-meta.php:53
msgid "Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
#: includes/class-wc-tpp-product-meta.php:70
msgid "Add Package"
msgstr "Add Package"
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr "Minimum Quantity"
#: includes/class-wc-tpp-product-meta.php:91
msgid "e.g., 10"
msgstr "e.g., 10"
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
msgid "e.g., 9.99"
msgstr "e.g., 9.99"
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
msgid "Remove"
msgstr "Remove"
#: includes/class-wc-tpp-product-meta.php:109
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr "Quantity"
#: includes/class-wc-tpp-product-meta.php:113
msgid "Fixed Price"
msgstr "Fixed Price"
#: includes/class-wc-tpp-product-meta.php:117
msgid "Label (Optional)"
msgstr "Label (Optional)"
#: includes/class-wc-tpp-product-meta.php:118
msgid "e.g., Starter Pack"
msgstr "e.g., Starter Pack"
#: includes/class-wc-tpp-frontend.php:71
msgid "Volume Discounts"
msgstr "Volume Discounts"
#: includes/class-wc-tpp-product-meta.php:94
#: includes/class-wc-tpp-frontend.php:76
msgid "Price per Unit"
msgstr "Price per Unit"
#: includes/class-wc-tpp-frontend.php:77
msgid "You Save"
msgstr "You Save"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Package Deals"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "pieces"
#: includes/class-wc-tpp-frontend.php:129
msgid "per unit"
msgstr "per unit"
#: includes/class-wc-tpp-frontend.php:133
msgid "Select Package"
msgstr "Select Package"
#: includes/class-wc-tpp-cart.php:63
msgid "Package price"
msgstr "Package price"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Volume discount"

View File

@@ -0,0 +1,166 @@
# Copyright (C) 2025 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.0.0\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2025-12-21 00:00+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2025-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"X-Generator: Poedit 3.0\n"
"X-Domain: wc-tier-package-prices\n"
#: wc-tier-and-package-prices.php:41
msgid "WooCommerce Tier and Package Prices requires WooCommerce to be installed and active."
msgstr ""
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
msgid "Tier & Package Prices"
msgstr ""
#: includes/class-wc-tpp-admin.php:54
msgid "Enable Tier Pricing"
msgstr ""
#: includes/class-wc-tpp-admin.php:58
msgid "Enable tier pricing for products"
msgstr ""
#: includes/class-wc-tpp-admin.php:63
msgid "Enable Package Pricing"
msgstr ""
#: includes/class-wc-tpp-admin.php:67
msgid "Enable fixed-price packages for products"
msgstr ""
#: includes/class-wc-tpp-admin.php:72
msgid "Display Pricing Table"
msgstr ""
#: includes/class-wc-tpp-admin.php:76
msgid "Show tier and package pricing table on product pages"
msgstr ""
#: includes/class-wc-tpp-admin.php:81
msgid "Display Position"
msgstr ""
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr ""
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
msgstr ""
#: includes/class-wc-tpp-admin.php:87
msgid "After Price"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:24
msgid "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr ""
#: includes/class-wc-tpp-product-meta.php:41
msgid "Add Tier"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:52
msgid "Package Pricing"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:53
msgid "Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr ""
#: includes/class-wc-tpp-product-meta.php:70
msgid "Add Package"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:90
msgid "Minimum Quantity"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:91
msgid "e.g., 10"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:94
msgid "Price per Unit"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
msgid "e.g., 9.99"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
msgid "Remove"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:109
msgid "Quantity"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:113
msgid "Fixed Price"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:117
msgid "Label (Optional)"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:118
msgid "e.g., Starter Pack"
msgstr ""
#: includes/class-wc-tpp-frontend.php:71
msgid "Volume Discounts"
msgstr ""
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr ""
#: includes/class-wc-tpp-frontend.php:76
msgid "Price per Unit"
msgstr ""
#: includes/class-wc-tpp-frontend.php:77
msgid "You Save"
msgstr ""
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr ""
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr ""
#: includes/class-wc-tpp-frontend.php:129
msgid "per unit"
msgstr ""
#: includes/class-wc-tpp-frontend.php:133
msgid "Select Package"
msgstr ""
#: includes/class-wc-tpp-cart.php:63
msgid "Package price"
msgstr ""
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr ""

View File

@@ -0,0 +1,36 @@
{#
# Admin Package Row Template
#
# @package WC_Tier_Package_Prices
# @var int index
# @var array package
#}
<div class="wc-tpp-package-row">
<p class="form-field">
<label>{{ 'Quantity'|__('wc-tier-package-prices') }}</label>
<input type="number"
name="_wc_tpp_packages[{{ 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>
<input type="text"
name="_wc_tpp_packages[{{ index|esc_attr }}][price]"
value="{{ package.price|default('')|esc_attr }}"
placeholder="{{ 'e.g., 99.99'|__('wc-tier-package-prices') }}"
class="short wc_input_price">
</p>
<p class="form-field">
<label>{{ 'Label (Optional)'|__('wc-tier-package-prices') }}</label>
<input type="text"
name="_wc_tpp_packages[{{ index|esc_attr }}][label]"
value="{{ package.label|default('')|esc_attr }}"
placeholder="{{ 'e.g., Starter Pack'|__('wc-tier-package-prices') }}"
class="short">
</p>
<button type="button" class="button wc-tpp-remove-package">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</div>

View File

@@ -0,0 +1,53 @@
{#
# Admin Settings Page Template
#
# @package WC_Tier_Package_Prices
#}
<div class="wrap">
<h1>{{ get_admin_page_title()|esc_html }}</h1>
<form action="options.php" method="post">
{{ settings_fields('wc_tpp_settings') }}
<table class="form-table">
<tr>
<th scope="row">
<label for="wc_tpp_enable_tier_pricing">{{ 'Enable Tier Pricing'|__('wc-tier-package-prices') }}</label>
</th>
<td>
<input type="checkbox" id="wc_tpp_enable_tier_pricing" name="wc_tpp_enable_tier_pricing" value="yes" {{ checked(get_option('wc_tpp_enable_tier_pricing'), 'yes')|raw }}>
<p class="description">{{ 'Enable tier pricing for products'|__('wc-tier-package-prices') }}</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wc_tpp_enable_package_pricing">{{ 'Enable Package Pricing'|__('wc-tier-package-prices') }}</label>
</th>
<td>
<input type="checkbox" id="wc_tpp_enable_package_pricing" name="wc_tpp_enable_package_pricing" value="yes" {{ checked(get_option('wc_tpp_enable_package_pricing'), 'yes')|raw }}>
<p class="description">{{ 'Enable fixed-price packages for products'|__('wc-tier-package-prices') }}</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wc_tpp_display_table">{{ 'Display Pricing Table'|__('wc-tier-package-prices') }}</label>
</th>
<td>
<input type="checkbox" id="wc_tpp_display_table" name="wc_tpp_display_table" value="yes" {{ checked(get_option('wc_tpp_display_table'), 'yes')|raw }}>
<p class="description">{{ 'Show tier and package pricing table on product pages'|__('wc-tier-package-prices') }}</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wc_tpp_display_position">{{ 'Display Position'|__('wc-tier-package-prices') }}</label>
</th>
<td>
<select id="wc_tpp_display_position" name="wc_tpp_display_position">
<option value="before_add_to_cart" {{ selected(get_option('wc_tpp_display_position'), 'before_add_to_cart')|raw }}>{{ 'Before Add to Cart'|__('wc-tier-package-prices') }}</option>
<option value="after_add_to_cart" {{ selected(get_option('wc_tpp_display_position'), 'after_add_to_cart')|raw }}>{{ 'After Add to Cart'|__('wc-tier-package-prices') }}</option>
<option value="after_price" {{ selected(get_option('wc_tpp_display_position'), 'after_price')|raw }}>{{ 'After Price'|__('wc-tier-package-prices') }}</option>
</select>
</td>
</tr>
</table>
{{ submit_button() }}
</form>
</div>

View File

@@ -0,0 +1,28 @@
{#
# Admin Tier Row Template
#
# @package WC_Tier_Package_Prices
# @var int index
# @var array tier
#}
<div class="wc-tpp-tier-row">
<p class="form-field">
<label>{{ 'Minimum Quantity'|__('wc-tier-package-prices') }}</label>
<input type="number"
name="_wc_tpp_tiers[{{ 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>
<input type="text"
name="_wc_tpp_tiers[{{ index|esc_attr }}][price]"
value="{{ tier.price|default('')|esc_attr }}"
placeholder="{{ 'e.g., 9.99'|__('wc-tier-package-prices') }}"
class="short wc_input_price">
</p>
<button type="button" class="button wc-tpp-remove-tier">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</div>

View File

@@ -0,0 +1,35 @@
{#
# Frontend Package Pricing Display Template
#
# @package WC_Tier_Package_Prices
# @var array packages
#}
<div class="wc-tpp-package-pricing-table">
<h3>{{ 'Package Deals'|__('wc-tier-package-prices') }}</h3>
<div class="wc-tpp-packages">
{% for index, package in packages %}
{% set price_per_unit = package.qty > 0 ? package.price / package.qty : 0 %}
<div class="wc-tpp-package" data-qty="{{ package.qty|esc_attr }}" data-price="{{ package.price|esc_attr }}">
<div class="wc-tpp-package-header">
{% if package.label is not empty %}
<h4>{{ package.label|esc_html }}</h4>
{% endif %}
</div>
<div class="wc-tpp-package-details">
<div class="wc-tpp-package-qty">
<strong>{{ package.qty|esc_html }}</strong> {{ 'pieces'|__('wc-tier-package-prices') }}
</div>
<div class="wc-tpp-package-price">
{{ wc_price(package.price)|raw }}
</div>
<div class="wc-tpp-package-unit-price">
{{ wc_price(price_per_unit)|raw }} {{ 'per unit'|__('wc-tier-package-prices') }}
</div>
</div>
<button type="button" class="button wc-tpp-select-package" data-package-index="{{ index|esc_attr }}">
{{ 'Select Package'|__('wc-tier-package-prices') }}
</button>
</div>
{% endfor %}
</div>
</div>

View File

@@ -0,0 +1,17 @@
{#
# Frontend Pricing Table Template
#
# @package WC_Tier_Package_Prices
# @var object product
# @var array tiers
# @var array packages
#}
<div class="wc-tpp-pricing-container">
{% if tiers is not empty and get_option('wc_tpp_enable_tier_pricing') == 'yes' %}
{% 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' %}
{% include 'frontend/package-pricing-display.twig' with {'packages': packages} %}
{% endif %}
</div>

View File

@@ -0,0 +1,41 @@
{#
# Frontend Tier Pricing Table Template
#
# @package WC_Tier_Package_Prices
# @var object product
# @var array tiers
#}
<div class="wc-tpp-tier-pricing-table">
<h3>{{ 'Volume Discounts'|__('wc-tier-package-prices') }}</h3>
<table class="wc-tpp-table">
<thead>
<tr>
<th>{{ 'Quantity'|__('wc-tier-package-prices') }}</th>
<th>{{ 'Price per Unit'|__('wc-tier-package-prices') }}</th>
<th>{{ 'You Save'|__('wc-tier-package-prices') }}</th>
</tr>
</thead>
<tbody>
{% set regular_price = product.get_regular_price() %}
{% for tier in tiers %}
{% set savings = 0 %}
{% set savings_percent = 0 %}
{% if regular_price > 0 %}
{% set savings = regular_price - tier.price %}
{% set savings_percent = (savings / regular_price) * 100 %}
{% endif %}
<tr data-min-qty="{{ tier.min_qty|esc_attr }}" data-price="{{ tier.price|esc_attr }}">
<td>{{ tier.min_qty|esc_html }}+</td>
<td>{{ wc_price(tier.price)|raw }}</td>
<td>
{% if savings > 0 %}
{{ wc_price(savings)|raw }} ({{ savings_percent|round(2) }}%)
{% else %}
-
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View File

@@ -0,0 +1,111 @@
<?php
/**
* Plugin Name: WooCommerce Tier and Package Prices
* Plugin URI: https://src.bundespruefstelle.ch/wc-tier-package-prices
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
* Version: 1.0.1
* Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev
* Text Domain: wc-tier-package-prices
* Domain Path: /languages
* Requires at least: 6.0
* Requires PHP: 7.4
* WC requires at least: 8.0
* WC tested up to: 10.0
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
// Define plugin constants
define('WC_TPP_VERSION', '1.0.1');
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WC_TPP_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WC_TPP_PLUGIN_BASENAME', plugin_basename(__FILE__));
// Load Composer autoloader
require_once WC_TPP_PLUGIN_DIR . 'vendor/autoload.php';
/**
* Check if WooCommerce is active
*/
if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
add_action('admin_notices', 'wc_tpp_woocommerce_missing_notice');
return;
}
function wc_tpp_woocommerce_missing_notice() {
?>
<div class="notice notice-error">
<p><?php _e('WooCommerce Tier and Package Prices requires WooCommerce to be installed and active.', 'wc-tier-package-prices'); ?></p>
</div>
<?php
}
/**
* Main plugin class
*/
class WC_Tier_Package_Prices {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
$this->includes();
}
private function init_hooks() {
add_action('plugins_loaded', array($this, 'load_textdomain'));
add_action('before_woocommerce_init', array($this, 'declare_hpos_compatibility'));
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
}
private function includes() {
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-template-loader.php';
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-admin.php';
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-product-meta.php';
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-frontend.php';
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-cart.php';
}
public function declare_hpos_compatibility() {
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
}
}
public function load_textdomain() {
load_plugin_textdomain('wc-tier-package-prices', false, dirname(WC_TPP_PLUGIN_BASENAME) . '/languages');
}
public function activate() {
// Add default options
add_option('wc_tpp_enable_tier_pricing', 'yes');
add_option('wc_tpp_enable_package_pricing', 'yes');
add_option('wc_tpp_display_table', 'yes');
flush_rewrite_rules();
}
public function deactivate() {
flush_rewrite_rules();
}
}
// Initialize the plugin
function wc_tpp_init() {
return WC_Tier_Package_Prices::get_instance();
}
add_action('plugins_loaded', 'wc_tpp_init', 11);