85 Commits

Author SHA1 Message Date
fa26247d1b Version 1.4.1 - Localhost license bypass and auto-updates
All checks were successful
Create Release Package / build-release (push) Successful in 1m3s
Added localhost/self-licensing license bypass and WordPress auto-update
integration from license server.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:55:41 +01:00
a4b84f7e41 Fix: Exclude CLAUDE.md from vendor symlink path
All checks were successful
Create Release Package / build-release (push) Successful in 1m4s
Zip follows symlinks, so vendor/magdev/wc-licensed-product-client/CLAUDE.md
was being included. Added exclusions for both lib/ and vendor/magdev/ paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:33:28 +01:00
2e9c948a07 Remove version field from composer.json
Some checks failed
Create Release Package / build-release (push) Failing after 58s
The version field causes composer validate --strict to fail with a warning.
Version is determined by git tags instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:18:34 +01:00
dd4333bd11 Update documentation for lib/ submodule location
Some checks failed
Create Release Package / build-release (push) Failing after 4m44s
- Update file structure in README.md and CLAUDE.md
- Document lib/ directory for git submodules
- Update submodule instructions with relative URL
- Add key learnings about vendor/ vs lib/ conflict

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:10:53 +01:00
b909221ae2 Fix CI/CD: Move submodule to lib/ directory like wp-fedistream
- Move submodule from vendor/magdev/ to lib/ to avoid Composer conflicts
- Use relative submodule URL (../wc-licensed-product-client.git)
- Pin submodule to v0.2.2 tag
- Update composer.json with ^0.2 version constraint
- Simplify .gitignore (no vendor exceptions needed)
- Update workflow to exclude lib/.git instead of vendor/.git

Based on working wp-fedistream implementation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:09:45 +01:00
d80c9d90f9 Update documentation for v1.4.0 CI/CD release process
Some checks failed
Create Release Package / build-release (push) Failing after 4m36s
- README: Update automated releases section with submodule info
- README: Update file structure with .gitea/workflows and submodule
- README: Add v1.4.0 changelog entry
- CLAUDE.md: Update version to 1.4.0, roadmap to 1.4.1
- CLAUDE.md: Rewrite release process for CI/CD workflow
- CLAUDE.md: Add git submodule documentation
- CLAUDE.md: Add v1.4.0 session history

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:03:54 +01:00
2bf0cd82fe Add wc-licensed-product-client as git submodule
Some checks failed
Create Release Package / build-release (push) Failing after 4m36s
- Bundle magdev/wc-licensed-product-client as git submodule
- Update composer.json to use path repository instead of VCS
- Update .gitignore to allow submodule in vendor directory
- Update CI workflow to checkout submodules recursively
- Remove private repository authentication step (no longer needed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:58:01 +01:00
9451cc1965 Version 1.4.0 - Add Gitea CI/CD release pipeline
Some checks failed
Create Release Package / build-release (push) Failing after 1m2s
- Automated release workflow triggered on version tags (v*)
- Validates plugin version matches tag
- Installs production Composer dependencies
- Compiles translation files
- Creates release package with proper exclusions
- Generates SHA256 checksum
- Publishes release to Gitea with changelog notes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:53:39 +01:00
02b0308058 Add Gitea CI/CD release pipeline
- Create automated release workflow triggered on version tags (v*)
- Validates plugin version matches tag version
- Installs production dependencies via Composer
- Compiles translation files (.po to .mo)
- Builds release package with proper exclusions
- Generates SHA256 checksum
- Publishes release to Gitea with changelog notes
- Document automated release process in README

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:49:29 +01:00
38e9506d4e Update v1.3.1 session learnings with release package fixes
- Document vendor .git exclusion requirement for release packages
- Update package size to 775KB (650 files) after proper exclusions
- Add correct zip parent directory path for release creation
- Document .claude/settings.json addition to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:36:56 +01:00
74cc56005e Add .claude/settings.json to gitignore
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:33:15 +01:00
136eed2bdd Document v1.3.1 session learnings in CLAUDE.md
- Update version to 1.3.1 and date to 2026-01-27
- Replace completed v1.3.1 roadmap with v1.3.2 placeholder
- Update license client dependency to dev-main
- Add v1.3.1 session history documenting SecureLicenseClient switch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:31:06 +01:00
178f86f3e6 Update README for v1.3.1 release
- Added license management feature to admin features list
- Updated PHP requirement from 7.4 to 8.3
- Updated file structure version comment to v1.3.1
- Added v1.3.0 and v1.3.1 changelog entries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:27:01 +01:00
7286459ff2 Version 1.3.1 - Switch to SecureLicenseClient with signature verification
- Upgraded from LicenseClient to SecureLicenseClient with HMAC-SHA256 response signature verification
- Added Server Secret configuration field for secure communication
- Added rate limit exception handling with retry time display
- Added signature verification error handling
- Added URL validation error handling (SSRF protection)
- Updated all translation files with new strings
- Compiled .mo files for all 7 language variants

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:23:42 +01:00
cbe758267e Document v1.3.0 session learnings in CLAUDE.md
- Updated PHP requirement from 7.4 to 8.3 in compatibility notes
- Added Session History section documenting v1.3.0 release session
- Documented key learnings about license client integration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:44:16 +01:00
74c14581f1 Release version 1.3.0 - License management and settings sub-tabs
Breaking Changes:
- PHP 8.3+ now required (previously 7.4+)

Added:
- License management integration using magdev/wc-licensed-product-client
- Settings page split into General and License sub-tabs
- License validation and activation via AJAX
- PHP version check with admin notice

Changed:
- Refactored settings class to use modern WooCommerce patterns
- Updated all translations with new license-related strings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:39:12 +01:00
0dbe18d954 Remove releases from git tracking and clean up MD5 checksums
- Remove all release packages from git tracking (kept locally)
- Delete MD5 checksum files, keeping only SHA256
- Update .gitignore to exclude releases/ directory
- Update CLAUDE.md release documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:17:04 +01:00
63c8137f4e Document v1.2.9 learnings in CLAUDE.md
Added three new critical sections documenting lessons learned during v1.2.9 development:

1. WordPress Translation Functions with printf
   - Correct pattern: printf(esc_html__('Text (%s)', 'domain'), value)
   - Wrong pattern: printf(__('Text (%s)', 'domain'), value) - missing text domain
   - Explains why text domain must be in translation function
   - Shows proper output escaping with esc_html

2. Twig Translation Filters and HTML Entity Encoding
   - Explains why translation filters encode special characters in concatenated strings
   - Correct pattern: {{ 'text ' ~ currency_symbol }} (no translation filter)
   - Wrong pattern: {{ ('text ' ~ currency_symbol)|__() }} causes HTML entity encoding
   - Rule: Only translate static text, not concatenated dynamic values

3. Defensive Programming for POST Data Processing
   - Compares v1.2.8 (branching) vs v1.2.9 (defensive) patterns
   - Shows why single decision point is better than multiple branches
   - Key principles: initialize early, minimize branching, guaranteed execution
   - Pattern: initialize → conditionally populate → unconditionally act

Also corrected v1.2.8 examples that had wrong patterns, noting they were fixed in v1.2.9.

These patterns prevent future bugs and ensure consistent, secure implementation.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 05:08:09 +01:00
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
1e6d86ca10 Release version 1.1.22 - UI improvements and bug documentation
- Increased width of label input fields in admin (short → regular)
- Documented double-install bug workaround in CHANGELOG
- Updated version to 1.1.22 across all files
- Created release package with proper exclusions

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 14:33:42 +01:00
f958c7b640 Release version 1.1.21 - Add multilingual support for Swiss locales
Added translations for Swiss German (formal and informal), Swiss French,
and Swiss Italian locales to support multilingual e-commerce in Switzerland.

New Features:
- Added de_CH (Swiss German - formal "Sie") translation
- Added de_DE_informal (Informal German - "du") translation
- Added fr_CH (Swiss French) translation
- Added it_CH (Swiss Italian) translation

Technical Changes:
- Created 4 new .po translation source files
- Compiled all .po files to .mo format for runtime use
- Updated version to 1.1.21 in plugin header and constant
- Updated composer.json version to 1.1.21
- Swiss locales use CHF currency formatting (e.g., "CHF 50.-")
- German informal translations use "du/dein" instead of "Sie/Ihr"

Documentation:
- Updated CHANGELOG.md with v1.1.21 release notes
- Updated CLAUDE.md with current version and translation status
- Marked roadmap item as completed in CLAUDE.md

Release Package:
- Created wc-tier-and-package-prices-1.1.21.zip (404 KB)
- Generated MD5 checksum: 16813b3ed0d1001d5f60194d61d36fc2
- Generated SHA256 checksum: e0063852a9ac23b1fd994471a2829f9dcbe26316f00ddee2d00f77c7c6a47c8f

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 01:44:41 +01:00
b1d31f4894 Update releases README.md to v1.1.20
- Updated latest release to version 1.1.20 (December 23, 2025)
- Added "What's New in 1.1.20" section highlighting critical fixes
- Updated all version references from 1.0.1 to 1.1.20
- Expanded core features list with new capabilities:
  - Tier labels with clickable rows
  - Package quantity restrictions
  - WooCommerce Blocks full support
  - HPOS compatibility
  - View Options catalog buttons
- Updated package contents with complete file structure
- Added all documentation files to contents list
- Enhanced version history with 5 major releases
- Updated system requirements table with "Tested Up To" column
- Confirmed compatibility: WP 6.9.x, WC 10.x, PHP 8.x

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 00:40:12 +01:00
0c75234dcb Update documentation to v1.1.20 with current features
- Updated all version references to 1.1.20
- Updated WordPress requirement to 6.0+ (tested up to 6.9.x)
- Updated WooCommerce requirement to 8.0+ (tested up to 10.x)
- Added comprehensive file structure showing Twig templates
- Added WooCommerce Blocks compatibility documentation
- Added tier labels feature documentation (v1.1.7)
- Added quantity restrictions feature documentation (v1.1.0)
- Added new usage examples for tier labels and quantity restrictions
- Updated INSTALLATION.md with complete directory structure
- Updated README.md with current version changelog
- Updated QUICKSTART.md with 8 pro tips including new features
- Updated USAGE_EXAMPLES.md with 6 complete examples
- Documented WooCommerce Blocks support and v1.1.20 critical fix

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 00:32:19 +01:00
556cba20fa added .claude/ and CLAUDE.md to exclude from release 2025-12-23 00:23:21 +01:00
d721ab123a Release version 1.1.20 - Fix WooCommerce Blocks fatal error
## Fixed
- Fatal error in WooCommerce Blocks: "Cannot use object of type WC_Product_Simple as array"
- Error occurred in class-wc-tpp-cart.php:233 in block_quantity_editable() method

## Technical Details
- Filter woocommerce_store_api_product_quantity_editable passes WC_Product object as second parameter, not cart item array
- Updated method signature from block_quantity_editable($editable, $cart_item) to block_quantity_editable($editable, $product)
- Changed parameter access from $cart_item['id'] to $product->get_id()
- Added proper product object validation with is_a($product, 'WC_Product')

## Added
- CLAUDE.md - Comprehensive AI context document for future development
- Complete project architecture documentation
- Historical bug context and solutions
- Development guidelines and testing checklists

## Release
- Version bumped to 1.1.20
- Release package created with MD5 and SHA256 checksums

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 00:17:01 +01:00
dfe1a4364a added new packages, now it works! 2025-12-22 23:41:31 +01:00
9b7638a7e2 Release version 1.1.19 - Enhanced duplicate detection
Fixed persistent duplicate settings page by improving duplicate detection
to check by class type and ID instead of strict instance comparison.

Issue:
- Strict instance comparison (===) failed to detect duplicates
- Different object instances of same class not caught by previous check
- Settings page still appearing twice despite singleton and instance caching

Solution:
- Changed duplicate detection from instance comparison to type/ID checks
- Added instanceof WC_TPP_Settings check
- Added get_id() method check for ID 'tier_package_prices'
- Added direct id property check as fallback
- Multiple layers of detection to catch duplicates regardless of how created

Fixes:
- Settings page rendering twice in WooCommerce backend
- Duplicate detection failing for different instances of same class

Changes:
- Enhanced add_settings_page() with multi-layer duplicate detection
- Checks: instanceof, get_id(), and id property
- Returns early if any existing page matches criteria

Technical Details:
- Strict comparison only works if exact same object instance
- Different instances (even of same class/ID) fail === check
- instanceof checks class type regardless of which instance
- ID checks ensure no duplicate pages with same identifier
- Fallback checks handle different WooCommerce versions/implementations

Updated Files:
- includes/class-wc-tpp-admin.php (enhanced duplicate detection)
- wc-tier-and-package-prices.php (version 1.1.19)
- composer.json (version 1.1.19)
- CHANGELOG.md (v1.1.19 section)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 23:33:12 +01:00
db9ba2bacd Release version 1.1.18 - Fix root cause of duplicate settings
Identified and fixed the actual root cause of duplicate settings page:
automatic instantiation in settings file being executed multiple times.

Root Cause Analysis:
- Settings file ended with: return new WC_TPP_Settings();
- File is in Composer's classmap for autoloading
- When autoloader loads class, it executes the entire file including instantiation
- Each include/autoload created a NEW instance despite admin singleton pattern
- Admin singleton prevented multiple admin instances but not multiple settings instances

The Fix:
- Removed automatic instantiation (return new) from settings file
- Settings file now only contains class definition
- Admin class explicitly creates instance: new WC_TPP_Settings()
- Changed from include to require_once to prevent multiple file loads
- Settings instance now created exactly once, when filter needs it

Fixes:
- Settings page rendering twice in WooCommerce backend
- Multiple WC_TPP_Settings instances being created
- Composer autoload triggering unintended instantiation

Changes:
- Removed line 145 from class-wc-tpp-settings.php (return new WC_TPP_Settings())
- Modified add_settings_page() to use require_once + explicit new
- Settings file now side-effect free, safe for autoloading

Technical Details:
- Composer classmap autoloader includes files when class is referenced
- Previous code had side effect (instantiation) on every include
- New code separates class definition from instantiation
- Instance creation now controlled explicitly by admin class
- require_once ensures file loaded once even if accessed multiple times

Updated Files:
- includes/class-wc-tpp-settings.php (removed return new line)
- includes/class-wc-tpp-admin.php (explicit instantiation)
- wc-tier-and-package-prices.php (version 1.1.18)
- composer.json (version 1.1.18)
- CHANGELOG.md (v1.1.18 section)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 23:29:35 +01:00
e46372da51 Release version 1.1.17 - Array duplicate prevention
Fixed persistent duplicate settings page by adding array-level duplicate
detection in addition to singleton pattern from v1.1.16.

Root Cause:
- WooCommerce calls woocommerce_get_settings_pages filter multiple times
- Even with singleton pattern, each filter call added settings instance to array
- Singleton prevented multiple class instances but not multiple array entries

Fixes:
- Settings page rendering twice despite singleton pattern in v1.1.16
- Filter adding same settings instance to array on repeated calls

Changes:
- Added duplicate detection loop in add_settings_page() before array append
- Uses strict comparison (===) to check if instance already in array
- Returns early if settings page already present, preventing duplicate

Technical Details:
- foreach loop iterates through existing $settings array
- Compares each element against cached self::$settings_instance
- Only appends to array if instance not found
- Complements singleton pattern with array-level protection
- Handles WooCommerce calling filter multiple times during page load

Updated Files:
- includes/class-wc-tpp-admin.php (added duplicate check in filter)
- wc-tier-and-package-prices.php (version 1.1.17)
- composer.json (version 1.1.17)
- CHANGELOG.md (v1.1.17 section)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 23:05:11 +01:00
2b2c06794b Release version 1.1.16 - Singleton pattern for settings page
Fixed persistent duplicate settings page rendering by implementing proper
singleton pattern for admin class and caching settings instance.

Fixes:
- Settings page still appearing twice despite v1.1.15 fix
- Multiple instantiation of WC_TPP_Admin class
- Duplicate creation of WC_TPP_Settings instances

Changes:
- Implemented singleton pattern for WC_TPP_Admin class
- Added private static $instance property with get_instance() method
- Made WC_TPP_Admin constructor private
- Added static $settings_instance property to cache settings page
- Modified add_settings_page() to check and reuse cached settings instance
- Changed instantiation from new WC_TPP_Admin() to WC_TPP_Admin::get_instance()

Technical Details:
- Ensures only one WC_TPP_Admin instance exists throughout plugin lifecycle
- Prevents duplicate filter registrations even if woocommerce_get_settings_pages called multiple times
- Settings page object created once and reused on subsequent filter calls
- Follows WordPress/WooCommerce best practices for singleton implementation

Updated Files:
- includes/class-wc-tpp-admin.php (singleton pattern implementation)
- wc-tier-and-package-prices.php (version 1.1.16)
- composer.json (version 1.1.16)
- CHANGELOG.md (v1.1.16 section)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 20:01:27 +01:00
959229b9d8 Release version 1.1.15 - Fix duplicate settings page
Fixed settings page appearing twice in WooCommerce settings due to double instantiation of WC_TPP_Settings class.

**Issue:**
- Settings page rendered twice on same page
- WC_TPP_Settings class instantiated twice: once automatically in settings file, once via admin class include

**Fix:**
- Removed conditional wrapper `if (class_exists('WC_TPP_Settings'))` from settings return statement
- Settings class now only instantiated when admin class includes the file via `return new WC_TPP_Settings();`
- Restored v1.1.2 pattern for settings file

**Files Modified:**
- includes/class-wc-tpp-settings.php (simplified return statement)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 19:39:20 +01:00
f0ab2ff755 Release version 1.1.14 - Restore plugin functionality
**CRITICAL FIX:** Restored plugin to working state after v1.1.8-1.1.13 were completely non-functional.

**Root Cause:**
- v1.1.8 moved class instantiation from individual files to init_classes() method
- v1.1.13 wrapped classes in class_exists() guards
- Combination prevented any classes from being instantiated
- Result: No settings, no frontend, no backend functionality

**Solution:**
- Reverted to v1.1.2 pattern (last working version)
- Each class file now instantiates itself with `new ClassName();`
- Removed init_classes() method and woocommerce_loaded hook
- All class_exists() guards remain for redeclaration protection

**What Now Works:**
 WooCommerce Settings → Tier & Package Prices tab
 Product edit pages show tier/package pricing meta boxes
 Frontend displays pricing tables on product pages
 Cart applies tier/package pricing correctly
 All plugin functionality fully operational

**Files Modified:**
- includes/class-wc-tpp-admin.php
- includes/class-wc-tpp-product-meta.php
- includes/class-wc-tpp-frontend.php
- includes/class-wc-tpp-cart.php
- wc-tier-and-package-prices.php (removed init_classes)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 19:32:47 +01:00
4dd9b3cd62 Release version 1.1.13 - Critical class redeclaration fixes
Fixed critical class redeclaration errors affecting all plugin functionality
in version 1.1.12. All plugin component classes now properly guarded.

**CRITICAL FIXES:**
- Plugin completely non-functional in v1.1.12 (no settings, no frontend, no backend)
- Fatal errors for WC_TPP_Admin, WC_TPP_Product_Meta, WC_TPP_Frontend, WC_TPP_Cart, WC_TPP_Settings classes
- All classes now wrapped in class_exists() checks

**Files Modified:**
- includes/class-wc-tpp-admin.php
- includes/class-wc-tpp-product-meta.php
- includes/class-wc-tpp-frontend.php
- includes/class-wc-tpp-cart.php
- includes/class-wc-tpp-settings.php

**Technical Details:**
- Completes comprehensive redeclaration protection started in v1.1.9-1.1.12
- All 2 functions, 4 constants, and 6 classes now protected
- Plugin activates successfully and all features functional
- Settings page, product meta boxes, frontend display, cart integration all working

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 19:02:18 +01:00
9dab123209 Release version 1.1.12 - Final redeclaration fix
Fixed critical class redeclaration error for WC_Tier_Package_Prices
affecting version 1.1.11. This completes all redeclaration protection
by protecting the main plugin class.

Fixes:
- Class redeclaration error for WC_Tier_Package_Prices
- Fatal error "Cannot redeclare class WC_Tier_Package_Prices" when plugin file loaded multiple times
- Plugin activation failures caused by class redeclaration

Technical Changes:
- Wrapped WC_Tier_Package_Prices class declaration in class_exists() check
- Completes comprehensive redeclaration protection for all plugin components
- All functions, constants, and classes now safely guarded against redeclaration
- Plugin now fully protected from all redeclaration scenarios

Protected Components:
- Functions: wc_tpp_woocommerce_missing_notice(), wc_tpp_init()
- Constants: WC_TPP_VERSION, WC_TPP_PLUGIN_DIR, WC_TPP_PLUGIN_URL, WC_TPP_PLUGIN_BASENAME
- Classes: WC_Tier_Package_Prices

Updated Files:
- wc-tier-and-package-prices.php (version 1.1.12, class protection)
- composer.json (version 1.1.12)
- CHANGELOG.md (v1.1.12 section)
- All translation files compiled (.mo files)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 18:52:34 +01:00
3f117ae519 Release version 1.1.11 - Complete redeclaration protection
Fixed critical constant redeclaration warnings/errors for all plugin
constants affecting versions 1.1.3-1.1.10. This completes comprehensive
protection against all redeclaration issues by protecting constants.

Fixes:
- Constant redeclaration warnings/errors for WC_TPP_VERSION, WC_TPP_PLUGIN_DIR, WC_TPP_PLUGIN_URL, WC_TPP_PLUGIN_BASENAME
- Potential errors when plugin constants already defined
- Plugin initialization failures caused by constant redeclaration

Technical Changes:
- Wrapped all define() calls in defined() checks
- Protected WC_TPP_VERSION, WC_TPP_PLUGIN_DIR, WC_TPP_PLUGIN_URL, WC_TPP_PLUGIN_BASENAME
- Prevents warnings/errors during WordPress plugin activation/deactivation cycles
- Comprehensive protection: all global functions and constants now safely guarded
- No more redeclaration issues possible

Updated Files:
- wc-tier-and-package-prices.php (version 1.1.11, all constants protected)
- composer.json (version 1.1.11)
- CHANGELOG.md (v1.1.11 section)
- All translation files compiled (.mo files)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 18:48:01 +01:00
58bbd5164f Release version 1.1.10 - Complete function redeclaration fix
Fixed critical function redeclaration error for wc_tpp_init() affecting
version 1.1.9. This completes the fix started in v1.1.9 by protecting
all global functions from redeclaration during plugin lifecycle events.

Fixes:
- Function redeclaration error for wc_tpp_init()
- Fatal error "Cannot redeclare function wc_tpp_init()" when plugin file loaded multiple times
- Plugin activation failures

Technical Changes:
- Wrapped wc_tpp_init() function in function_exists() check
- Both wc_tpp_woocommerce_missing_notice() and wc_tpp_init() now safely guarded
- Prevents fatal errors during WordPress plugin activation/deactivation cycles
- Comprehensive protection for all global function declarations

Updated Files:
- wc-tier-and-package-prices.php (version 1.1.10, wc_tpp_init safety check)
- composer.json (version 1.1.10)
- CHANGELOG.md (v1.1.10 section)
- All translation files compiled (.mo files)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 18:45:05 +01:00
cfdbfe1504 Release version 1.1.9 - Critical function redeclaration fix
Fixed critical function redeclaration error affecting versions 1.1.3-1.1.8
that caused fatal errors during plugin activation and deactivation.

Fixes:
- Function redeclaration error for wc_tpp_woocommerce_missing_notice()
- Fatal error "Cannot redeclare function" when plugin file loaded multiple times
- Plugin activation/deactivation failures

Technical Changes:
- Wrapped wc_tpp_woocommerce_missing_notice() in function_exists() check
- Moved function declaration before WooCommerce check for better organization
- Prevents fatal errors during WordPress plugin lifecycle events

Updated Files:
- wc-tier-and-package-prices.php (version 1.1.9, function safety check)
- composer.json (version 1.1.9)
- CHANGELOG.md (v1.1.9 section)
- All translation files compiled (.mo files)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 18:40:48 +01:00
e4e4de82cb Release version 1.1.8 - Critical activation fix
Fixed critical plugin activation error that was preventing the plugin from
being activated on WordPress 6.9.x and WooCommerce 10.x.

The issue was introduced in v1.1.3 when admin classes were still being
instantiated immediately at file include time, before WooCommerce was loaded.
While v1.1.6 fixed this for Frontend and Cart classes, the Admin and
Product Meta classes were missed.

Critical Fix:
- Removed immediate instantiation from WC_TPP_Admin class
- Removed immediate instantiation from WC_TPP_Product_Meta class
- Both classes now instantiated via woocommerce_loaded hook
- Ensures all WooCommerce hooks are available before registration

Technical Changes:
- Removed "new WC_TPP_Admin();" from class-wc-tpp-admin.php
- Removed "new WC_TPP_Product_Meta();" from class-wc-tpp-product-meta.php
- Added both classes to init_classes() method in main plugin file
- All four main classes now follow consistent initialization pattern

Impact:
- Plugin now activates correctly on all WordPress/WooCommerce versions
- Resolves fatal errors during plugin activation
- Last working version was v1.1.2, now fixed in v1.1.8

Updated Files:
- includes/class-wc-tpp-admin.php (removed auto-instantiation)
- includes/class-wc-tpp-product-meta.php (removed auto-instantiation)
- wc-tier-and-package-prices.php (version 1.1.8, init_classes updated)
- composer.json (version 1.1.8)
- CHANGELOG.md (v1.1.8 section added)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 00:24:12 +01:00
af532b56eb Release version 1.1.7 - Enhanced user experience features
Added three new customer-facing features to improve product page interaction
and tier pricing functionality.

Features:
- Optional text labels for tier pricing (similar to package labels)
- Clickable tier pricing table rows to auto-populate quantity field
- Add to Cart button auto-disables when quantity is 0 or less

Enhanced User Experience:
- Tier pricing rows now clickable with cursor pointer and hover animation
- Clicking tier row sets quantity and smoothly scrolls to quantity field
- Add to Cart button shows disabled state with reduced opacity
- Tier labels display below quantity in italic gray text

Technical Changes:
- Added optional 'label' field to tier pricing admin meta box
- Updated tier save logic to include label field (sanitized)
- Enhanced tier pricing frontend template to display labels
- Added click handler for tier pricing rows in frontend.js
- Added updateAddToCartButton() function to manage button state
- CSS: .wc-tpp-tier-label styling for tier labels
- CSS: Clickable cursor and hover transform for tier rows
- CSS: Disabled button styling (.single_add_to_cart_button:disabled)

Updated Files:
- templates/admin/tier-row.twig (added label field)
- includes/class-wc-tpp-product-meta.php (save label, template update)
- templates/frontend/tier-pricing-table.twig (display labels)
- assets/js/frontend.js (tier row clicks, button disable logic)
- assets/css/frontend.css (tier label style, clickable rows, disabled button)
- wc-tier-and-package-prices.php (version 1.1.7)
- composer.json (version 1.1.7)
- CHANGELOG.md (v1.1.7 section)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 00:15:48 +01:00
e2a11de80a Release version 1.1.6 - Minor improvements
Updated Plugin URI to correct repository path and added notes files to gitignore.

Changes:
- Updated Plugin URI from /wc-tier-package-prices to /magdev/wc-tier-package-prices
- Added notes.* to .gitignore to exclude local notes files

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 00:02:19 +01:00
e40830b69b Release version 1.1.6 - Critical fix for plugin activation
Fixed critical bug that prevented plugin activation in v1.1.3, v1.1.4, and v1.1.5.

Root Cause:
- WC_TPP_Cart and WC_TPP_Frontend classes were instantiated immediately when
  their files were loaded (via `new ClassName();` at bottom of files)
- This happened BEFORE WooCommerce was fully loaded
- Hook registration attempted to access WooCommerce functions before they existed
- Result: Fatal error during plugin activation

Solution:
- Removed immediate instantiation from class-wc-tpp-cart.php (line 251)
- Removed immediate instantiation from class-wc-tpp-frontend.php (line 186)
- Added init_classes() method to main plugin class
- Classes now instantiated via woocommerce_loaded hook
- Ensures WooCommerce is fully initialized before any hooks are registered

Changes:
- includes/class-wc-tpp-cart.php - Removed `new WC_TPP_Cart();`
- includes/class-wc-tpp-frontend.php - Removed `new WC_TPP_Frontend();`
- wc-tier-and-package-prices.php - Added init_classes() and woocommerce_loaded hook
- CHANGELOG.md - Added v1.1.6 section
- composer.json - Version 1.1.6
- All translation files updated to 1.1.6
- All .mo files recompiled

This fix ensures proper WordPress plugin lifecycle:
1. Plugin file loaded
2. Classes defined (but not instantiated)
3. WooCommerce loads
4. woocommerce_loaded hook fires
5. Classes instantiated with full WooCommerce availability

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 19:57:27 +01:00
9765c5f119 Release version 1.1.5 - Critical bug fix for plugin activation
CRITICAL FIX: Resolved fatal error that prevented plugin activation in
versions 1.1.3 and 1.1.4. The add_cart_quantity_css() method was attempting
to access WooCommerce cart object during wp_head action, causing failures
when WooCommerce wasn't fully initialized or on admin pages.

Fixes:
- Plugin activation error in v1.1.3 and v1.1.4
- Fatal error when WooCommerce cart object not available
- Frontend errors on admin pages
- Issues during plugin initialization

Technical Changes:
- Added function_exists('WC') check before accessing WooCommerce
- Added is_admin() check to prevent CSS injection on admin pages
- Enhanced add_cart_quantity_css() with proper guards
- Line 191: if (!function_exists('WC') || !WC()->cart || is_admin())

Root Cause:
The add_cart_quantity_css() method (added in v1.1.3) hooks into wp_head
but didn't properly check if WooCommerce cart was available, causing
errors during plugin activation and on admin pages.

Updated Files:
- includes/class-wc-tpp-cart.php (enhanced error checking)
- wc-tier-and-package-prices.php (version 1.1.5)
- composer.json (version 1.1.5)
- CHANGELOG.md (v1.1.5 section)
- All translation files (.pot, .po, .mo) updated to version 1.1.5

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 19:49:24 +01:00
5cfabedb94 released v1.1.4 2025-12-21 19:36:35 +01:00
88e30d028c Release version 1.1.4 - WooCommerce Blocks support and improved styling
Added comprehensive WooCommerce Blocks support for quantity restrictions
and improved "View Options" button styling to match standard WooCommerce
Add to Cart buttons.

Features:
- WooCommerce Blocks cart/mini-cart quantity restriction support
- Store API integration for block-based carts
- Improved "View Options" button styling with WooCommerce standards
- Enhanced hover effects and transitions

Technical Changes:
- Added woocommerce_store_api_product_quantity_editable filter
- Added block_quantity_editable() method in WC_TPP_Cart class
- Enhanced CSS for .wc-block-components-quantity-selector targeting
- Updated button styling: padding (0.618em × 1em), font-weight (700)
- Added transition effects for smooth hover states

Fixed:
- WooCommerce blocks cart quantity selector now properly hidden
- WooCommerce blocks mini-cart quantity selector visibility
- "View Options" button now matches Add to Cart button appearance

Updated Files:
- includes/class-wc-tpp-cart.php (WooCommerce Blocks support)
- assets/css/frontend.css (enhanced button styling)
- wc-tier-and-package-prices.php (version 1.1.4)
- composer.json (version 1.1.4)
- CHANGELOG.md (v1.1.4 section)
- All translation files (.pot, .po, .mo) updated to version 1.1.4

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 19:33:34 +01:00
d55ada7924 Release version 1.1.3 - Cart quantity visibility fix
Fixed persistent cart quantity input visibility issues for products with
package quantity restrictions. Enhanced implementation ensures quantity
inputs are properly hidden in both main cart and mini-cart/sidebar.

Fixes:
- Cart quantity inputs now properly hidden with increased filter priority
- Mini-cart quantity inputs correctly replaced with read-only text
- Added fallback CSS injection to handle theme/plugin conflicts
- Enhanced DOM targeting with data attributes and multiple CSS selectors

Technical Changes:
- Increased filter priority to 999 for woocommerce_cart_item_quantity
- Added woocommerce_widget_cart_item_quantity filter for mini-cart support
- Added add_cart_quantity_css() method for dynamic CSS injection
- Added maybe_hide_mini_cart_quantity_input() method
- Enhanced quantity spans with data-product-id attribute
- Added wc-tpp-restricted-qty CSS class
- Implemented sibling (+) and general sibling (~) CSS selectors

Updated Files:
- includes/class-wc-tpp-cart.php (enhanced with mini-cart support)
- wc-tier-and-package-prices.php (version 1.1.3)
- composer.json (version 1.1.3)
- CHANGELOG.md (v1.1.3 section)
- All translation files (.pot, .po, .mo) updated to version 1.1.3

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 19:19:18 +01:00
4ece4dd69e Release version 1.1.2 - Catalog button modification
Enhanced package quantity restriction enforcement by replacing "Add to Cart"
buttons with "View Options" links on catalog pages for products with
quantity restrictions. This prevents customers from attempting to add
restricted products directly from shop/category pages.

Changes:
- Added catalog button modification for restricted products
- Implemented "View Options" button with eye icon styling
- Created has_quantity_restriction() helper method
- Extended CSS loading to all WooCommerce pages
- Added modify_catalog_add_to_cart_button() filter method
- Updated translations with 2 new strings (en_US, de_DE, de_CH_informal)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 19:04:41 +01:00
3e06137559 Release version 1.1.1 - Cart quantity field hiding
Enhanced package quantity restriction enforcement by hiding the cart
quantity input field when restrictions are enabled. This prevents
customers from modifying quantities in the cart to bypass package
restrictions.

Changes:
- Added cart quantity input hiding for restricted products
- Implemented woocommerce_cart_item_quantity filter hook
- Created maybe_hide_cart_quantity_input() method in WC_TPP_Cart
- Fixed cart quantity bypass vulnerability
- Cart displays quantity as read-only text for restricted products

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 17:21:40 +01:00
e0a32821ee Release version 1.1.0 - Package quantity restriction feature
Added comprehensive package quantity restriction functionality that allows
limiting product purchases to predefined package sizes only.

Features:
- Global setting to enable package quantity restrictions
- Per-product override for quantity restrictions
- Automatic hiding of quantity input field when restricted
- Frontend validation with package selection UI
- Server-side cart validation
- User-friendly error messages
- Complete translations for all supported languages

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 15:54:04 +01:00
dea2c5f0b3 Release version 1.0.2
- Migrate settings to WooCommerce Settings page
- Add WC_TPP_Settings class for proper WooCommerce integration
- Remove standalone settings submenu
- Improve UX with native WooCommerce settings UI
2025-12-21 05:14:19 +01:00
46 changed files with 9272 additions and 364 deletions

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

View File

@@ -5,7 +5,44 @@
"Bash(msgfmt:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(composer init:*)"
"Bash(composer init:*)",
"Bash(composer install:*)",
"Bash(composer update:*)",
"Bash(git add:*)",
"Bash(git tag:*)",
"Bash(rsync:*)",
"Bash(zip -r:*)",
"Bash(cat:*)",
"Bash(for po in *.po)",
"Bash(do msgfmt -o \"$po%.po.mo\" \"$po\")",
"Bash(done)",
"Bash(git commit:*)",
"Bash(node -c:*)",
"Bash(php -l:*)",
"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:*)"
]
}
}

View File

@@ -0,0 +1,217 @@
name: Create Release Package
on:
push:
tags:
- 'v*'
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, xml, zip, intl, gettext
tools: composer:v2
- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF_NAME#v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Building version: $VERSION"
- name: Validate composer.json
run: composer validate --strict
- name: Install Composer dependencies (production)
run: |
composer config platform.php 8.3.0
composer install --no-dev --optimize-autoloader --no-interaction
- name: Install gettext
run: apt-get update && apt-get install -y gettext
- name: Compile translations
run: |
for po in languages/*.po; do
if [ -f "$po" ]; then
mo="${po%.po}.mo"
echo "Compiling $po to $mo"
msgfmt -o "$mo" "$po"
fi
done
- name: Verify plugin version matches tag
run: |
PLUGIN_VERSION=$(grep -oP "Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+" wc-tier-and-package-prices.php | head -1)
TAG_VERSION=${{ steps.version.outputs.version }}
if [ "$PLUGIN_VERSION" != "$TAG_VERSION" ]; then
echo "Error: Plugin version ($PLUGIN_VERSION) does not match tag version ($TAG_VERSION)"
exit 1
fi
echo "Version verified: $PLUGIN_VERSION"
- name: Create release directory
run: mkdir -p releases
- name: Build release package
run: |
VERSION=${{ steps.version.outputs.version }}
PLUGIN_NAME="wc-tier-and-package-prices"
RELEASE_FILE="releases/${PLUGIN_NAME}-${VERSION}.zip"
cd ..
zip -r "${PLUGIN_NAME}/${RELEASE_FILE}" "${PLUGIN_NAME}" \
-x "${PLUGIN_NAME}/.git/*" \
-x "${PLUGIN_NAME}/.gitea/*" \
-x "${PLUGIN_NAME}/.github/*" \
-x "${PLUGIN_NAME}/.vscode/*" \
-x "${PLUGIN_NAME}/.idea/*" \
-x "${PLUGIN_NAME}/.claude/*" \
-x "${PLUGIN_NAME}/CLAUDE.md" \
-x "${PLUGIN_NAME}/wordpress" \
-x "${PLUGIN_NAME}/wordpress/*" \
-x "${PLUGIN_NAME}/core" \
-x "${PLUGIN_NAME}/core/*" \
-x "${PLUGIN_NAME}/wp-core" \
-x "${PLUGIN_NAME}/wp-core/*" \
-x "${PLUGIN_NAME}/releases/*" \
-x "${PLUGIN_NAME}/composer.lock" \
-x "${PLUGIN_NAME}/*.log" \
-x "${PLUGIN_NAME}/logs/*" \
-x "${PLUGIN_NAME}/.gitignore" \
-x "${PLUGIN_NAME}/.gitmodules" \
-x "${PLUGIN_NAME}/.editorconfig" \
-x "${PLUGIN_NAME}/phpcs.xml*" \
-x "${PLUGIN_NAME}/phpunit.xml*" \
-x "${PLUGIN_NAME}/tests/*" \
-x "${PLUGIN_NAME}/templates/cache/*" \
-x "${PLUGIN_NAME}/notes.*" \
-x "${PLUGIN_NAME}/*.po~" \
-x "${PLUGIN_NAME}/*.bak" \
-x "${PLUGIN_NAME}/lib/*/.git/*" \
-x "${PLUGIN_NAME}/lib/*/CLAUDE.md" \
-x "${PLUGIN_NAME}/vendor/magdev/*/.git/*" \
-x "${PLUGIN_NAME}/vendor/magdev/*/CLAUDE.md" \
-x "*.DS_Store" \
-x "*Thumbs.db"
cd "${PLUGIN_NAME}"
echo "Created: ${RELEASE_FILE}"
ls -lh "${RELEASE_FILE}"
- name: Generate checksums
run: |
VERSION=${{ steps.version.outputs.version }}
PLUGIN_NAME="wc-tier-and-package-prices"
RELEASE_FILE="releases/${PLUGIN_NAME}-${VERSION}.zip"
cd releases
sha256sum "${PLUGIN_NAME}-${VERSION}.zip" > "${PLUGIN_NAME}-${VERSION}.zip.sha256"
echo "SHA256:"
cat "${PLUGIN_NAME}-${VERSION}.zip.sha256"
- name: Verify package structure
run: |
set +o pipefail
VERSION=${{ steps.version.outputs.version }}
PLUGIN_NAME="wc-tier-and-package-prices"
echo "Package contents (first 50 entries):"
unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | head -50 || true
# Verify main plugin file exists
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -q "${PLUGIN_NAME}/${PLUGIN_NAME}.php"; then
echo "✓ Main plugin file at correct location"
else
echo "✗ Error: Main plugin file not found at ${PLUGIN_NAME}/${PLUGIN_NAME}.php"
exit 1
fi
# Verify vendor directory included
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -q "${PLUGIN_NAME}/vendor/"; then
echo "✓ Vendor directory included"
else
echo "✗ Error: Vendor directory not found"
exit 1
fi
# Verify excluded files are not present
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -qE "CLAUDE\.md|\.claude/|\.git/|wordpress/"; then
echo "✗ Error: Excluded files found in package"
unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -E "CLAUDE\.md|\.claude/|\.git/|wordpress/"
exit 1
else
echo "✓ No excluded files in package"
fi
- name: Extract changelog for release notes
id: changelog
run: |
VERSION=${{ steps.version.outputs.version }}
# Extract release notes from CHANGELOG.md
NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/p" CHANGELOG.md | sed '$ d' | tail -n +2)
if [ -z "$NOTES" ]; then
NOTES="Release version ${VERSION}"
fi
echo "$NOTES" > release_notes.txt
echo "Release notes extracted"
- name: Create Gitea Release
env:
GITEA_TOKEN: ${{ secrets.SRC_GITEA_TOKEN }}
run: |
VERSION=${{ steps.version.outputs.version }}
TAG_NAME=${{ github.ref_name }}
PLUGIN_NAME="wc-tier-and-package-prices"
# Check if this is a prerelease (contains hyphen like v1.0.0-beta)
PRERELEASE="false"
if [[ "$TAG_NAME" == *-* ]]; then
PRERELEASE="true"
fi
BODY=$(cat release_notes.txt)
# Create release via Gitea API
RELEASE_RESPONSE=$(curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${VERSION}\", \"body\": $(echo "$BODY" | jq -Rs .), \"draft\": false, \"prerelease\": ${PRERELEASE}}" \
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
if [ "$RELEASE_ID" == "null" ] || [ -z "$RELEASE_ID" ]; then
echo "Failed to create release:"
echo "$RELEASE_RESPONSE"
exit 1
fi
echo "Created release ID: $RELEASE_ID"
# Upload release assets
for file in "releases/${PLUGIN_NAME}-${VERSION}.zip" "releases/${PLUGIN_NAME}-${VERSION}.zip.sha256"; do
if [ -f "$file" ]; then
FILENAME=$(basename "$file")
echo "Uploading $FILENAME..."
curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$file" \
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${FILENAME}"
echo "Uploaded $FILENAME"
fi
done
echo "Release created successfully: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG_NAME}"

15
.gitignore vendored Normal file → Executable file
View File

@@ -21,7 +21,22 @@ npm-debug.log
# Logs
*.log
/logs
# Notes
notes.*
# OS
.DS_Store
._*
# local code
wordpress
core
wp-core
# Releases (not tracked in git)
/releases/
# Claude Code local settings
.claude/settings.json

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "lib/wc-licensed-product-client"]
path = lib/wc-licensed-product-client
url = ../wc-licensed-product-client.git

File diff suppressed because it is too large Load Diff

1326
CLAUDE.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -90,8 +90,8 @@ After activation, you should see:
Before installation, verify:
- ✓ WordPress 5.8 or higher
- ✓ WooCommerce 5.0 or higher installed and activated
- ✓ WordPress 6.0 or higher (tested up to 6.9.x)
- ✓ WooCommerce 8.0 or higher (tested up to 10.x) installed and activated
- ✓ PHP 7.4 or higher
- ✓ Write permissions in `/wp-content/plugins/` directory
@@ -153,16 +153,34 @@ wp-content/
├── USAGE_EXAMPLES.md
├── includes/
│ ├── class-wc-tpp-admin.php
│ ├── class-wc-tpp-settings.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
── class-wc-tpp-product-meta.php
└── class-wc-tpp-template-loader.php
├── templates/
│ ├── admin/
── tier-row.twig
│ │ └── package-row.twig
── frontend/
── pricing-table.twig
│ ├── tier-pricing-table.twig
│ └── package-pricing-display.twig
├── assets/
│ ├── css/
│ │ ├── admin.css
│ │ └── frontend.css
│ └── js/
│ ├── admin.js
│ └── frontend.js
├── languages/
│ ├── wc-tier-package-prices.pot
│ ├── wc-tier-package-prices-de_DE.po
│ ├── wc-tier-package-prices-de_DE.mo
│ ├── wc-tier-package-prices-de_CH_informal.po
│ └── wc-tier-package-prices-de_CH_informal.mo
└── vendor/
└── (Twig and dependencies)
```
## Getting Help

View File

@@ -1,6 +1,6 @@
# Quick Start Guide
Get started with WooCommerce Tier and Package Prices in 5 minutes!
Get started with WooCommerce Tier and Package Prices (v1.1.20) in 5 minutes!
## Step 1: Install (2 minutes)
@@ -79,8 +79,11 @@ Use packages for common bundles (6-pack, dozen, case)
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
4. **Label Tiers**: Use descriptive labels like "Wholesale Price" or "Bulk Discount"
5. **Test Checkout**: Always complete a test order
6. **Mobile Check**: View on phone to verify responsiveness
7. **Quantity Restrictions**: Enable package restrictions to prevent arbitrary quantities
8. **Blocks Compatible**: Works with both classic and block-based carts/checkout
## Need More Help?

143
README.md
View File

@@ -1,5 +1,7 @@
# WooCommerce Tier and Package Prices
__THIS PROJECT IS 100% VIBE-CODED USING CLAUDE.AI__
A powerful WooCommerce plugin that adds tier pricing and package pricing functionality to your products with configurable quantities at fixed prices.
## Features
@@ -18,10 +20,11 @@ A powerful WooCommerce plugin that adds tier pricing and package pricing functio
### Admin Features
- Easy-to-use product meta boxes for adding tiers and packages
- Global settings page under WooCommerce menu
- Global settings page under WooCommerce menu with sub-tabs
- Configure display position (before/after add to cart, after price)
- Enable/disable tier or package pricing independently
- Sortable pricing rules
- License management with secure HMAC signature verification
### Frontend Features
- Beautiful pricing tables on product pages
@@ -37,6 +40,20 @@ A powerful WooCommerce plugin that adds tier pricing and package pricing functio
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Make sure WooCommerce is installed and activated
### Automated Releases
This project uses a Gitea CI/CD pipeline for automated releases. When a version tag (e.g., `v1.4.0`) is pushed:
1. Code is checked out with git submodules (dependencies bundled)
2. The pipeline validates the plugin version matches the tag
3. Composer dependencies are installed (production only)
4. Translation files are compiled (.po → .mo)
5. A release package is created with proper exclusions
6. SHA256 checksum is generated
7. Release is published to Gitea with changelog notes extracted from CHANGELOG.md
The `magdev/wc-licensed-product-client` library is bundled as a git submodule to avoid private repository authentication during CI/CD.
## Configuration
### Global Settings
@@ -108,12 +125,24 @@ When editing a product, scroll to the **Product data** panel:
```
wc-tier-and-package-prices/
├── wc-tier-and-package-prices.php # Main plugin file
├── wc-tier-and-package-prices.php # Main plugin file (v1.4.0)
├── .gitea/workflows/
│ └── release.yml # CI/CD release pipeline
├── includes/
│ ├── class-wc-tpp-admin.php # Admin settings
│ ├── class-wc-tpp-admin.php # Admin settings integration
│ ├── class-wc-tpp-settings.php # WooCommerce settings page
│ ├── class-wc-tpp-product-meta.php # Product meta boxes
│ ├── class-wc-tpp-frontend.php # Frontend display
── class-wc-tpp-cart.php # Cart price calculations
│ ├── class-wc-tpp-frontend.php # Frontend display logic
── class-wc-tpp-cart.php # Cart price calculations
│ └── class-wc-tpp-template-loader.php # Twig template engine
├── templates/
│ ├── admin/
│ │ ├── tier-row.twig # Tier pricing input row
│ │ └── package-row.twig # Package pricing input row
│ └── frontend/
│ ├── pricing-table.twig # Main pricing display wrapper
│ ├── tier-pricing-table.twig # Tier pricing table
│ └── package-pricing-display.twig # Package selection UI
├── assets/
│ ├── css/
│ │ ├── admin.css # Admin styles
@@ -121,14 +150,25 @@ wc-tier-and-package-prices/
│ └── js/
│ ├── admin.js # Admin JavaScript
│ └── frontend.js # Frontend JavaScript
── README.md
── languages/
│ ├── wc-tier-package-prices.pot # Translation template
│ ├── wc-tier-package-prices-*.po # Translation sources
│ └── wc-tier-package-prices-*.mo # Compiled translations
├── lib/ # Bundled libraries (git submodules)
│ └── wc-licensed-product-client/ # License client library
├── vendor/ # Composer dependencies (generated)
├── CHANGELOG.md # Complete version history
├── INSTALLATION.md # Installation guide
├── QUICKSTART.md # Quick start guide
├── USAGE_EXAMPLES.md # Usage examples
└── README.md # This file
```
## 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
- PHP 8.3 or higher (required since v1.3.0)
### Compatibility
@@ -150,7 +190,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
@@ -162,39 +202,76 @@ This plugin is licensed under the GPL v2 or later.
## Changelog
### Version 1.0.0 - 2025-12-21
### Version 1.4.0 - 2026-01-29
#### Compatibility Updates
__Current Release__ - CI/CD Release Pipeline
- ✅ 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
- __New__: Gitea CI/CD release pipeline for automated builds and releases
- __New__: Git submodule for `magdev/wc-licensed-product-client` library
- __Changed__: Composer now uses path repository for bundled license client
- __DevOps__: Automated version validation, translation compilation, and release publishing
#### Security Enhancements
See [CHANGELOG.md](CHANGELOG.md) for complete details.
- ✅ Added nonce verification for product meta save operations
- ✅ Added capability checks for user permissions
- ✅ Enhanced data escaping and sanitization
- ✅ Implemented autosave prevention
### Version 1.3.1 - 2026-01-27
#### Code Improvements
Secure License Client
- ✅ 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
- __Changed__: Switched to `SecureLicenseClient` with HMAC-SHA256 response signature verification
- __New__: Server Secret configuration field for secure communication with license server
- __Security__: Response signatures verified using HMAC-SHA256 with license-specific derived keys
#### Initial Features
### Version 1.3.0 - 2026-01-25
- 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
__Breaking Changes__ - PHP 8.3+ Required
- __Breaking__: Minimum PHP version increased from 7.4 to 8.3
- __New__: License management via `magdev/wc-licensed-product-client` library
- __New__: Settings page split into "General" and "License" sub-tabs
- __New__: AJAX-based license validation and activation with visual feedback
- __New__: License status caching with daily auto-refresh
- __New__: PHP version check with admin notice for incompatible servers
### Version 1.2.0 - 2025-12-29
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
### Recent Major Updates
#### Version 1.1.7 - Enhanced Tier Pricing
- Added optional text labels for tier pricing
- Clickable tier rows that auto-populate quantity field
- Add to Cart button auto-disable when quantity is invalid
#### Version 1.1.4 - WooCommerce Blocks Support
- Full support for WooCommerce block-based cart and checkout
- Quantity restrictions work with both classic and block carts
- Enhanced "View Options" button styling
#### Version 1.1.0 - Package Quantity Restrictions
- Global and per-product package quantity restrictions
- Prevents customers from ordering non-package quantities
- Automatic quantity field hiding when restrictions enabled
#### Version 1.0.1 - Twig Template Engine
- Migrated to Twig templating system
- Enhanced security with automatic HTML escaping
- Added German (Switzerland, Informal) translation
#### Version 1.0.0 - Initial Release
- Tier pricing functionality (quantity-based discounts)
- Package pricing functionality (fixed-price bundles)
- WooCommerce HPOS compatibility
- Multilingual support (English, German)
For complete version history, see [CHANGELOG.md](CHANGELOG.md)
## Credits

View File

@@ -1,5 +1,8 @@
# Usage Examples
**Plugin Version:** 1.1.20
**Last Updated:** 2025-12-23
## Example 1: T-Shirt Store with Volume Discounts
### Tier Pricing Setup
@@ -16,9 +19,9 @@ For a t-shirt that normally costs $20:
**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
3. Add tier: Min Qty = 10, Price = $18.00, Label = "Bulk Discount"
4. Add tier: Min Qty = 25, Price = $16.00, Label = "Volume Pricing"
5. Add tier: Min Qty = 50, Price = $14.00, Label = "Wholesale Rate"
6. Save product
**Customer experience:**
@@ -97,6 +100,62 @@ Regular mug price: $10.00
- Package 2: Qty = 4, Price = $32.00, Label = "Family Set"
- Package 3: Qty = 10, Price = $70.00, Label = "Office Bundle"
## Example 5: Quantity Restrictions (New in v1.1.0)
### Party Supplies with Fixed Packages
For products that should ONLY be sold in specific package quantities:
**Product:** Balloons - Regular price $1.00 each
**Package Configuration:**
- Package 1: Qty = 12, Price = $10.00, Label = "Dozen Pack"
- Package 2: Qty = 24, Price = $18.00, Label = "Party Pack"
- Package 3: Qty = 50, Price = $40.00, Label = "Event Pack"
**How to configure:**
1. Edit balloon product
2. Set regular price to $1.00
3. Add packages as shown above
4. **Check "Restrict to package quantities only"** (per-product setting)
5. Save product
**Customer experience:**
- Quantity input field is hidden on product page
- Customer MUST select a package using the package selection buttons
- Attempting to add custom quantities via URL or API will fail with validation error
- Cart displays selected package quantity (cannot be edited)
- "View Options" button appears on shop/category pages instead of "Add to Cart"
**When to use quantity restrictions:**
- Pre-packaged items (dozen eggs, 6-pack drinks, etc.)
- Products with fixed manufacturing quantities
- Promotional bundles where you don't want individual sales
- Subscription boxes with specific item counts
## Example 6: Tier Labels for Enhanced UX (New in v1.1.7)
### Office Supplies with Clickable Tiers
**Product:** Premium Notebooks - Regular price $8.00 each
**Tier Configuration with Labels:**
- Tier 1: Min Qty = 5, Price = $7.50, Label = "Small Business Discount"
- Tier 2: Min Qty = 10, Price = $7.00, Label = "Wholesale Pricing"
- Tier 3: Min Qty = 25, Price = $6.50, Label = "Corporate Rate"
**Customer experience:**
- Tier labels appear below quantity in pricing table (italicized)
- Clicking a tier row auto-fills the quantity field with that tier's minimum quantity
- Smooth scroll animation highlights the quantity field
- Add to Cart button automatically disables when quantity is 0 or invalid
**Benefits of tier labels:**
- Helps customers understand pricing context
- Makes tiers more appealing with descriptive names
- Improves conversion by highlighting value propositions
- Clickable rows improve user experience
## Tips for Best Results
### Tier Pricing Best Practices
@@ -113,6 +172,11 @@ Regular mug price: $10.00
- Minimum 5-10% per tier level
- Higher tiers should have progressively better deals
4. **Use Labels** (v1.1.7+): Add descriptive labels to tiers
- "Wholesale Price" instead of just showing the number
- "Bulk Discount", "Volume Pricing", "Corporate Rate"
- Makes pricing more professional and appealing
### Package Pricing Best Practices
1. **Strategic Quantities**: Match common use cases
@@ -169,3 +233,32 @@ Regular mug price: $10.00
- Package 2: Medium party (25 pieces) = $110
- Package 3: Large party (50 pieces) = $200
- Package 4: Event package (100 pieces) = $350
## WooCommerce Blocks Compatibility (v1.1.4+, Fixed in v1.1.20)
This plugin is **fully compatible** with WooCommerce block-based cart and checkout:
### Supported Block Types
- Cart Block (`woocommerce/cart`)
- Mini Cart Block (`woocommerce/mini-cart`)
- Checkout Block (`woocommerce/checkout`)
- All Store API endpoints
### Block-Specific Features
- Quantity restrictions work in block-based carts
- Package-restricted products hide quantity selectors in blocks
- Tier and package pricing applies correctly in block checkout
- Mini cart displays correct prices and restrictions
### Technical Notes
- v1.1.20 fixed critical fatal error in WooCommerce Blocks
- Uses `woocommerce_store_api_product_quantity_editable` filter
- Works with both classic and block-based themes
- No configuration needed - blocks work automatically
### Testing Your Block Setup
1. Add block-based cart to a page (`/cart`)
2. Add mini-cart block to your header
3. Add block-based checkout to a page (`/checkout`)
4. Test tier pricing, package pricing, and quantity restrictions
5. Verify prices calculate correctly at checkout

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,96 @@
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;
}
/* License Status Styling */
.wc-tpp-license-active {
color: #46b450;
font-weight: 600;
}
.wc-tpp-license-inactive {
color: #dc3232;
font-weight: 600;
}
.wc-tpp-license-expired {
color: #ffb900;
font-weight: 600;
}
#wc-tpp-license-spinner {
float: none;
margin-top: 0;
vertical-align: middle;
}
#wc-tpp-validate-license,
#wc-tpp-activate-license {
margin-right: 8px;
}
#wc-tpp-license-status-container {
margin-bottom: 10px;
padding: 10px 15px;
background: #f9f9f9;
border-left: 4px solid #ccc;
border-radius: 2px;
}
#wc-tpp-license-status-container.valid {
border-left-color: #46b450;
background: #f0fff0;
}
#wc-tpp-license-status-container.invalid {
border-left-color: #dc3232;
background: #fff0f0;
}
#wc-tpp-license-status-container small {
color: #666;
}

View File

@@ -50,11 +50,13 @@
.wc-tpp-table tbody tr {
border-bottom: 1px solid #e0e0e0;
transition: background-color 0.2s;
transition: all 0.2s;
cursor: pointer;
}
.wc-tpp-table tbody tr:hover {
background: #f5f5f5;
transform: translateX(2px);
}
.wc-tpp-table tbody tr.wc-tpp-active-tier {
@@ -67,6 +69,14 @@
font-size: 0.95em;
}
.wc-tpp-tier-label {
display: inline-block;
margin-top: 4px;
color: #666;
font-style: italic;
font-size: 0.9em;
}
/* Package pricing */
.wc-tpp-packages {
display: grid;
@@ -156,6 +166,67 @@
font-style: italic;
}
/* Catalog "View Options" button */
a.wc-tpp-view-options {
display: inline-block;
text-align: center;
text-decoration: none;
position: relative;
/* Match WooCommerce button styling */
font-size: 1em;
font-weight: 700;
padding: 0.618em 1em;
line-height: 1.5;
border-radius: 3px;
cursor: pointer;
transition: all 0.2s ease;
}
a.wc-tpp-view-options::before {
content: "\f06e";
font-family: "dashicons";
margin-right: 5px;
display: inline-block;
font-size: 1em;
vertical-align: middle;
line-height: 1;
}
/* Hover state for View Options button */
a.wc-tpp-view-options:hover {
opacity: 0.85;
text-decoration: none;
}
/* Cart quantity display for restricted products */
.wc-tpp-cart-quantity {
display: inline-block;
padding: 5px 10px;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 3px;
font-weight: 600;
}
/* Restriction notice */
.wc-tpp-restriction-notice {
padding: 10px 15px;
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 4px;
margin-bottom: 15px;
color: #856404;
font-size: 0.95em;
}
/* Disabled add to cart button */
.single_add_to_cart_button.disabled,
.single_add_to_cart_button:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
/* Responsive design */
@media (max-width: 768px) {
.wc-tpp-packages {

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

@@ -8,8 +8,10 @@
$(document).ready(function() {
const $quantityInput = $('input.qty');
const $priceDisplay = $('.woocommerce-Price-amount.amount').first();
const $addToCartButton = $('.single_add_to_cart_button');
const isRestrictedMode = $('.wc-tpp-package-pricing-table').hasClass('wc-tpp-restricted-mode');
if ($quantityInput.length === 0) {
if ($quantityInput.length === 0 && !isRestrictedMode) {
return;
}
@@ -153,9 +155,35 @@
$('.wc-tpp-price-message').remove();
}
// Toggle add to cart button state based on quantity
function updateAddToCartButton() {
const quantity = parseInt($quantityInput.val()) || 0;
if (quantity <= 0) {
$addToCartButton.prop('disabled', true).addClass('disabled');
} else {
$addToCartButton.prop('disabled', false).removeClass('disabled');
}
}
// Handle quantity input changes
$quantityInput.on('input change', function() {
updatePriceDisplay();
updateAddToCartButton();
});
// Handle tier pricing row clicks
$('.wc-tpp-tier-pricing-table tbody tr').on('click', function() {
const minQty = parseInt($(this).data('min-qty'));
if ($quantityInput.length > 0 && !isRestrictedMode) {
$quantityInput.val(minQty).trigger('change');
// Scroll to quantity input for better UX
$('html, body').animate({
scrollTop: $quantityInput.offset().top - 100
}, 300);
}
});
// Handle package selection
@@ -164,7 +192,30 @@
const $package = $(this).closest('.wc-tpp-package');
const qty = parseInt($package.data('qty'));
$quantityInput.val(qty).trigger('change');
if (isRestrictedMode) {
// In restricted mode, we need to set a hidden input or use data attribute
// since the quantity field is hidden
if ($quantityInput.length === 0) {
// Create a hidden quantity input if it doesn't exist
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 {
$quantityInput.val(qty);
}
// Highlight selected package
$('.wc-tpp-package').removeClass('wc-tpp-selected');
$package.addClass('wc-tpp-selected');
// Update price display
const price = parseFloat($package.data('price'));
const unitPrice = price / qty;
updatePrice(unitPrice, 'Package price: ' + formatPrice(price) + ' total');
} else {
$quantityInput.val(qty).trigger('change');
}
// Scroll to add to cart button
$('html, body').animate({
@@ -172,8 +223,135 @@
}, 500);
});
// In restricted mode, prevent form submission if no package is selected
if (isRestrictedMode) {
$('form.cart').on('submit', function(e) {
const hasSelection = $('.wc-tpp-package.wc-tpp-selected').length > 0;
if (!hasSelection) {
e.preventDefault();
alert('Please select a package size before adding to cart.');
return false;
}
});
}
// Initial update
updatePriceDisplay();
if (!isRestrictedMode) {
updatePriceDisplay();
}
// Initial button state check
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,6 @@
{
"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": [
@@ -10,13 +9,28 @@
"homepage": "https://src.bundespruefstelle.ch/magdev"
}
],
"repositories": [
{
"type": "path",
"url": "lib/wc-licensed-product-client"
}
],
"require": {
"php": ">=7.4",
"twig/twig": "^3.0"
"php": ">=8.3",
"twig/twig": "^3.0",
"magdev/wc-licensed-product-client": "^0.2"
},
"autoload": {
"classmap": [
"includes/"
]
}
},
"config": {
"optimize-autoloader": true,
"platform": {
"php": "8.3.0"
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

View File

@@ -7,42 +7,64 @@ if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Admin {
if (!class_exists('WC_TPP_Admin')) {
class WC_TPP_Admin {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
private static $instance = null;
private static $settings_instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_filter('woocommerce_get_settings_pages', array($this, 'add_settings_page'));
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')
);
}
/**
* Add settings page to WooCommerce settings
*/
public function add_settings_page($settings) {
if (null === self::$settings_instance) {
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-settings.php';
self::$settings_instance = new WC_TPP_Settings();
}
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');
// Check if our settings page is already in the array to prevent duplicates
// Check by class type and ID instead of instance comparison
foreach ($settings as $settings_page) {
if ($settings_page instanceof WC_TPP_Settings) {
return $settings;
}
// Also check by ID property if it's a WC_Settings_Page
if (is_object($settings_page) &&
method_exists($settings_page, 'get_id') &&
$settings_page->get_id() === 'tier_package_prices') {
return $settings;
}
// Check id property directly
if (is_object($settings_page) &&
isset($settings_page->id) &&
$settings_page->id === 'tier_package_prices') {
return $settings;
}
}
$settings[] = self::$settings_instance;
return $settings;
}
public function enqueue_admin_scripts($hook) {
if ('woocommerce_page_wc-tier-package-prices' === $hook || 'post.php' === $hook || 'post-new.php' === $hook) {
if ('woocommerce_page_wc-settings' === $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();
WC_TPP_Admin::get_instance();
}

View File

@@ -7,12 +7,20 @@ if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Cart {
if (!class_exists('WC_TPP_Cart')) {
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);
add_filter('woocommerce_add_to_cart_validation', array($this, 'validate_package_quantity'), 10, 3);
add_filter('woocommerce_cart_item_quantity', array($this, 'maybe_hide_cart_quantity_input'), 999, 3);
add_filter('woocommerce_widget_cart_item_quantity', array($this, 'maybe_hide_mini_cart_quantity_input'), 999, 3);
add_action('wp_head', array($this, 'add_cart_quantity_css'));
// WooCommerce Blocks support
add_filter('woocommerce_store_api_product_quantity_editable', array($this, 'block_quantity_editable'), 10, 2);
}
public function apply_tier_package_pricing($cart) {
@@ -32,6 +40,8 @@ class 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'];
@@ -40,10 +50,10 @@ class 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) {
@@ -54,9 +64,9 @@ class 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
@@ -89,6 +99,196 @@ class WC_TPP_Cart {
}
return $subtotal;
}
public function validate_package_quantity($passed, $product_id, $quantity) {
// Check for variation ID in request (for variable products)
$variation_id = isset($_REQUEST['variation_id']) ? absint($_REQUEST['variation_id']) : 0;
// 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/variation (with parent fallback)
$packages = $this->get_packages_with_fallback($product_id, $variation_id);
if (!$packages) {
return $passed;
}
// Check if the quantity matches any package
$valid_quantity = false;
$available_quantities = array();
foreach ($packages as $package) {
$available_quantities[] = $package['qty'];
if ($quantity == $package['qty']) {
$valid_quantity = true;
break;
}
}
if (!$valid_quantity) {
$product = wc_get_product($product_id);
$product_name = $product ? $product->get_name() : __('this product', 'wc-tier-package-prices');
wc_add_notice(
sprintf(
__('The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s', 'wc-tier-package-prices'),
$quantity,
$product_name,
implode(', ', $available_quantities)
),
'error'
);
return false;
}
return $passed;
}
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 (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>',
$effective_id,
$cart_item['quantity']
);
}
return $product_quantity;
}
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 (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>',
$effective_id,
$cart_item['quantity']
);
}
return $product_quantity;
}
public function add_cart_quantity_css() {
// Get all cart items and check which products have restrictions
if (!function_exists('WC') || !WC()->cart || is_admin()) {
return;
}
$restricted_products = array();
foreach (WC()->cart->get_cart() as $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 (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;
}
}
if (!empty($restricted_products)) {
echo '<style type="text/css">';
foreach ($restricted_products as $product_id) {
// Hide quantity inputs for restricted products in cart (classic cart)
echo '.cart_item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] + .quantity,';
echo '.cart_item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] ~ .quantity,';
echo '.woocommerce-mini-cart-item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] + .quantity,';
echo '.woocommerce-mini-cart-item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] ~ .quantity { display: none !important; }';
// Hide WooCommerce blocks quantity selector for restricted products
echo '.wc-block-cart-item[data-product-id="' . esc_attr($product_id) . '"] .wc-block-components-quantity-selector,';
echo '.wc-block-mini-cart__items .wc-block-cart-item[data-product-id="' . esc_attr($product_id) . '"] .wc-block-components-quantity-selector { display: none !important; }';
}
echo '</style>';
}
}
/**
* Make quantity non-editable for restricted products in WooCommerce blocks
*
* @param bool $editable Whether the quantity is editable
* @param WC_Product $product Product object (can be variation)
* @return bool
*/
public function block_quantity_editable($editable, $product) {
// Validate product object
if (!$product || !is_a($product, 'WC_Product')) {
return $editable;
}
$product_id = $product->get_id();
if (!$product_id) {
return $editable;
}
// For variations, get parent product ID and variation ID
$variation_id = 0;
$parent_id = $product_id;
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

@@ -7,22 +7,38 @@ if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Frontend {
if (!class_exists('WC_TPP_Frontend')) {
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);
add_action('woocommerce_before_add_to_cart_quantity', array($this, 'maybe_hide_quantity_input'));
// 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() {
if (is_product()) {
// Enqueue CSS on all WooCommerce pages (for catalog buttons and cart)
if (is_woocommerce() || is_cart() || is_checkout() || is_product()) {
wp_enqueue_style('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/css/frontend.css', array(), WC_TPP_VERSION);
}
// Enqueue JS only on product pages
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()),
@@ -50,6 +66,29 @@ class WC_TPP_Frontend {
}
}
public function maybe_hide_quantity_input() {
global $product;
if (!$product || !is_a($product, 'WC_Product')) {
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';
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
// Hide quantity input if restriction is enabled and packages exist
if (($global_restrict || $product_restrict) && !empty($packages)) {
echo '<style>.quantity { display: none !important; }</style>';
}
}
public function display_pricing_table() {
global $product;
@@ -57,9 +96,18 @@ class 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);
$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';
if (empty($tiers) && empty($packages)) {
return;
@@ -68,12 +116,27 @@ class WC_TPP_Frontend {
WC_TPP_Template_Loader::get_instance()->display('frontend/pricing-table.twig', array(
'product' => $product,
'tiers' => $tiers,
'packages' => $packages
'packages' => $packages,
'restrict_to_packages' => $global_restrict || $product_restrict
));
}
public static function get_tier_price($product_id, $quantity) {
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
/**
* Get tier price for a product or variation
*
* @param int $product_id Product ID (parent for simple, parent for variable)
* @param int $quantity Quantity
* @param int $variation_id Variation ID (0 for simple products)
* @return float|null Tier price or null if not applicable
*/
public static function get_tier_price($product_id, $quantity, $variation_id = 0) {
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
$tiers = get_post_meta($effective_id, '_wc_tpp_tiers', true);
// 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;
@@ -89,8 +152,22 @@ class WC_TPP_Frontend {
return $applicable_price;
}
public static function get_package_price($product_id, $quantity) {
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
/**
* Get package price for a product or variation
*
* @param int $product_id Product ID (parent for simple, parent for variable)
* @param int $quantity Quantity
* @param int $variation_id Variation ID (0 for simple products)
* @return float|null Package price or null if not applicable
*/
public static function get_package_price($product_id, $quantity, $variation_id = 0) {
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
// 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;
@@ -104,6 +181,127 @@ class WC_TPP_Frontend {
return null;
}
/**
* Check if a product has quantity restrictions enabled
*
* @param int $product_id Product ID
* @return bool
*/
public static function has_quantity_restriction($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);
return ($global_restrict || $product_restrict) && !empty($packages);
}
/**
* Modify catalog add to cart button for products with quantity restrictions
*
* @param string $html Add to cart button HTML
* @param WC_Product $product Product object
* @return string Modified HTML
*/
public function modify_catalog_add_to_cart_button($html, $product) {
if (!$product || !is_a($product, 'WC_Product')) {
return $html;
}
$product_id = $product->get_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;
}
// Replace add to cart button with "View Options" link
$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 %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

@@ -0,0 +1,333 @@
<?php
/**
* License Checker
*
* Handles license validation with localhost and self-licensing bypass
*
* @package WC_Tier_Package_Prices
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* WC_TPP_License_Checker class
*/
if (!class_exists('WC_TPP_License_Checker')) {
class WC_TPP_License_Checker {
/**
* Singleton instance
*
* @var WC_TPP_License_Checker|null
*/
private static $instance = null;
/**
* Cache key for license status
*/
private const CACHE_KEY = 'wc_tpp_license_status';
/**
* Cache TTL for successful validation (1 hour)
*/
private const CACHE_TTL_SUCCESS = 3600;
/**
* Cache TTL for failed validation (5 minutes)
*/
private const CACHE_TTL_ERROR = 300;
/**
* Localhost patterns
*
* @var array
*/
private const LOCALHOST_HOSTS = ['localhost', '127.0.0.1', '::1'];
/**
* Localhost TLDs
*
* @var array
*/
private const LOCALHOST_TLDS = ['.localhost', '.local', '.test', '.example', '.invalid'];
/**
* Get singleton instance
*
* @return WC_TPP_License_Checker
*/
public static function get_instance(): WC_TPP_License_Checker {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
// Clear cache when license settings change
add_action('update_option_wc_tpp_license_key', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_license_server_url', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_license_server_secret', array($this, 'clear_cache'));
}
/**
* Check if the current environment is localhost
*
* Matches patterns:
* - localhost
* - 127.0.0.1
* - ::1 (IPv6 localhost)
* - *.localhost (subdomains)
* - *.local (subdomains)
* - *.test (RFC 2606)
* - *.example (RFC 2606)
* - *.invalid (RFC 2606)
*
* @return bool
*/
public function is_localhost(): bool {
$domain = $this->get_current_domain();
// Remove port number if present
$domain = preg_replace('/:[\d]+$/', '', $domain);
$domain = strtolower($domain);
// Check exact matches
if (in_array($domain, self::LOCALHOST_HOSTS, true)) {
return true;
}
// Check TLD patterns
foreach (self::LOCALHOST_TLDS as $tld) {
if (str_ends_with($domain, $tld)) {
return true;
}
}
// Check for private IP ranges (Docker, VMs, etc.)
if ($this->is_private_ip($domain)) {
return true;
}
return false;
}
/**
* Check if domain is a private IP address
*
* @param string $domain The domain to check.
* @return bool
*/
private function is_private_ip(string $domain): bool {
// Check if it's a valid IP first
if (!filter_var($domain, FILTER_VALIDATE_IP)) {
return false;
}
// Check for private/reserved ranges
return !filter_var(
$domain,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
);
}
/**
* Check if the current site is self-licensing
*
* Returns true if the license server URL and site URL are on the same domain.
* This allows the license server itself to use the plugin without a license.
*
* @return bool
*/
public function is_self_licensing(): bool {
$server_url = get_option('wc_tpp_license_server_url', '');
if (empty($server_url)) {
return false;
}
$server_domain = $this->normalize_domain($server_url);
$site_domain = $this->normalize_domain(get_site_url());
return $server_domain === $site_domain;
}
/**
* Normalize a URL to its domain
*
* @param string $url The URL to normalize.
* @return string
*/
private function normalize_domain(string $url): string {
$parsed = wp_parse_url($url);
$host = $parsed['host'] ?? '';
// Convert to lowercase
$host = strtolower($host);
// Remove www. prefix
$host = preg_replace('/^www\./', '', $host);
return $host;
}
/**
* Get current domain from site URL
*
* @return string
*/
public function get_current_domain(): string {
$site_url = get_site_url();
$parsed = wp_parse_url($site_url);
$host = $parsed['host'] ?? 'localhost';
// Include port if non-standard
if (isset($parsed['port'])) {
$host .= ':' . $parsed['port'];
}
return strtolower($host);
}
/**
* Check if the license is valid
*
* This is the main entry point for license validation.
* It implements the bypass logic for localhost and self-licensing.
*
* @return bool
*/
public function is_license_valid(): bool {
// Always valid on localhost
if ($this->is_localhost()) {
return true;
}
// Always valid for self-licensing
if ($this->is_self_licensing()) {
return true;
}
// Check cached status
$cached = $this->get_cached_status();
if (false !== $cached) {
return !empty($cached['valid']);
}
// Validate against server
return $this->validate_license();
}
/**
* Get the license bypass reason if applicable
*
* @return string|null 'localhost', 'self_licensing', or null
*/
public function get_bypass_reason(): ?string {
if ($this->is_localhost()) {
return 'localhost';
}
if ($this->is_self_licensing()) {
return 'self_licensing';
}
return null;
}
/**
* Get cached license status
*
* @return array|false
*/
public function get_cached_status() {
return get_transient(self::CACHE_KEY);
}
/**
* Validate license against the server
*
* @return bool
*/
private function validate_license(): bool {
$license_key = get_option('wc_tpp_license_key', '');
$server_url = get_option('wc_tpp_license_server_url', '');
$server_secret = get_option('wc_tpp_license_server_secret', '');
// Can't validate without credentials
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
return false;
}
try {
$client = $this->get_license_client($server_url, $server_secret);
$domain = $this->get_current_domain();
// Remove port for validation
$domain = preg_replace('/:[\d]+$/', '', $domain);
$result = $client->validate($license_key, $domain);
// Cache successful validation
set_transient(self::CACHE_KEY, array(
'valid' => true,
'product_id' => $result->productId,
'expires_at' => $result->expiresAt?->format('Y-m-d H:i:s'),
'is_lifetime' => $result->isLifetime(),
'checked_at' => current_time('mysql'),
), self::CACHE_TTL_SUCCESS);
return true;
} catch (\Exception $e) {
// Cache validation failure
set_transient(self::CACHE_KEY, array(
'valid' => false,
'error' => $e->getMessage(),
'checked_at' => current_time('mysql'),
), self::CACHE_TTL_ERROR);
return false;
}
}
/**
* Get license client instance
*
* @param string $server_url License server URL.
* @param string $server_secret Shared secret for signature verification.
* @return \Magdev\WcLicensedProductClient\LicenseClientInterface
*/
private function get_license_client(string $server_url, string $server_secret): \Magdev\WcLicensedProductClient\LicenseClientInterface {
$httpClient = \Symfony\Component\HttpClient\HttpClient::create();
return new \Magdev\WcLicensedProductClient\SecureLicenseClient(
httpClient: $httpClient,
baseUrl: $server_url,
serverSecret: $server_secret,
);
}
/**
* Clear the license cache
*/
public function clear_cache(): void {
delete_transient(self::CACHE_KEY);
}
/**
* Force revalidation of the license
*
* @return bool
*/
public function revalidate(): bool {
$this->clear_cache();
return $this->is_license_valid();
}
}
}

View File

@@ -7,16 +7,137 @@ if (!defined('ABSPATH')) {
exit;
}
class WC_TPP_Product_Meta {
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);
}
/**
* 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-variable-parent-pricing">
<p class="form-field">
<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>
<!-- 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)) {
$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>
<!-- 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)) {
$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>
<?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">
@@ -24,18 +145,28 @@ class WC_TPP_Product_Meta {
<span class="description"><?php _e('Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'); ?></span>
</p>
<div class="wc-tpp-tiers-container">
<?php
$tiers = get_post_meta($post->ID, '_wc_tpp_tiers', true);
if (!is_array($tiers)) {
$tiers = array();
}
<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);
}
?>
</div>
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>
@@ -46,6 +177,13 @@ class WC_TPP_Product_Meta {
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">
@@ -53,26 +191,45 @@ class WC_TPP_Product_Meta {
<span class="description"><?php _e('Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100.', 'wc-tier-package-prices'); ?></span>
</p>
<div class="wc-tpp-packages-container">
<?php
$packages = get_post_meta($post->ID, '_wc_tpp_packages', true);
if (!is_array($packages)) {
$packages = array();
}
<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);
}
?>
</div>
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>
<?php
woocommerce_wp_checkbox(array(
'id' => '_wc_tpp_restrict_to_packages',
'label' => __('Restrict to Package Quantities', 'wc-tier-package-prices'),
'description' => __('Only allow quantities defined in packages above', 'wc-tier-package-prices'),
'desc_tip' => true,
));
?>
</div>
<script type="text/html" id="wc-tpp-tier-row-template">
<?php $this->render_tier_row('{{INDEX}}', array('min_qty' => '', 'price' => '')); ?>
<?php $this->render_tier_row('{{INDEX}}', array('min_qty' => '', 'price' => '', 'label' => '')); ?>
</script>
<script type="text/html" id="wc-tpp-package-row-template">
@@ -84,14 +241,148 @@ class 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()
));
}
@@ -118,7 +409,8 @@ class WC_TPP_Product_Meta {
if (!empty($tier['min_qty']) && !empty($tier['price'])) {
$tiers[] = array(
'min_qty' => absint($tier['min_qty']),
'price' => wc_format_decimal($tier['price'])
'price' => wc_format_decimal($tier['price']),
'label' => sanitize_text_field($tier['label'] ?? '')
);
}
}
@@ -126,7 +418,12 @@ class WC_TPP_Product_Meta {
usort($tiers, function($a, $b) {
return $a['min_qty'] - $b['min_qty'];
});
update_post_meta($post_id, '_wc_tpp_tiers', $tiers);
// 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');
}
@@ -147,11 +444,89 @@ class WC_TPP_Product_Meta {
usort($packages, function($a, $b) {
return $a['qty'] - $b['qty'];
});
update_post_meta($post_id, '_wc_tpp_packages', $packages);
// 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

@@ -0,0 +1,748 @@
<?php
/**
* WooCommerce Settings Integration
*
* Adds Tier & Package Prices settings to WooCommerce Settings with sub-tabs
*
* @package WC_Tier_Package_Prices
*/
if (!defined('ABSPATH')) {
exit;
}
if (!class_exists('WC_Settings_Page')) {
return;
}
/**
* WC_TPP_Settings class
*/
if (!class_exists('WC_TPP_Settings')) {
class WC_TPP_Settings extends WC_Settings_Page {
/**
* Constructor
*/
public function __construct() {
$this->id = 'tier_package_prices';
$this->label = __('Tier & Package Prices', 'wc-tier-package-prices');
parent::__construct();
// Add AJAX handlers for license validation
add_action('wp_ajax_wc_tpp_validate_license', array($this, 'ajax_validate_license'));
add_action('wp_ajax_wc_tpp_activate_license', array($this, 'ajax_activate_license'));
// Add AJAX handler for update checks
add_action('wp_ajax_wc_tpp_check_updates', array($this, 'ajax_check_updates'));
}
/**
* Get own sections - Modern WooCommerce pattern
*
* @return array
*/
protected function get_own_sections() {
return array(
'' => __('General', 'wc-tier-package-prices'),
'license' => __('License', 'wc-tier-package-prices'),
'updates' => __('Auto-Updates', 'wc-tier-package-prices'),
);
}
/**
* Get settings for the default (General) section
*
* @return array
*/
protected function get_settings_for_default_section() {
return array(
array(
'title' => __('Tier & Package Prices Settings', 'wc-tier-package-prices'),
'type' => 'title',
'desc' => __('Configure tier pricing and package pricing options for your WooCommerce products.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_settings',
),
array(
'title' => __('Enable Tier Pricing', 'wc-tier-package-prices'),
'desc' => __('Enable tier pricing for products', 'wc-tier-package-prices'),
'id' => 'wc_tpp_enable_tier_pricing',
'default' => 'yes',
'type' => 'checkbox',
'desc_tip' => __('Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'),
),
array(
'title' => __('Enable Package Pricing', 'wc-tier-package-prices'),
'desc' => __('Enable fixed-price packages for products', 'wc-tier-package-prices'),
'id' => 'wc_tpp_enable_package_pricing',
'default' => 'yes',
'type' => 'checkbox',
'desc_tip' => __('Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100.', 'wc-tier-package-prices'),
),
array(
'title' => __('Display Pricing Table', 'wc-tier-package-prices'),
'desc' => __('Show tier and package pricing table on product pages', 'wc-tier-package-prices'),
'id' => 'wc_tpp_display_table',
'default' => 'yes',
'type' => 'checkbox',
'desc_tip' => __('Display the pricing table to customers on product pages.', 'wc-tier-package-prices'),
),
array(
'title' => __('Display Position', 'wc-tier-package-prices'),
'desc' => __('Choose where to display the pricing table on product pages.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_display_position',
'default' => 'after_add_to_cart',
'type' => 'select',
'class' => 'wc-enhanced-select',
'css' => 'min-width:300px;',
'desc_tip' => true,
'options' => array(
'before_add_to_cart' => __('Before Add to Cart Button', 'wc-tier-package-prices'),
'after_add_to_cart' => __('After Add to Cart Button', 'wc-tier-package-prices'),
'after_price' => __('After Price', 'wc-tier-package-prices'),
),
),
array(
'title' => __('Restrict to Package Quantities', 'wc-tier-package-prices'),
'desc' => __('Limit quantities to defined package sizes only', 'wc-tier-package-prices'),
'id' => 'wc_tpp_restrict_package_quantities',
'default' => 'no',
'type' => 'checkbox',
'desc_tip' => __('When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons.', 'wc-tier-package-prices'),
),
array(
'type' => 'sectionend',
'id' => 'wc_tpp_settings',
),
);
}
/**
* Get settings for the License section
*
* @return array
*/
protected function get_settings_for_license_section() {
return array(
array(
'title' => __('License Management', 'wc-tier-package-prices'),
'type' => 'title',
'desc' => __('Enter your license key to receive updates and support.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_license_settings',
),
array(
'title' => __('License Server URL', 'wc-tier-package-prices'),
'desc' => __('The URL of the license server.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_license_server_url',
'type' => 'url',
'default' => '',
'css' => 'min-width:400px;',
'desc_tip' => true,
),
array(
'title' => __('License Key', 'wc-tier-package-prices'),
'desc' => __('Your license key for this plugin.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_license_key',
'type' => 'text',
'default' => '',
'css' => 'min-width:400px;',
'desc_tip' => true,
),
array(
'title' => __('Server Secret', 'wc-tier-package-prices'),
'desc' => __('The shared secret for secure communication with the license server.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_license_server_secret',
'type' => 'password',
'default' => '',
'css' => 'min-width:400px;',
'desc_tip' => true,
),
array(
'title' => __('License Status', 'wc-tier-package-prices'),
'type' => 'wc_tpp_license_status',
'id' => 'wc_tpp_license_status_display',
),
array(
'type' => 'sectionend',
'id' => 'wc_tpp_license_settings',
),
);
}
/**
* Get settings for the Auto-Updates section
*
* @return array
*/
protected function get_settings_for_updates_section() {
// Check if auto-install is available (requires valid license or bypass)
$license_checker = WC_TPP_License_Checker::get_instance();
$auto_install_disabled = !$license_checker->is_license_valid();
$auto_install_desc = __('Automatically install updates when available.', 'wc-tier-package-prices');
if ($auto_install_disabled) {
$auto_install_desc .= ' ' . __('(Requires a valid license)', 'wc-tier-package-prices');
}
return array(
array(
'title' => __('Auto-Update Settings', 'wc-tier-package-prices'),
'type' => 'title',
'desc' => __('Configure automatic plugin updates from the license server.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_update_settings',
),
array(
'title' => __('Enable Update Notifications', 'wc-tier-package-prices'),
'desc' => __('Check for available plugin updates.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_update_notification_enabled',
'default' => 'yes',
'type' => 'checkbox',
'desc_tip' => __('When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin.', 'wc-tier-package-prices'),
),
array(
'title' => __('Automatically Install Updates', 'wc-tier-package-prices'),
'desc' => $auto_install_desc,
'id' => 'wc_tpp_auto_install_enabled',
'default' => 'no',
'type' => 'checkbox',
'desc_tip' => __('When enabled, updates will be automatically installed when WordPress performs background updates.', 'wc-tier-package-prices'),
'custom_attributes' => $auto_install_disabled ? array('disabled' => 'disabled') : array(),
),
array(
'title' => __('Check Frequency (Hours)', 'wc-tier-package-prices'),
'desc' => __('How often to check for updates.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_update_check_frequency',
'default' => '12',
'type' => 'number',
'css' => 'width: 80px;',
'desc_tip' => __('Number of hours between update checks. Default is 12 hours.', 'wc-tier-package-prices'),
'custom_attributes' => array(
'min' => '1',
'max' => '168',
),
),
array(
'title' => __('Update Status', 'wc-tier-package-prices'),
'type' => 'wc_tpp_update_status',
'id' => 'wc_tpp_update_status_display',
),
array(
'type' => 'sectionend',
'id' => 'wc_tpp_update_settings',
),
);
}
/**
* Get cached license status
*
* @return array|false
*/
private function get_cached_license_status() {
return get_transient('wc_tpp_license_status');
}
/**
* AJAX handler for license validation
*/
public function ajax_validate_license() {
check_ajax_referer('wc_tpp_license_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Permission denied.', 'wc-tier-package-prices')));
}
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
}
try {
$client = $this->get_license_client($server_url, $server_secret);
$domain = $this->get_current_domain();
$result = $client->validate($license_key, $domain);
// Cache the status
set_transient('wc_tpp_license_status', array(
'valid' => true,
'product_id' => $result->productId,
'expires_at' => $result->expiresAt?->format('Y-m-d H:i:s'),
'is_lifetime' => $result->isLifetime(),
'checked_at' => current_time('mysql'),
), DAY_IN_SECONDS);
wp_send_json_success(array(
'message' => __('License is valid!', 'wc-tier-package-prices'),
'status' => $this->get_cached_license_status(),
));
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
wp_send_json_error(array(
'message' => sprintf(
/* translators: %d: Number of seconds to wait */
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
$e->retryAfter ?? 60
),
'code' => 'rate_limit_exceeded',
'retry_after' => $e->retryAfter,
));
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
delete_transient('wc_tpp_license_status');
wp_send_json_error(array(
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
'code' => 'signature_error',
));
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
delete_transient('wc_tpp_license_status');
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => $e->errorCode ?? 'unknown',
));
} catch (\InvalidArgumentException $e) {
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => 'invalid_url',
));
} catch (\Exception $e) {
delete_transient('wc_tpp_license_status');
wp_send_json_error(array(
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
'code' => 'exception',
));
}
}
/**
* AJAX handler for license activation
*/
public function ajax_activate_license() {
check_ajax_referer('wc_tpp_license_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Permission denied.', 'wc-tier-package-prices')));
}
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
}
try {
$client = $this->get_license_client($server_url, $server_secret);
$domain = $this->get_current_domain();
$result = $client->activate($license_key, $domain);
if ($result->success) {
// Validate to get full status after activation
$validate_result = $client->validate($license_key, $domain);
set_transient('wc_tpp_license_status', array(
'valid' => true,
'product_id' => $validate_result->productId,
'expires_at' => $validate_result->expiresAt?->format('Y-m-d H:i:s'),
'is_lifetime' => $validate_result->isLifetime(),
'checked_at' => current_time('mysql'),
), DAY_IN_SECONDS);
wp_send_json_success(array(
'message' => __('License activated successfully!', 'wc-tier-package-prices'),
'status' => $this->get_cached_license_status(),
));
}
wp_send_json_error(array('message' => $result->message));
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
wp_send_json_error(array(
'message' => sprintf(
/* translators: %d: Number of seconds to wait */
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
$e->retryAfter ?? 60
),
'code' => 'rate_limit_exceeded',
'retry_after' => $e->retryAfter,
));
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
wp_send_json_error(array(
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
'code' => 'signature_error',
));
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => $e->errorCode ?? 'unknown',
));
} catch (\InvalidArgumentException $e) {
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => 'invalid_url',
));
} catch (\Exception $e) {
wp_send_json_error(array(
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
'code' => 'exception',
));
}
}
/**
* Get license client instance
*
* Uses SecureLicenseClient for HMAC signature verification.
*
* @param string $server_url License server URL.
* @param string $server_secret Shared secret for signature verification.
* @return \Magdev\WcLicensedProductClient\LicenseClientInterface
*/
private function get_license_client(string $server_url, string $server_secret): \Magdev\WcLicensedProductClient\LicenseClientInterface {
$httpClient = \Symfony\Component\HttpClient\HttpClient::create();
return new \Magdev\WcLicensedProductClient\SecureLicenseClient(
httpClient: $httpClient,
baseUrl: $server_url,
serverSecret: $server_secret,
);
}
/**
* Get current domain for license validation
*
* @return string
*/
private function get_current_domain(): string {
return wp_parse_url(home_url(), PHP_URL_HOST);
}
/**
* Output the settings
*/
public function output() {
global $current_section;
// Register custom field types
add_action('woocommerce_admin_field_wc_tpp_license_status', array($this, 'output_license_status_field'));
add_action('woocommerce_admin_field_wc_tpp_update_status', array($this, 'output_update_status_field'));
parent::output();
// Add JavaScript for license section
if ('license' === $current_section) {
$this->output_license_scripts();
}
// Add JavaScript for updates section
if ('updates' === $current_section) {
$this->output_updates_scripts();
}
}
/**
* Output license status custom field
*
* @param array $value Field configuration.
*/
public function output_license_status_field($value) {
$status = $this->get_cached_license_status();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label><?php esc_html_e('License Status', 'wc-tier-package-prices'); ?></label>
</th>
<td class="forminp">
<div id="wc-tpp-license-status-container" class="<?php echo !empty($status['valid']) ? 'valid' : 'invalid'; ?>">
<?php $this->render_license_status_html($status); ?>
</div>
<p class="description" style="margin-top: 10px;">
<button type="button" class="button" id="wc-tpp-validate-license">
<?php esc_html_e('Validate License', 'wc-tier-package-prices'); ?>
</button>
<button type="button" class="button" id="wc-tpp-activate-license">
<?php esc_html_e('Activate License', 'wc-tier-package-prices'); ?>
</button>
<span class="spinner" id="wc-tpp-license-spinner"></span>
</p>
</td>
</tr>
<?php
}
/**
* Render license status HTML
*
* @param array|false $status License status data.
*/
private function render_license_status_html($status) {
// Check for license bypass
$license_checker = WC_TPP_License_Checker::get_instance();
$bypass_reason = $license_checker->get_bypass_reason();
if ($bypass_reason) {
echo '<span class="wc-tpp-license-active">' . esc_html__('License Active', 'wc-tier-package-prices') . '</span>';
if ('localhost' === $bypass_reason) {
echo '<br><small>' . esc_html__('(Localhost environment - license validation bypassed)', 'wc-tier-package-prices') . '</small>';
} elseif ('self_licensing' === $bypass_reason) {
echo '<br><small>' . esc_html__('(Self-licensing server - license validation bypassed)', 'wc-tier-package-prices') . '</small>';
}
return;
}
if (empty($status)) {
echo '<span class="wc-tpp-license-inactive">' . esc_html__('No license activated', 'wc-tier-package-prices') . '</span>';
return;
}
if (!empty($status['valid'])) {
echo '<span class="wc-tpp-license-active">' . esc_html__('License Active', 'wc-tier-package-prices') . '</span>';
if (!empty($status['expires_at']) && empty($status['is_lifetime'])) {
echo '<br><small>' . sprintf(
/* translators: %s: Expiration date */
esc_html__('Expires: %s', 'wc-tier-package-prices'),
esc_html($status['expires_at'])
) . '</small>';
} elseif (!empty($status['is_lifetime'])) {
echo '<br><small>' . esc_html__('Lifetime License', 'wc-tier-package-prices') . '</small>';
}
if (!empty($status['checked_at'])) {
echo '<br><small>' . sprintf(
/* translators: %s: Last check timestamp */
esc_html__('Last checked: %s', 'wc-tier-package-prices'),
esc_html($status['checked_at'])
) . '</small>';
}
} else {
echo '<span class="wc-tpp-license-inactive">' . esc_html__('License Invalid', 'wc-tier-package-prices') . '</span>';
}
}
/**
* Output update status custom field
*
* @param array $value Field configuration.
*/
public function output_update_status_field($value) {
$update_checker = WC_TPP_Update_Checker::get_instance();
$available_version = $update_checker->get_available_version();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label><?php esc_html_e('Update Status', 'wc-tier-package-prices'); ?></label>
</th>
<td class="forminp">
<div id="wc-tpp-update-status-container">
<?php $this->render_update_status_html($available_version); ?>
</div>
<p class="description" style="margin-top: 10px;">
<button type="button" class="button" id="wc-tpp-check-updates">
<?php esc_html_e('Check for Updates', 'wc-tier-package-prices'); ?>
</button>
<span class="spinner" id="wc-tpp-update-spinner"></span>
</p>
</td>
</tr>
<?php
}
/**
* Render update status HTML
*
* @param string|null $available_version Available update version.
*/
private function render_update_status_html(?string $available_version) {
echo '<p><strong>' . esc_html__('Current Version:', 'wc-tier-package-prices') . '</strong> ' . esc_html(WC_TPP_VERSION) . '</p>';
if ($available_version) {
echo '<p class="wc-tpp-update-available"><strong>' . esc_html__('Update Available:', 'wc-tier-package-prices') . '</strong> ' . esc_html($available_version) . '</p>';
echo '<p><a href="' . esc_url(admin_url('update-core.php')) . '" class="button button-primary">' . esc_html__('Update Now', 'wc-tier-package-prices') . '</a></p>';
} else {
echo '<p class="wc-tpp-up-to-date">' . esc_html__('You are running the latest version.', 'wc-tier-package-prices') . '</p>';
}
}
/**
* Output JavaScript for license management
*/
private function output_license_scripts() {
$nonce = wp_create_nonce('wc_tpp_license_nonce');
?>
<script type="text/javascript">
jQuery(function($) {
var $validateBtn = $('#wc-tpp-validate-license');
var $activateBtn = $('#wc-tpp-activate-license');
var $spinner = $('#wc-tpp-license-spinner');
function getLicenseData() {
return {
license_key: $('#wc_tpp_license_key').val(),
server_url: $('#wc_tpp_license_server_url').val(),
server_secret: $('#wc_tpp_license_server_secret').val(),
nonce: '<?php echo esc_js($nonce); ?>'
};
}
function showSpinner() {
$spinner.addClass('is-active');
$validateBtn.prop('disabled', true);
$activateBtn.prop('disabled', true);
}
function hideSpinner() {
$spinner.removeClass('is-active');
$validateBtn.prop('disabled', false);
$activateBtn.prop('disabled', false);
}
$validateBtn.on('click', function() {
var data = getLicenseData();
if (!data.license_key || !data.server_url || !data.server_secret) {
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
return;
}
showSpinner();
$.post(ajaxurl, $.extend({action: 'wc_tpp_validate_license'}, data))
.done(function(response) {
if (response.success) {
alert(response.data.message);
location.reload();
} else {
alert(response.data.message || '<?php echo esc_js(__('Validation failed.', 'wc-tier-package-prices')); ?>');
}
})
.fail(function() {
alert('<?php echo esc_js(__('Request failed. Please try again.', 'wc-tier-package-prices')); ?>');
})
.always(hideSpinner);
});
$activateBtn.on('click', function() {
var data = getLicenseData();
if (!data.license_key || !data.server_url || !data.server_secret) {
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
return;
}
showSpinner();
$.post(ajaxurl, $.extend({action: 'wc_tpp_activate_license'}, data))
.done(function(response) {
if (response.success) {
alert(response.data.message);
location.reload();
} else {
alert(response.data.message || '<?php echo esc_js(__('Activation failed.', 'wc-tier-package-prices')); ?>');
}
})
.fail(function() {
alert('<?php echo esc_js(__('Request failed. Please try again.', 'wc-tier-package-prices')); ?>');
})
.always(hideSpinner);
});
});
</script>
<?php
}
/**
* AJAX handler for checking updates
*/
public function ajax_check_updates() {
check_ajax_referer('wc_tpp_update_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Permission denied.', 'wc-tier-package-prices')));
}
$update_checker = WC_TPP_Update_Checker::get_instance();
$update_info = $update_checker->force_check();
if ($update_info && !empty($update_info['update_available'])) {
wp_send_json_success(array(
'message' => sprintf(
/* translators: %s: Version number */
__('Update available: version %s', 'wc-tier-package-prices'),
$update_info['version']
),
'update_available' => true,
'version' => $update_info['version'],
'current_version' => WC_TPP_VERSION,
));
} else {
wp_send_json_success(array(
'message' => __('You are running the latest version.', 'wc-tier-package-prices'),
'update_available' => false,
'current_version' => WC_TPP_VERSION,
));
}
}
/**
* Output JavaScript for update management
*/
private function output_updates_scripts() {
$nonce = wp_create_nonce('wc_tpp_update_nonce');
?>
<script type="text/javascript">
jQuery(function($) {
var $checkBtn = $('#wc-tpp-check-updates');
var $spinner = $('#wc-tpp-update-spinner');
var $container = $('#wc-tpp-update-status-container');
$checkBtn.on('click', function() {
$spinner.addClass('is-active');
$checkBtn.prop('disabled', true);
$.post(ajaxurl, {
action: 'wc_tpp_check_updates',
nonce: '<?php echo esc_js($nonce); ?>'
})
.done(function(response) {
if (response.success) {
var html = '<p><strong><?php echo esc_js(__('Current Version:', 'wc-tier-package-prices')); ?></strong> ' + response.data.current_version + '</p>';
if (response.data.update_available) {
html += '<p class="wc-tpp-update-available"><strong><?php echo esc_js(__('Update Available:', 'wc-tier-package-prices')); ?></strong> ' + response.data.version + '</p>';
html += '<p><a href="<?php echo esc_url(admin_url('update-core.php')); ?>" class="button button-primary"><?php echo esc_js(__('Update Now', 'wc-tier-package-prices')); ?></a></p>';
} else {
html += '<p class="wc-tpp-up-to-date"><?php echo esc_js(__('You are running the latest version.', 'wc-tier-package-prices')); ?></p>';
}
$container.html(html);
} else {
alert(response.data.message || '<?php echo esc_js(__('Failed to check for updates.', 'wc-tier-package-prices')); ?>');
}
})
.fail(function() {
alert('<?php echo esc_js(__('Request failed. Please try again.', 'wc-tier-package-prices')); ?>');
})
.always(function() {
$spinner.removeClass('is-active');
$checkBtn.prop('disabled', false);
});
});
});
</script>
<?php
}
}
}

View File

@@ -0,0 +1,468 @@
<?php
/**
* Update Checker
*
* Handles WordPress plugin updates from the license server
*
* @package WC_Tier_Package_Prices
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* WC_TPP_Update_Checker class
*/
if (!class_exists('WC_TPP_Update_Checker')) {
class WC_TPP_Update_Checker {
/**
* Singleton instance
*
* @var WC_TPP_Update_Checker|null
*/
private static $instance = null;
/**
* Plugin slug
*/
private const PLUGIN_SLUG = 'wc-tier-and-package-prices';
/**
* Update check cache key
*/
private const CACHE_KEY = 'wc_tpp_update_info';
/**
* Default check frequency in hours
*/
private const DEFAULT_CHECK_FREQUENCY = 12;
/**
* Get singleton instance
*
* @return WC_TPP_Update_Checker
*/
public static function get_instance(): WC_TPP_Update_Checker {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
// Only register if updates are not disabled
if ($this->is_disabled()) {
return;
}
$this->register_hooks();
}
/**
* Check if auto-updates are disabled via constant
*
* @return bool
*/
private function is_disabled(): bool {
return defined('WC_TPP_DISABLE_AUTO_UPDATE') && WC_TPP_DISABLE_AUTO_UPDATE;
}
/**
* Register WordPress hooks
*/
private function register_hooks(): void {
// Check for updates
add_filter('pre_set_site_transient_update_plugins', array($this, 'check_for_updates'));
// Provide plugin information for update modal
add_filter('plugins_api', array($this, 'get_plugin_info'), 10, 3);
// Add authentication headers to download requests
add_filter('http_request_args', array($this, 'add_auth_headers'), 10, 2);
// Handle auto-install setting
add_filter('auto_update_plugin', array($this, 'handle_auto_install'), 10, 2);
// Clear cache when settings change
add_action('update_option_wc_tpp_license_key', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_license_server_url', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_update_notification_enabled', array($this, 'clear_cache'));
}
/**
* Check if update notifications are enabled
*
* @return bool
*/
public static function is_update_notification_enabled(): bool {
return get_option('wc_tpp_update_notification_enabled', 'yes') === 'yes';
}
/**
* Check if auto-install is enabled
*
* @return bool
*/
public static function is_auto_install_enabled(): bool {
return get_option('wc_tpp_auto_install_enabled', 'no') === 'yes';
}
/**
* Get update check frequency in hours
*
* @return int
*/
public static function get_check_frequency(): int {
$frequency = (int) get_option('wc_tpp_update_check_frequency', self::DEFAULT_CHECK_FREQUENCY);
return max(1, min(168, $frequency)); // Clamp between 1 and 168 hours
}
/**
* Check for plugin updates
*
* @param object $transient WordPress update transient.
* @return object
*/
public function check_for_updates($transient) {
if (empty($transient->checked)) {
return $transient;
}
// Skip if notifications disabled
if (!self::is_update_notification_enabled()) {
return $transient;
}
// Get update info (cached)
$update_info = $this->get_update_info();
if (empty($update_info) || !$update_info['update_available']) {
return $transient;
}
// Build update object
$update_obj = $this->build_update_object($update_info);
if ($update_obj) {
$transient->response[WC_TPP_PLUGIN_BASENAME] = $update_obj;
}
return $transient;
}
/**
* Get plugin information for update modal
*
* @param false|object|array $result The result object or array.
* @param string $action The type of information being requested.
* @param object $args Plugin API arguments.
* @return false|object
*/
public function get_plugin_info($result, $action, $args) {
if ('plugin_information' !== $action) {
return $result;
}
if (!isset($args->slug) || self::PLUGIN_SLUG !== $args->slug) {
return $result;
}
// Get update info
$update_info = $this->get_update_info(true); // Force fetch for full info
if (empty($update_info)) {
return $result;
}
// Build plugin info object
return $this->build_plugin_info_object($update_info);
}
/**
* Add authentication headers to download requests
*
* @param array $args HTTP request arguments.
* @param string $url The request URL.
* @return array
*/
public function add_auth_headers(array $args, string $url): array {
$server_url = get_option('wc_tpp_license_server_url', '');
// Only add headers for requests to our license server
if (empty($server_url) || strpos($url, $server_url) !== 0) {
return $args;
}
$license_key = get_option('wc_tpp_license_key', '');
if (!empty($license_key)) {
$args['headers']['X-License-Key'] = $license_key;
}
return $args;
}
/**
* Handle auto-install setting
*
* @param bool|null $update Whether to auto-update.
* @param object $item The plugin update object.
* @return bool|null
*/
public function handle_auto_install($update, $item) {
// Check if this is our plugin
if (!isset($item->plugin) || WC_TPP_PLUGIN_BASENAME !== $item->plugin) {
return $update;
}
// Return our setting, or default behavior
if (self::is_auto_install_enabled()) {
return true;
}
return $update;
}
/**
* Get update info from cache or server
*
* @param bool $force_fetch Force fetching from server.
* @return array|null
*/
private function get_update_info(bool $force_fetch = false): ?array {
// Check cache first
if (!$force_fetch) {
$cached = get_transient(self::CACHE_KEY);
if (false !== $cached) {
return $cached;
}
}
// Fetch from server
$update_info = $this->fetch_update_info();
if ($update_info) {
// Cache the result
$cache_ttl = self::get_check_frequency() * HOUR_IN_SECONDS;
set_transient(self::CACHE_KEY, $update_info, $cache_ttl);
}
return $update_info;
}
/**
* Fetch update info from license server
*
* @return array|null
*/
private function fetch_update_info(): ?array {
$server_url = get_option('wc_tpp_license_server_url', '');
$license_key = get_option('wc_tpp_license_key', '');
$server_secret = get_option('wc_tpp_license_server_secret', '');
if (empty($server_url)) {
return null;
}
// Build the update check endpoint
$endpoint = trailingslashit($server_url) . 'wp-json/wc-licensed-product/v1/update-check';
// Get license checker for domain
$license_checker = WC_TPP_License_Checker::get_instance();
$domain = $license_checker->get_current_domain();
// Remove port for API call
$domain = preg_replace('/:[\d]+$/', '', $domain);
// Prepare request body
$body = array(
'license_key' => $license_key,
'domain' => $domain,
'plugin_slug' => self::PLUGIN_SLUG,
'current_version' => WC_TPP_VERSION,
);
// Make the request
$response = wp_remote_post($endpoint, array(
'timeout' => 15,
'headers' => array(
'Content-Type' => 'application/json',
'Accept' => 'application/json',
),
'body' => wp_json_encode($body),
));
if (is_wp_error($response)) {
return null;
}
$response_code = wp_remote_retrieve_response_code($response);
// Handle rate limiting
if (429 === $response_code) {
return null;
}
// Handle other errors
if ($response_code < 200 || $response_code >= 300) {
return null;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (empty($data) || !isset($data['success'])) {
return null;
}
return $data;
}
/**
* Build WordPress update object
*
* @param array $update_info Update information from server.
* @return object|null
*/
private function build_update_object(array $update_info): ?object {
if (empty($update_info['version'])) {
return null;
}
// Check if update is actually available
if (version_compare(WC_TPP_VERSION, $update_info['version'], '>=')) {
return null;
}
$obj = new \stdClass();
$obj->id = $update_info['id'] ?? self::PLUGIN_SLUG;
$obj->slug = $update_info['slug'] ?? self::PLUGIN_SLUG;
$obj->plugin = $update_info['plugin'] ?? WC_TPP_PLUGIN_BASENAME;
$obj->new_version = $update_info['version'];
$obj->url = $update_info['url'] ?? '';
$obj->package = $update_info['download_url'] ?? $update_info['package'] ?? '';
$obj->tested = $update_info['tested'] ?? '';
$obj->requires = $update_info['requires'] ?? '6.0';
$obj->requires_php = $update_info['requires_php'] ?? '8.3';
// Icons
if (!empty($update_info['icons'])) {
$obj->icons = $update_info['icons'];
}
// Banners
if (!empty($update_info['banners'])) {
$obj->banners = $update_info['banners'];
}
return $obj;
}
/**
* Build plugin info object for update modal
*
* @param array $update_info Update information from server.
* @return object
*/
private function build_plugin_info_object(array $update_info): object {
$obj = new \stdClass();
$obj->name = $update_info['name'] ?? 'WooCommerce Tier and Package Prices';
$obj->slug = $update_info['slug'] ?? self::PLUGIN_SLUG;
$obj->version = $update_info['version'] ?? WC_TPP_VERSION;
$obj->author = $update_info['author'] ?? '<a href="https://src.bundespruefstelle.ch/magdev">Marco Graetsch</a>';
$obj->author_profile = $update_info['author_profile'] ?? 'https://src.bundespruefstelle.ch/magdev';
$obj->homepage = $update_info['homepage'] ?? $update_info['url'] ?? '';
$obj->requires = $update_info['requires'] ?? '6.0';
$obj->tested = $update_info['tested'] ?? '';
$obj->requires_php = $update_info['requires_php'] ?? '8.3';
$obj->last_updated = $update_info['last_updated'] ?? '';
$obj->download_link = $update_info['download_url'] ?? $update_info['package'] ?? '';
// Sections (description, changelog, etc.)
$obj->sections = array();
if (!empty($update_info['sections']['description'])) {
$obj->sections['description'] = $update_info['sections']['description'];
} else {
$obj->sections['description'] = __('Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices.', 'wc-tier-package-prices');
}
if (!empty($update_info['sections']['changelog'])) {
$obj->sections['changelog'] = $update_info['sections']['changelog'];
} elseif (!empty($update_info['changelog'])) {
$obj->sections['changelog'] = $update_info['changelog'];
}
if (!empty($update_info['sections']['installation'])) {
$obj->sections['installation'] = $update_info['sections']['installation'];
}
// Icons
if (!empty($update_info['icons'])) {
$obj->icons = $update_info['icons'];
}
// Banners
if (!empty($update_info['banners'])) {
$obj->banners = $update_info['banners'];
}
return $obj;
}
/**
* Clear update cache
*/
public function clear_cache(): void {
delete_transient(self::CACHE_KEY);
// Also clear WordPress plugin update transient to force recheck
delete_site_transient('update_plugins');
}
/**
* Force check for updates
*
* @return array|null
*/
public function force_check(): ?array {
$this->clear_cache();
return $this->get_update_info(true);
}
/**
* Get the available update version if any
*
* @return string|null
*/
public function get_available_version(): ?string {
$update_info = $this->get_update_info();
if (empty($update_info) || empty($update_info['version'])) {
return null;
}
// Check if it's actually newer
if (version_compare(WC_TPP_VERSION, $update_info['version'], '>=')) {
return null;
}
return $update_info['version'];
}
/**
* Check if an update is available
*
* @return bool
*/
public function is_update_available(): bool {
return null !== $this->get_available_version();
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,495 @@
# German (Switzerland) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2026 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Switzerland)\n"
"Language: de_CH\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
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Staffel- & Paketpreise"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "Allgemein"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Staffel- & Paketpreise Einstellungen"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Konfigurieren Sie Staffelpreise und Paketpreise für Ihre WooCommerce-Produkte."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Staffelpreise aktivieren"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Staffelpreise für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Ermöglicht mengenbasierte Preisstaffeln. Kunden erhalten reduzierte Preise beim Kauf grösserer Mengen."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Paketpreise aktivieren"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Festpreis-Pakete für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Ermöglicht Festpreis-Pakete mit bestimmten Mengen. Zum Beispiel: 10 Stück für CHF 50.-, 25 Stück für CHF 100.-."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Preistabelle anzeigen"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Staffel- und Paketpreis-Tabelle auf Produktseiten anzeigen"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Zeigt die Preistabelle den Kunden auf Produktseiten an."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Anzeigeposition"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Wählen Sie, wo die Preistabelle auf Produktseiten angezeigt werden soll."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Vor \"In den Warenkorb\"-Button"
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "Nach \"In den Warenkorb\"-Button"
#: 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
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "Nach dem Preis"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Auf Paketmengen beschränken"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Mengen nur auf definierte Paketgrössen beschränken"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "Wenn aktiviert, können Kunden Produkte nur in den genau definierten Paketmengen kaufen. Das Mengeneingabefeld wird ausgeblendet und durch Paketauswahl-Buttons ersetzt."
#: 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ö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:77
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"
#: 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 "Sie sparen"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Paketangebote"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Wählen Sie unten eine Paketgrösse"
#: 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"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "dieses Produkt"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wählen Sie aus den verfügbaren Paketgrössen: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "Optionen 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Staffel- und Paketpreise erfordert PHP 8.3 oder höher. Ihr Server verwendet PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "Lizenzverwaltung"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Geben Sie Ihren Lizenzschlüssel ein, um Updates und Support zu erhalten."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "Lizenzserver-URL"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "Die URL des Lizenzservers."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "Lizenzschlüssel"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "Ihr Lizenzschlüssel für dieses Plugin."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "Lizenzstatus"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Lizenz überprüfen"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Lizenz aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "Keine Lizenz aktiviert"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "Lizenz aktiv"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "Lizenz ungültig"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Lebenslange Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Läuft ab: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Zuletzt geprüft: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "Lizenz ist gültig!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "Lizenz erfolgreich aktiviert!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Zugriff verweigert."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Server-Geheimnis"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Anfragelimit überschritten. Bitte versuchen Sie es in %d Sekunden erneut."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfen Sie Ihr Server-Geheimnis."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Bitte geben Sie die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "Überprüfung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "Aktivierung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "Anfrage fehlgeschlagen. Bitte versuchen Sie es erneut."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Auto-Updates"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Auto-Update Einstellungen"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Konfigurieren Sie automatische Plugin-Updates vom Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Update-Benachrichtigungen aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Nach verfügbaren Plugin-Updates suchen."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "Wenn aktiviert, prüft das Plugin auf Updates vom Lizenzserver und zeigt Benachrichtigungen im WordPress-Admin an."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Updates automatisch installieren"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Updates automatisch installieren, wenn verfügbar."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Erfordert eine gültige Lizenz)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "Wenn aktiviert, werden Updates automatisch installiert, wenn WordPress Hintergrund-Updates durchführt."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Prüfhäufigkeit (Stunden)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "Wie oft nach Updates gesucht werden soll."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Anzahl der Stunden zwischen Update-Prüfungen. Standard ist 12 Stunden."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Update-Status"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Nach Updates suchen"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Aktuelle Version:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Update verfügbar:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "Sie verwenden die neueste Version."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Jetzt aktualisieren"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Update verfügbar: Version %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Fehler beim Suchen nach Updates."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Localhost-Umgebung - Lizenzvalidierung übersprungen)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Selbstlizenzierender Server - Lizenzvalidierung übersprungen)"

View File

@@ -1,12 +1,12 @@
# German (Switzerland, Informal) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2025 Marco Graetsch
# Copyright (C) 2026 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"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\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: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Switzerland)\n"
"Language: de_CH_informal\n"
@@ -23,37 +23,81 @@ msgstr "WooCommerce Staffel- und Paketpreise benötigt eine installierte und akt
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Staffel- & Paketpreise"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "Allgemein"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Staffel- & Paketpreise Einstellungen"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Konfiguriere Staffelpreise und Paketpreise für deine WooCommerce-Produkte."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Staffelpreise aktivieren"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Staffelpreise für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Ermöglicht mengenbasierte Preisstaffeln. Kunden erhalten reduzierte Preise beim Kauf grösserer Mengen."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Paketpreise aktivieren"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Festpreis-Pakete für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Ermöglicht Festpreis-Pakete mit bestimmten Mengen. Zum Beispiel: 10 Stück für CHF 50.-, 25 Stück für CHF 100.-."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Preistabelle anzeigen"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Staffel- und Paketpreis-Tabelle auf Produktseiten anzeigen"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Zeigt die Preistabelle den Kunden auf Produktseiten an."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Anzeigeposition"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Wähle, wo die Preistabelle auf Produktseiten angezeigt werden soll."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Vor \"In den Warenkorb\"-Button"
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "Nach \"In den Warenkorb\"-Button"
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Vor \"In den Warenkorb\""
@@ -63,9 +107,23 @@ msgid "After Add to Cart"
msgstr "Nach \"In den Warenkorb\""
#: includes/class-wc-tpp-admin.php:87
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "Nach dem Preis"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Auf Paketmengen beschränken"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Mengen nur auf definierte Paketgrössen beschränken"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "Wenn aktiviert, kannst du Produkte nur in den genau definierten Paketmengen kaufen. Das Mengeneingabefeld wird ausgeblendet und durch Paketauswahl-Buttons ersetzt."
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Staffelpreise"
@@ -90,6 +148,30 @@ msgstr "Festpreis-Pakete mit bestimmten Mengen festlegen. Zum Beispiel: 10 Stüc
msgid "Add Package"
msgstr "Paket hinzufügen"
#: includes/class-wc-tpp-product-meta.php:77
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"
@@ -142,6 +224,10 @@ msgstr "Du sparst"
msgid "Package Deals"
msgstr "Paketangebote"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Wähle unten eine Paketgrösse"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "Stück"
@@ -161,3 +247,249 @@ msgstr "Paketpreis"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Mengenrabatt"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "dieses Produkt"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wähle aus den verfügbaren Paketgrössen: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "Optionen 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Staffel- und Paketpreise erfordert PHP 8.3 oder höher. Dein Server verwendet PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "Lizenzverwaltung"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Gib deinen Lizenzschlüssel ein, um Updates und Support zu erhalten."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "Lizenzserver-URL"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "Die URL des Lizenzservers."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "Lizenzschlüssel"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "Dein Lizenzschlüssel für dieses Plugin."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "Lizenzstatus"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Lizenz überprüfen"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Lizenz aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "Keine Lizenz aktiviert"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "Lizenz aktiv"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "Lizenz ungültig"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Lebenslange Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Läuft ab: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Zuletzt geprüft: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "Lizenz ist gültig!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "Lizenz erfolgreich aktiviert!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Zugriff verweigert."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Server-Geheimnis"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Anfragelimit überschritten. Bitte versuche es in %d Sekunden erneut."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfe dein Server-Geheimnis."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Bitte gib die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "Überprüfung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "Aktivierung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "Anfrage fehlgeschlagen. Bitte versuche es erneut."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Auto-Updates"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Auto-Update Einstellungen"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Konfiguriere automatische Plugin-Updates vom Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Update-Benachrichtigungen aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Nach verfügbaren Plugin-Updates suchen."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "Wenn aktiviert, prüft das Plugin auf Updates vom Lizenzserver und zeigt Benachrichtigungen im WordPress-Admin an."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Updates automatisch installieren"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Updates automatisch installieren, wenn verfügbar."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Erfordert eine gültige Lizenz)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "Wenn aktiviert, werden Updates automatisch installiert, wenn WordPress Hintergrund-Updates durchführt."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Prüfhäufigkeit (Stunden)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "Wie oft nach Updates gesucht werden soll."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Anzahl der Stunden zwischen Update-Prüfungen. Standard ist 12 Stunden."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Update-Status"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Nach Updates suchen"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Aktuelle Version:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Update verfügbar:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "Du verwendest die neueste Version."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Jetzt aktualisieren"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Update verfügbar: Version %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Fehler beim Suchen nach Updates."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Localhost-Umgebung - Lizenzvalidierung übersprungen)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Selbstlizenzierender Server - Lizenzvalidierung übersprungen)"

View File

@@ -1,12 +1,12 @@
# German (Germany) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2025 Marco Graetsch
# Copyright (C) 2026 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"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\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: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -23,37 +23,81 @@ msgstr "WooCommerce Staffel- und Paketpreise erfordert, dass WooCommerce install
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Staffel- & Paketpreise"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "Allgemein"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Staffel- & Paketpreise Einstellungen"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Konfigurieren Sie Staffelpreise und Paketpreise für Ihre WooCommerce-Produkte."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Staffelpreise aktivieren"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Staffelpreise für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Ermöglicht mengenbasierte Preisstaffeln. Kunden erhalten reduzierte Preise beim Kauf größerer Mengen."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Paketpreise aktivieren"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Festpreis-Pakete für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Ermöglicht Festpreis-Pakete mit bestimmten Mengen. Zum Beispiel: 10 Stück für 50€, 25 Stück für 100€."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Preistabelle anzeigen"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Staffel- und Paketpreis-Tabelle auf Produktseiten anzeigen"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Zeigt die Preistabelle den Kunden auf Produktseiten an."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Anzeigeposition"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Wählen Sie, wo die Preistabelle auf Produktseiten angezeigt werden soll."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Vor \"In den Warenkorb\"-Button"
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "Nach \"In den Warenkorb\"-Button"
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Vor \"In den Warenkorb\""
@@ -63,9 +107,23 @@ msgid "After Add to Cart"
msgstr "Nach \"In den Warenkorb\""
#: includes/class-wc-tpp-admin.php:87
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "Nach dem Preis"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Auf Paketmengen beschränken"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Mengen nur auf definierte Paketgrößen beschränken"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "Wenn aktiviert, können Kunden Produkte nur in den genau definierten Paketmengen kaufen. Das Mengeneingabefeld wird ausgeblendet und durch Paketauswahl-Buttons ersetzt."
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Staffelpreise"
@@ -90,6 +148,30 @@ msgstr "Festpreis-Pakete mit bestimmten Mengen festlegen. Zum Beispiel: 10 Stüc
msgid "Add Package"
msgstr "Paket hinzufügen"
#: includes/class-wc-tpp-product-meta.php:77
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"
@@ -142,6 +224,10 @@ msgstr "Sie sparen"
msgid "Package Deals"
msgstr "Paketangebote"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Wählen Sie unten eine Paketgröße"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "Stück"
@@ -161,3 +247,249 @@ msgstr "Paketpreis"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Mengenrabatt"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "dieses Produkt"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wählen Sie aus den verfügbaren Paketgrößen: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "Optionen 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Staffel- und Paketpreise erfordert PHP 8.3 oder höher. Ihr Server verwendet PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "Lizenzverwaltung"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Geben Sie Ihren Lizenzschlüssel ein, um Updates und Support zu erhalten."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "Lizenzserver-URL"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "Die URL des Lizenzservers."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "Lizenzschlüssel"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "Ihr Lizenzschlüssel für dieses Plugin."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "Lizenzstatus"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Lizenz überprüfen"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Lizenz aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "Keine Lizenz aktiviert"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "Lizenz aktiv"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "Lizenz ungültig"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Lebenslange Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Läuft ab: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Zuletzt geprüft: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "Lizenz ist gültig!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "Lizenz erfolgreich aktiviert!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Zugriff verweigert."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Server-Geheimnis"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Anfragelimit überschritten. Bitte versuchen Sie es in %d Sekunden erneut."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfen Sie Ihr Server-Geheimnis."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Bitte geben Sie die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "Überprüfung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "Aktivierung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "Anfrage fehlgeschlagen. Bitte versuchen Sie es erneut."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Auto-Updates"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Auto-Update Einstellungen"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Konfigurieren Sie automatische Plugin-Updates vom Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Update-Benachrichtigungen aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Nach verfügbaren Plugin-Updates suchen."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "Wenn aktiviert, prüft das Plugin auf Updates vom Lizenzserver und zeigt Benachrichtigungen im WordPress-Admin an."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Updates automatisch installieren"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Updates automatisch installieren, wenn verfügbar."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Erfordert eine gültige Lizenz)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "Wenn aktiviert, werden Updates automatisch installiert, wenn WordPress Hintergrund-Updates durchführt."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Prüfhäufigkeit (Stunden)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "Wie oft nach Updates gesucht werden soll."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Anzahl der Stunden zwischen Update-Prüfungen. Standard ist 12 Stunden."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Update-Status"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Nach Updates suchen"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Aktuelle Version:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Update verfügbar:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "Sie verwenden die neueste Version."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Jetzt aktualisieren"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Update verfügbar: Version %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Fehler beim Suchen nach Updates."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Localhost-Umgebung - Lizenzvalidierung übersprungen)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Selbstlizenzierender Server - Lizenzvalidierung übersprungen)"

Binary file not shown.

View File

@@ -0,0 +1,495 @@
# German (Germany, Informal) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2026 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: German (Germany)\n"
"Language: de_DE_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 erfordert, dass WooCommerce installiert und aktiviert ist."
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Staffel- & Paketpreise"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "Allgemein"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Staffel- & Paketpreise Einstellungen"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Konfiguriere Staffelpreise und Paketpreise für deine WooCommerce-Produkte."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Staffelpreise aktivieren"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Staffelpreise für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Ermöglicht mengenbasierte Preisstaffeln. Kunden erhalten reduzierte Preise beim Kauf größerer Mengen."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Paketpreise aktivieren"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Festpreis-Pakete für Produkte aktivieren"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Ermöglicht Festpreis-Pakete mit bestimmten Mengen. Zum Beispiel: 10 Stück für 50€, 25 Stück für 100€."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Preistabelle anzeigen"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Staffel- und Paketpreis-Tabelle auf Produktseiten anzeigen"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Zeigt die Preistabelle den Kunden auf Produktseiten an."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Anzeigeposition"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Wähle, wo die Preistabelle auf Produktseiten angezeigt werden soll."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Vor \"In den Warenkorb\"-Button"
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "Nach \"In den Warenkorb\"-Button"
#: 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
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "Nach dem Preis"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Auf Paketmengen beschränken"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Mengen nur auf definierte Paketgrößen beschränken"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "Wenn aktiviert, kannst du Produkte nur in den genau definierten Paketmengen kaufen. Das Mengeneingabefeld wird ausgeblendet und durch Paketauswahl-Buttons ersetzt."
#: 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:77
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"
#: 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 "Du sparst"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Paketangebote"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Wähle unten eine Paketgröße"
#: 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"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "dieses Produkt"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "Die Menge %1$d ist für %2$s nicht verfügbar. Bitte wähle aus den verfügbaren Paketgrößen: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "Optionen 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Staffel- und Paketpreise erfordert PHP 8.3 oder höher. Dein Server verwendet PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "Lizenzverwaltung"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Gib deinen Lizenzschlüssel ein, um Updates und Support zu erhalten."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "Lizenzserver-URL"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "Die URL des Lizenzservers."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "Lizenzschlüssel"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "Dein Lizenzschlüssel für dieses Plugin."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "Lizenzstatus"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Lizenz überprüfen"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Lizenz aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "Keine Lizenz aktiviert"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "Lizenz aktiv"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "Lizenz ungültig"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Lebenslange Lizenz"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Läuft ab: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Zuletzt geprüft: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "Lizenz ist gültig!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "Lizenz erfolgreich aktiviert!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Zugriff verweigert."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Server-Geheimnis"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Anfragelimit überschritten. Bitte versuche es in %d Sekunden erneut."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfe dein Server-Geheimnis."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Bitte gib die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "Überprüfung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "Aktivierung fehlgeschlagen."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "Anfrage fehlgeschlagen. Bitte versuche es erneut."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Auto-Updates"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Auto-Update Einstellungen"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Konfiguriere automatische Plugin-Updates vom Lizenzserver."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Update-Benachrichtigungen aktivieren"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Nach verfügbaren Plugin-Updates suchen."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "Wenn aktiviert, prüft das Plugin auf Updates vom Lizenzserver und zeigt Benachrichtigungen im WordPress-Admin an."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Updates automatisch installieren"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Updates automatisch installieren, wenn verfügbar."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Erfordert eine gültige Lizenz)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "Wenn aktiviert, werden Updates automatisch installiert, wenn WordPress Hintergrund-Updates durchführt."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Prüfhäufigkeit (Stunden)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "Wie oft nach Updates gesucht werden soll."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Anzahl der Stunden zwischen Update-Prüfungen. Standard ist 12 Stunden."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Update-Status"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Nach Updates suchen"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Aktuelle Version:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Update verfügbar:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "Du verwendest die neueste Version."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Jetzt aktualisieren"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Update verfügbar: Version %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Fehler beim Suchen nach Updates."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Localhost-Umgebung - Lizenzvalidierung übersprungen)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Selbstlizenzierender Server - Lizenzvalidierung übersprungen)"

View File

@@ -1,12 +1,12 @@
# English (US) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2025 Marco Graetsch
# Copyright (C) 2026 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"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\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: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -23,37 +23,81 @@ msgstr "WooCommerce Tier and Package Prices requires WooCommerce to be installed
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Tier & Package Prices"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "General"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Tier & Package Prices Settings"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Configure tier pricing and package pricing options for your WooCommerce products."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Enable Tier Pricing"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Enable tier pricing for products"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Enable Package Pricing"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Enable fixed-price packages for products"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Display Pricing Table"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Show tier and package pricing table on product pages"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Display the pricing table to customers on product pages."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Display Position"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Choose where to display the pricing table on product pages."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Before Add to Cart Button"
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "After Add to Cart Button"
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Before Add to Cart"
@@ -63,9 +107,23 @@ msgid "After Add to Cart"
msgstr "After Add to Cart"
#: includes/class-wc-tpp-admin.php:87
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "After Price"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Restrict to Package Quantities"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Limit quantities to defined package sizes only"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Tier Pricing"
@@ -90,6 +148,30 @@ msgstr "Set fixed-price packages with specific quantities. For example: 10 piece
msgid "Add Package"
msgstr "Add Package"
#: includes/class-wc-tpp-product-meta.php:77
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"
@@ -142,6 +224,10 @@ msgstr "You Save"
msgid "Package Deals"
msgstr "Package Deals"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Choose a package size below"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "pieces"
@@ -161,3 +247,249 @@ msgstr "Package price"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Volume discount"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "this product"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "View Options"
#: 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "License"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "License Management"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Enter your license key to receive updates and support."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "License Server URL"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "The URL of the license server."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "License Key"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "Your license key for this plugin."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "License Status"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Validate License"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Activate License"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "No license activated"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "License Active"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "License Invalid"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Lifetime License"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Expires: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Last checked: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "License is valid!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "License activated successfully!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Permission denied."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Server Secret"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "The shared secret for secure communication with the license server."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "License key, server URL, and server secret are required."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Rate limit exceeded. Please try again in %d seconds."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "Response signature verification failed. Please check your server secret."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "An unexpected error occurred. Please try again."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Please enter license server URL, license key, and server secret."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "Validation failed."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "Activation failed."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "Request failed. Please try again."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Auto-Updates"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Auto-Update Settings"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Configure automatic plugin updates from the license server."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Enable Update Notifications"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Check for available plugin updates."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Automatically Install Updates"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Automatically install updates when available."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Requires a valid license)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "When enabled, updates will be automatically installed when WordPress performs background updates."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Check Frequency (Hours)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "How often to check for updates."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Number of hours between update checks. Default is 12 hours."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Update Status"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Check for Updates"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Current Version:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Update Available:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "You are running the latest version."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Update Now"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Update available: version %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Failed to check for updates."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Localhost environment - license validation bypassed)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Self-licensing server - license validation bypassed)"

Binary file not shown.

View File

@@ -0,0 +1,495 @@
# French (Switzerland) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2026 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: French (Switzerland)\n"
"Language: fr_CH\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 Prix Échelonnés et Forfaitaires nécessite que WooCommerce soit installé et actif."
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Prix Échelonnés & Forfaitaires"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "Général"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Paramètres Prix Échelonnés & Forfaitaires"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Configurez les options de prix échelonnés et forfaitaires pour vos produits WooCommerce."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Activer les prix échelonnés"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Activer les prix échelonnés pour les produits"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Permet des paliers de prix basés sur la quantité. Les clients bénéficient de prix réduits lors de l'achat de quantités plus importantes."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Activer les prix forfaitaires"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Activer les forfaits à prix fixe pour les produits"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Permet des forfaits à prix fixe avec des quantités spécifiques. Par exemple: 10 pièces pour CHF 50.-, 25 pièces pour CHF 100.-."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Afficher le tableau des prix"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Afficher le tableau des prix échelonnés et forfaitaires sur les pages produits"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Affiche le tableau des prix aux clients sur les pages produits."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Position d'affichage"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Choisissez où afficher le tableau des prix sur les pages produits."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Avant le bouton \"Ajouter au panier\""
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "Après le bouton \"Ajouter au panier\""
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Avant \"Ajouter au panier\""
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
msgstr "Après \"Ajouter au panier\""
#: includes/class-wc-tpp-admin.php:87
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "Après le prix"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Restreindre aux quantités forfaitaires"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Limiter les quantités aux tailles de forfaits définies uniquement"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "Lorsque cette option est activée, les clients ne peuvent acheter les produits que dans les quantités exactes définies dans les forfaits. Le champ de saisie de quantité sera masqué et remplacé par des boutons de sélection de forfait."
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Prix échelonnés"
#: includes/class-wc-tpp-product-meta.php:24
msgid "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Définir des paliers de prix basés sur la quantité. Les clients bénéficient de réductions lors de l'achat de quantités plus importantes."
#: includes/class-wc-tpp-product-meta.php:41
msgid "Add Tier"
msgstr "Ajouter un palier"
#: includes/class-wc-tpp-product-meta.php:52
msgid "Package Pricing"
msgstr "Prix forfaitaires"
#: 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 "Définir des forfaits à prix fixe avec des quantités spécifiques. Par exemple: 10 pièces pour CHF 50.-, 25 pièces pour CHF 100.-."
#: includes/class-wc-tpp-product-meta.php:70
msgid "Add Package"
msgstr "Ajouter un forfait"
#: includes/class-wc-tpp-product-meta.php:77
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"
#: includes/class-wc-tpp-product-meta.php:91
msgid "e.g., 10"
msgstr "p.ex. 10"
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
msgid "e.g., 9.99"
msgstr "p.ex. 9.90"
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
msgid "Remove"
msgstr "Supprimer"
#: includes/class-wc-tpp-product-meta.php:109
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr "Quantité"
#: includes/class-wc-tpp-product-meta.php:113
msgid "Fixed Price"
msgstr "Prix fixe"
#: includes/class-wc-tpp-product-meta.php:117
msgid "Label (Optional)"
msgstr "Étiquette (Optionnel)"
#: includes/class-wc-tpp-product-meta.php:118
msgid "e.g., Starter Pack"
msgstr "p.ex. Pack de démarrage"
#: includes/class-wc-tpp-frontend.php:71
msgid "Volume Discounts"
msgstr "Remises sur quantité"
#: includes/class-wc-tpp-product-meta.php:94
#: includes/class-wc-tpp-frontend.php:76
msgid "Price per Unit"
msgstr "Prix par unité"
#: includes/class-wc-tpp-frontend.php:77
msgid "You Save"
msgstr "Vous économisez"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Offres forfaitaires"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Choisissez une taille de forfait ci-dessous"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "pièces"
#: includes/class-wc-tpp-frontend.php:129
msgid "per unit"
msgstr "par unité"
#: includes/class-wc-tpp-frontend.php:133
msgid "Select Package"
msgstr "Sélectionner le forfait"
#: includes/class-wc-tpp-cart.php:63
msgid "Package price"
msgstr "Prix forfaitaire"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Remise sur quantité"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "ce produit"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "La quantité %1$d n'est pas disponible pour %2$s. Veuillez choisir parmi les tailles de forfait disponibles: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "Voir les options"
#: 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Prix par paliers et forfaits nécessite PHP 8.3 ou supérieur. Votre serveur utilise PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "Licence"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "Gestion des licences"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Entrez votre clé de licence pour recevoir des mises à jour et du support."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "URL du serveur de licence"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "L'URL du serveur de licence."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "Clé de licence"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "Votre clé de licence pour cette extension."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "Statut de la licence"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Valider la licence"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Activer la licence"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "Aucune licence activée"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "Licence active"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "Licence invalide"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Licence à vie"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Expire le: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Dernière vérification: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "La licence est valide!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "Licence activée avec succès!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Accès refusé."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Secret serveur"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "Le secret partagé pour la communication sécurisée avec le serveur de licence."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "La clé de licence, l'URL du serveur et le secret serveur sont requis."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Limite de requêtes dépassée. Veuillez réessayer dans %d secondes."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "La vérification de la signature a échoué. Veuillez vérifier votre secret serveur."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "Une erreur inattendue s'est produite. Veuillez réessayer."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Veuillez entrer l'URL du serveur de licence, la clé de licence et le secret serveur."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "La validation a échoué."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "L'activation a échoué."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "La requête a échoué. Veuillez réessayer."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Mises à jour automatiques"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Paramètres de mise à jour automatique"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Configurez les mises à jour automatiques du plugin depuis le serveur de licence."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Activer les notifications de mise à jour"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Rechercher les mises à jour disponibles du plugin."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "Lorsque cette option est activée, le plugin vérifiera les mises à jour depuis le serveur de licence et affichera des notifications dans l'administration WordPress."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Installer automatiquement les mises à jour"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Installer automatiquement les mises à jour lorsqu'elles sont disponibles."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Nécessite une licence valide)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "Lorsque cette option est activée, les mises à jour seront automatiquement installées lors des mises à jour en arrière-plan de WordPress."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Fréquence de vérification (heures)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "Fréquence de recherche des mises à jour."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Nombre d'heures entre les vérifications de mise à jour. Par défaut: 12 heures."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Statut de mise à jour"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Rechercher des mises à jour"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Version actuelle:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Mise à jour disponible:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "Vous utilisez la dernière version."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Mettre à jour maintenant"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Mise à jour disponible: version %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Échec de la recherche de mises à jour."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Environnement localhost - validation de licence ignorée)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Serveur auto-licencié - validation de licence ignorée)"

Binary file not shown.

View File

@@ -0,0 +1,495 @@
# Italian (Switzerland) translation for WooCommerce Tier and Package Prices
# Copyright (C) 2026 Marco Graetsch
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
"POT-Creation-Date: 2026-02-03 00:00+0000\n"
"PO-Revision-Date: 2026-02-03 00:00+0000\n"
"Last-Translator: Marco Graetsch\n"
"Language-Team: Italian (Switzerland)\n"
"Language: it_CH\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 Prezzi Scaglionati e Pacchetti richiede che WooCommerce sia installato e attivo."
#: includes/class-wc-tpp-admin.php:21
#: includes/class-wc-tpp-admin.php:22
#: includes/class-wc-tpp-settings.php:28
msgid "Tier & Package Prices"
msgstr "Prezzi Scaglionati & Pacchetti"
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr "Generale"
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr "Impostazioni Prezzi Scaglionati & Pacchetti"
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr "Configura le opzioni di prezzi scaglionati e pacchetti per i tuoi prodotti WooCommerce."
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr "Attiva prezzi scaglionati"
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr "Attiva prezzi scaglionati per i prodotti"
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Permette scaglioni di prezzo basati sulla quantità. I clienti ottengono prezzi scontati quando acquistano quantità maggiori."
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr "Attiva prezzi pacchetto"
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr "Attiva pacchetti a prezzo fisso per i prodotti"
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr "Permette pacchetti a prezzo fisso con quantità specifiche. Ad esempio: 10 pezzi per CHF 50.-, 25 pezzi per CHF 100.-."
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr "Visualizza tabella prezzi"
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr "Mostra la tabella dei prezzi scaglionati e pacchetti nelle pagine prodotto"
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr "Visualizza la tabella prezzi ai clienti nelle pagine prodotto."
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr "Posizione visualizzazione"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr "Scegli dove visualizzare la tabella prezzi nelle pagine prodotto."
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr "Prima del pulsante \"Aggiungi al carrello\""
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr "Dopo il pulsante \"Aggiungi al carrello\""
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
msgstr "Prima di \"Aggiungi al carrello\""
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
msgstr "Dopo \"Aggiungi al carrello\""
#: includes/class-wc-tpp-admin.php:87
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr "Dopo il prezzo"
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr "Limita alle quantità pacchetto"
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr "Limita le quantità solo alle dimensioni pacchetto definite"
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr "Quando attivato, i clienti possono acquistare prodotti solo nelle quantità esatte definite nei pacchetti. Il campo di inserimento quantità verrà nascosto e sostituito con pulsanti di selezione pacchetto."
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr "Prezzi scaglionati"
#: includes/class-wc-tpp-product-meta.php:24
msgid "Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr "Imposta scaglioni di prezzo basati sulla quantità. I clienti ottengono sconti quando acquistano quantità maggiori."
#: includes/class-wc-tpp-product-meta.php:41
msgid "Add Tier"
msgstr "Aggiungi scaglione"
#: includes/class-wc-tpp-product-meta.php:52
msgid "Package Pricing"
msgstr "Prezzi pacchetto"
#: 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 "Imposta pacchetti a prezzo fisso con quantità specifiche. Ad esempio: 10 pezzi per CHF 50.-, 25 pezzi per CHF 100.-."
#: includes/class-wc-tpp-product-meta.php:70
msgid "Add Package"
msgstr "Aggiungi pacchetto"
#: includes/class-wc-tpp-product-meta.php:77
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"
#: includes/class-wc-tpp-product-meta.php:91
msgid "e.g., 10"
msgstr "es. 10"
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
msgid "e.g., 9.99"
msgstr "es. 9.90"
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
msgid "Remove"
msgstr "Rimuovi"
#: includes/class-wc-tpp-product-meta.php:109
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr "Quantità"
#: includes/class-wc-tpp-product-meta.php:113
msgid "Fixed Price"
msgstr "Prezzo fisso"
#: includes/class-wc-tpp-product-meta.php:117
msgid "Label (Optional)"
msgstr "Etichetta (Opzionale)"
#: includes/class-wc-tpp-product-meta.php:118
msgid "e.g., Starter Pack"
msgstr "es. Pacchetto starter"
#: includes/class-wc-tpp-frontend.php:71
msgid "Volume Discounts"
msgstr "Sconti quantità"
#: includes/class-wc-tpp-product-meta.php:94
#: includes/class-wc-tpp-frontend.php:76
msgid "Price per Unit"
msgstr "Prezzo per unità"
#: includes/class-wc-tpp-frontend.php:77
msgid "You Save"
msgstr "Risparmi"
#: includes/class-wc-tpp-frontend.php:110
msgid "Package Deals"
msgstr "Offerte pacchetto"
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr "Scegli una dimensione pacchetto qui sotto"
#: includes/class-wc-tpp-frontend.php:123
msgid "pieces"
msgstr "pezzi"
#: includes/class-wc-tpp-frontend.php:129
msgid "per unit"
msgstr "per unità"
#: includes/class-wc-tpp-frontend.php:133
msgid "Select Package"
msgstr "Seleziona pacchetto"
#: includes/class-wc-tpp-cart.php:63
msgid "Package price"
msgstr "Prezzo pacchetto"
#: includes/class-wc-tpp-cart.php:66
msgid "Volume discount"
msgstr "Sconto quantità"
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr "questo prodotto"
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr "La quantità %1$d non è disponibile per %2$s. Si prega di scegliere tra le dimensioni pacchetto disponibili: %3$s"
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr "Visualizza opzioni"
#: 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)"
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr "WooCommerce Prezzi a scaglioni e pacchetti richiede PHP 8.3 o superiore. Il tuo server utilizza PHP %s."
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr "Licenza"
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr "Gestione licenza"
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr "Inserisci la tua chiave di licenza per ricevere aggiornamenti e supporto."
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr "URL server licenza"
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr "L'URL del server di licenza."
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr "Chiave di licenza"
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr "La tua chiave di licenza per questo plugin."
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr "Stato licenza"
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr "Verifica licenza"
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr "Attiva licenza"
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr "Nessuna licenza attivata"
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr "Licenza attiva"
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr "Licenza non valida"
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr "Licenza a vita"
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr "Scade il: %s"
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr "Ultima verifica: %s"
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr "La licenza è valida!"
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr "Licenza attivata con successo!"
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr "Accesso negato."
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr "Segreto del server"
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr "Il segreto condiviso per la comunicazione sicura con il server di licenza."
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr "La chiave di licenza, l'URL del server e il segreto del server sono obbligatori."
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr "Limite di richieste superato. Per favore riprova tra %d secondi."
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr "Verifica della firma della risposta fallita. Per favore controlla il tuo segreto del server."
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr "Si è verificato un errore imprevisto. Per favore riprova."
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr "Inserisci l'URL del server di licenza, la chiave di licenza e il segreto del server."
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr "Verifica fallita."
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr "Attivazione fallita."
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr "Richiesta fallita. Per favore riprova."
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr "Aggiornamenti automatici"
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr "Impostazioni aggiornamento automatico"
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr "Configura gli aggiornamenti automatici del plugin dal server di licenza."
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr "Abilita notifiche di aggiornamento"
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr "Verifica la disponibilità di aggiornamenti del plugin."
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr "Se abilitato, il plugin verificherà la presenza di aggiornamenti dal server di licenza e mostrerà le notifiche nell'amministrazione WordPress."
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr "Installa automaticamente gli aggiornamenti"
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr "Installa automaticamente gli aggiornamenti quando disponibili."
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr "(Richiede una licenza valida)"
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr "Se abilitato, gli aggiornamenti verranno installati automaticamente quando WordPress esegue gli aggiornamenti in background."
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr "Frequenza di controllo (ore)"
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr "Con quale frequenza controllare gli aggiornamenti."
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr "Numero di ore tra i controlli degli aggiornamenti. Predefinito: 12 ore."
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr "Stato aggiornamento"
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr "Verifica aggiornamenti"
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr "Versione attuale:"
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr "Aggiornamento disponibile:"
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr "Stai utilizzando l'ultima versione."
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr "Aggiorna ora"
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr "Aggiornamento disponibile: versione %s"
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr "Impossibile verificare la presenza di aggiornamenti."
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr "(Ambiente localhost - verifica della licenza saltata)"
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr "(Server auto-licenziato - verifica della licenza saltata)"

View File

@@ -1,10 +1,10 @@
# Copyright (C) 2025 Marco Graetsch
# Copyright (C) 2026 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"
"Project-Id-Version: WooCommerce Tier and Package Prices 1.4.1\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: 2026-02-03 00:00+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -14,55 +14,96 @@ msgstr ""
"X-Generator: Poedit 3.0\n"
"X-Domain: wc-tier-package-prices\n"
#: wc-tier-and-package-prices.php:41
#: wc-tier-and-package-prices.php:44
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
#: includes/class-wc-tpp-settings.php:28
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices"
msgstr ""
#: includes/class-wc-tpp-admin.php:54
#: includes/class-wc-tpp-settings.php:40
msgid "General"
msgstr ""
#: includes/class-wc-tpp-settings.php:58
msgid "Tier & Package Prices Settings"
msgstr ""
#: includes/class-wc-tpp-settings.php:60
msgid "Configure tier pricing and package pricing options for your WooCommerce products."
msgstr ""
#: includes/class-wc-tpp-settings.php:65
msgid "Enable Tier Pricing"
msgstr ""
#: includes/class-wc-tpp-admin.php:58
#: includes/class-wc-tpp-settings.php:66
msgid "Enable tier pricing for products"
msgstr ""
#: includes/class-wc-tpp-admin.php:63
#: includes/class-wc-tpp-settings.php:70
msgid "Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities."
msgstr ""
#: includes/class-wc-tpp-settings.php:74
msgid "Enable Package Pricing"
msgstr ""
#: includes/class-wc-tpp-admin.php:67
#: includes/class-wc-tpp-settings.php:75
msgid "Enable fixed-price packages for products"
msgstr ""
#: includes/class-wc-tpp-admin.php:72
#: includes/class-wc-tpp-settings.php:79
msgid "Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100."
msgstr ""
#: includes/class-wc-tpp-settings.php:83
msgid "Display Pricing Table"
msgstr ""
#: includes/class-wc-tpp-admin.php:76
#: includes/class-wc-tpp-settings.php:84
msgid "Show tier and package pricing table on product pages"
msgstr ""
#: includes/class-wc-tpp-admin.php:81
#: includes/class-wc-tpp-settings.php:88
msgid "Display the pricing table to customers on product pages."
msgstr ""
#: includes/class-wc-tpp-settings.php:92
msgid "Display Position"
msgstr ""
#: includes/class-wc-tpp-admin.php:85
msgid "Before Add to Cart"
#: includes/class-wc-tpp-settings.php:93
msgid "Choose where to display the pricing table on product pages."
msgstr ""
#: includes/class-wc-tpp-admin.php:86
msgid "After Add to Cart"
#: includes/class-wc-tpp-settings.php:101
msgid "Before Add to Cart Button"
msgstr ""
#: includes/class-wc-tpp-admin.php:87
#: includes/class-wc-tpp-settings.php:102
msgid "After Add to Cart Button"
msgstr ""
#: includes/class-wc-tpp-settings.php:103
msgid "After Price"
msgstr ""
#: includes/class-wc-tpp-settings.php:108
#: includes/class-wc-tpp-product-meta.php:76
msgid "Restrict to Package Quantities"
msgstr ""
#: includes/class-wc-tpp-settings.php:109
msgid "Limit quantities to defined package sizes only"
msgstr ""
#: includes/class-wc-tpp-settings.php:113
msgid "When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons."
msgstr ""
#: includes/class-wc-tpp-product-meta.php:23
msgid "Tier Pricing"
msgstr ""
@@ -87,80 +128,341 @@ msgstr ""
msgid "Add Package"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:90
#: includes/class-wc-tpp-product-meta.php:77
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 ""
#: includes/class-wc-tpp-product-meta.php:91
#: templates/admin/tier-row.twig:13
#: templates/admin/package-row.twig:13
msgid "e.g., 10"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:94
#: templates/admin/tier-row.twig:16
msgid "Price per Unit"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:95
#: includes/class-wc-tpp-product-meta.php:114
#: templates/admin/tier-row.twig:21
msgid "e.g., 9.99"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:97
#: includes/class-wc-tpp-product-meta.php:120
#: templates/admin/tier-row.twig:25
#: templates/admin/package-row.twig:30
msgid "Remove"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:109
#: templates/admin/package-row.twig:9
#: templates/frontend/tier-pricing-table.twig:13
msgid "Quantity"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:113
#: templates/admin/package-row.twig:20
msgid "Fixed Price"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:117
#: templates/admin/package-row.twig:18
msgid "e.g., 99.99"
msgstr ""
#: templates/admin/package-row.twig:24
msgid "Label (Optional)"
msgstr ""
#: includes/class-wc-tpp-product-meta.php:118
#: templates/admin/package-row.twig:29
msgid "e.g., Starter Pack"
msgstr ""
#: includes/class-wc-tpp-frontend.php:71
#: templates/frontend/tier-pricing-table.twig:9
msgid "Volume Discounts"
msgstr ""
#: includes/class-wc-tpp-frontend.php:75
msgid "Quantity"
msgstr ""
#: includes/class-wc-tpp-frontend.php:76
#: templates/frontend/tier-pricing-table.twig:14
msgid "Price per Unit"
msgstr ""
#: includes/class-wc-tpp-frontend.php:77
#: templates/frontend/tier-pricing-table.twig:15
msgid "You Save"
msgstr ""
#: includes/class-wc-tpp-frontend.php:110
#: templates/frontend/package-pricing-display.twig:8
msgid "Package Deals"
msgstr ""
#: includes/class-wc-tpp-frontend.php:123
#: templates/frontend/package-pricing-display.twig:11
msgid "Choose a package size below"
msgstr ""
#: templates/frontend/package-pricing-display.twig:20
msgid "pieces"
msgstr ""
#: includes/class-wc-tpp-frontend.php:129
#: templates/frontend/package-pricing-display.twig:24
msgid "per unit"
msgstr ""
#: includes/class-wc-tpp-frontend.php:133
#: templates/frontend/package-pricing-display.twig:28
msgid "Select Package"
msgstr ""
#: includes/class-wc-tpp-cart.php:63
#: includes/class-wc-tpp-cart.php:76
msgid "Package price"
msgstr ""
#: includes/class-wc-tpp-cart.php:66
#: includes/class-wc-tpp-cart.php:79
msgid "Volume discount"
msgstr ""
#: includes/class-wc-tpp-cart.php:124
msgid "this product"
msgstr ""
#: includes/class-wc-tpp-cart.php:128
msgid "The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s"
msgstr ""
#: includes/class-wc-tpp-frontend.php:173
msgid "View Options"
msgstr ""
#: includes/class-wc-tpp-frontend.php:178
msgid "View options for %s"
msgstr ""
#: wc-tier-and-package-prices.php
msgid "WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License Management"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Enter your license key to receive updates and support."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License Server URL"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "The URL of the license server."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License Key"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Your license key for this plugin."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License Status"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Validate License"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Activate License"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "No license activated"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License Active"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License Invalid"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Lifetime License"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Expires: %s"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Last checked: %s"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License is valid!"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License activated successfully!"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Permission denied."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Server Secret"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "The shared secret for secure communication with the license server."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "License key, server URL, and server secret are required."
msgstr ""
#: includes/class-wc-tpp-settings.php
#. translators: %d: Number of seconds to wait
msgid "Rate limit exceeded. Please try again in %d seconds."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Response signature verification failed. Please check your server secret."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "An unexpected error occurred. Please try again."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Please enter license server URL, license key, and server secret."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Validation failed."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Activation failed."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Request failed. Please try again."
msgstr ""
#. v1.4.1 - Auto-Updates and License Bypass
#: includes/class-wc-tpp-settings.php
msgid "Auto-Updates"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Auto-Update Settings"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Configure automatic plugin updates from the license server."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Enable Update Notifications"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Check for available plugin updates."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Automatically Install Updates"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Automatically install updates when available."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "(Requires a valid license)"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "When enabled, updates will be automatically installed when WordPress performs background updates."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Check Frequency (Hours)"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "How often to check for updates."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Number of hours between update checks. Default is 12 hours."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Update Status"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Check for Updates"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Current Version:"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Update Available:"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "You are running the latest version."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Update Now"
msgstr ""
#: includes/class-wc-tpp-settings.php
#. translators: %s: Version number
msgid "Update available: version %s"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "Failed to check for updates."
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "(Localhost environment - license validation bypassed)"
msgstr ""
#: includes/class-wc-tpp-settings.php
msgid "(Self-licensing server - license validation bypassed)"
msgstr ""

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="short">
</p>
<button type="button" class="button wc-tpp-remove-package">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</div>
class="regular">
</td>
<td>
<button type="button" class="button wc-tpp-remove-package">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</td>
</tr>

View File

@@ -1,53 +0,0 @@
{#
# 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

@@ -4,25 +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>
<button type="button" class="button wc-tpp-remove-tier">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</div>
</td>
<td>
<input type="text"
name="{{ name_prefix }}[{{ index|esc_attr }}][label]"
value="{{ tier.label|default('')|esc_attr }}"
placeholder="{{ 'e.g., Wholesale'|__('wc-tier-package-prices') }}"
class="regular">
</td>
<td>
<button type="button" class="button wc-tpp-remove-tier">{{ 'Remove'|__('wc-tier-package-prices') }}</button>
</td>
</tr>

View File

@@ -3,13 +3,17 @@
#
# @package WC_Tier_Package_Prices
# @var array packages
# @var bool restrict_to_packages
#}
<div class="wc-tpp-package-pricing-table">
<div class="wc-tpp-package-pricing-table{% if restrict_to_packages %} wc-tpp-restricted-mode{% endif %}">
<h3>{{ 'Package Deals'|__('wc-tier-package-prices') }}</h3>
{% if restrict_to_packages %}
<p class="wc-tpp-restriction-notice">{{ 'Choose a package size below'|__('wc-tier-package-prices') }}</p>
{% endif %}
<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{% if restrict_to_packages %} wc-tpp-package-selectable{% endif %}" 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>

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' %}
{% include 'frontend/package-pricing-display.twig' with {'packages': packages} %}
{% 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

@@ -25,7 +25,12 @@
{% 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>
{{ tier.min_qty|esc_html }}+
{% if tier.label is defined and tier.label is not empty %}
<br><small class="wc-tpp-tier-label">{{ tier.label|esc_html }}</small>
{% endif %}
</td>
<td>{{ wc_price(tier.price)|raw }}</td>
<td>
{% if savings > 0 %}

174
wc-tier-and-package-prices.php Normal file → Executable file
View File

@@ -2,15 +2,15 @@
/**
* Plugin Name: WooCommerce Tier and Package Prices
* Plugin URI: https://src.bundespruefstelle.ch/wc-tier-package-prices
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
* Version: 1.0.1
* Version: 1.4.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
* Requires PHP: 8.3
* WC requires at least: 8.0
* WC tested up to: 10.0
* License: GPL v2 or later
@@ -21,15 +21,59 @@ if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
// Check PHP version requirement
if (version_compare(PHP_VERSION, '8.3.0', '<')) {
add_action('admin_notices', 'wc_tpp_php_version_notice');
return;
}
/**
* Display PHP version notice
*/
if (!function_exists('wc_tpp_php_version_notice')) {
function wc_tpp_php_version_notice() {
?>
<div class="notice notice-error">
<p><?php printf(
/* translators: %s: Current PHP version */
__('WooCommerce Tier and Package Prices requires PHP 8.3 or higher. Your server is running PHP %s.', 'wc-tier-package-prices'),
PHP_VERSION
); ?></p>
</div>
<?php
}
}
// 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__));
if (!defined('WC_TPP_VERSION')) {
define('WC_TPP_VERSION', '1.4.1');
}
if (!defined('WC_TPP_PLUGIN_DIR')) {
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
}
if (!defined('WC_TPP_PLUGIN_URL')) {
define('WC_TPP_PLUGIN_URL', plugin_dir_url(__FILE__));
}
if (!defined('WC_TPP_PLUGIN_BASENAME')) {
define('WC_TPP_PLUGIN_BASENAME', plugin_basename(__FILE__));
}
// Load Composer autoloader
require_once WC_TPP_PLUGIN_DIR . 'vendor/autoload.php';
/**
* Display WooCommerce missing notice
*/
if (!function_exists('wc_tpp_woocommerce_missing_notice')) {
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
}
}
/**
* Check if WooCommerce is active
*/
@@ -38,74 +82,84 @@ if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get
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 {
if (!class_exists('WC_Tier_Package_Prices')) {
class WC_Tier_Package_Prices {
private static $instance = null;
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
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);
private function __construct() {
$this->init_hooks();
$this->includes();
}
}
public function load_textdomain() {
load_plugin_textdomain('wc-tier-package-prices', false, dirname(WC_TPP_PLUGIN_BASENAME) . '/languages');
}
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'));
}
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();
}
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-license-checker.php';
require_once WC_TPP_PLUGIN_DIR . 'includes/class-wc-tpp-update-checker.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 deactivate() {
flush_rewrite_rules();
// Initialize license checker (singleton)
WC_TPP_License_Checker::get_instance();
// Initialize update checker (singleton)
WC_TPP_Update_Checker::get_instance();
}
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');
// Add default auto-update options
add_option('wc_tpp_update_notification_enabled', 'yes');
add_option('wc_tpp_auto_install_enabled', 'no');
add_option('wc_tpp_update_check_frequency', '12');
flush_rewrite_rules();
}
public function deactivate() {
flush_rewrite_rules();
}
}
}
// Initialize the plugin
function wc_tpp_init() {
return WC_Tier_Package_Prices::get_instance();
if (!function_exists('wc_tpp_init')) {
function wc_tpp_init() {
return WC_Tier_Package_Prices::get_instance();
}
}
add_action('plugins_loaded', 'wc_tpp_init', 11);