You've already forked wc-licensed-product
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 142500cab0 | |||
| 20fb39d1a1 | |||
| 953aa6c8e8 | |||
| db4966caf2 | |||
| 9c4232f14f | |||
| 0638767ce3 | |||
| 9826c8181e | |||
| fa972ceaf0 | |||
| 3abf05cff3 | |||
| 169eed65eb | |||
| 90cb8d97bd | |||
| fc281f7f4a | |||
| 962368d35f | |||
| 4dcace6f06 | |||
| 62aecc0240 | |||
| 1f676556f2 | |||
| 5f51aafe3b | |||
| 279b0d5dd6 |
84
CHANGELOG.md
84
CHANGELOG.md
@@ -7,6 +7,90 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.5.12] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL:** Fixed stock indicator ("1 in stock") appearing in cart for licensed variable product variations
|
||||
- Override `get_children()` with direct SQL query to bypass WooCommerce's `is_type('variable')` check
|
||||
- Override `get_variation_attributes()` to properly load taxonomy attribute terms
|
||||
- Override `get_variation_prices()` to prevent fatal error with null `$this->prices_array`
|
||||
- Override `get_available_variations()` with empty `availability_html` for variations
|
||||
- Added `is_type()` override to return true for both 'licensed-variable' and 'variable' type checks
|
||||
- Added multiple stock-related filters: `woocommerce_get_availability_text`, `woocommerce_product_get_stock_quantity`, `woocommerce_product_variation_get_stock_quantity`
|
||||
- Improved `isLicensedProductOrVariation()` check using `WC_Product_Factory::get_product_type()` for reliable parent type detection
|
||||
|
||||
### Changed
|
||||
|
||||
- `LicensedProductVariation` now includes `get_availability()`, `managing_stock()`, and `is_purchasable()` overrides
|
||||
- Simplified `isVirtual()` to use shared `isLicensedProductOrVariation()` helper
|
||||
|
||||
## [0.5.11] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL:** Fixed "sold out" message on licensed variable products by correcting `is_purchasable()` method
|
||||
- Variable products don't have a direct price - `is_purchasable()` now delegates to parent `WC_Product_Variable` class
|
||||
- Fixed variation class detection by using product ID parameter instead of unreliable global `$post`
|
||||
- Product class filter now properly accepts all 4 WooCommerce filter parameters for reliable variation detection
|
||||
|
||||
## [0.5.10] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed licensed variable products not showing variations even when attributes are defined
|
||||
- Re-load product via `wc_get_product()` to ensure correct class instance is used
|
||||
- Removed overly strict type check that was preventing variations from displaying
|
||||
- Now mirrors WooCommerce's standard `woocommerce_variable_add_to_cart()` implementation
|
||||
|
||||
## [0.5.9] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed frontend error on licensed variable products when no attributes are defined
|
||||
- Added null checks for `get_variation_attributes()`, `get_available_variations()`, and `get_default_attributes()`
|
||||
- Show informative message instead of error when product has no variations configured
|
||||
- Changed product type check from `instanceof` to `is_type()` for better compatibility
|
||||
|
||||
## [0.5.8] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CRITICAL:** Fixed critical error on frontend product pages for licensed variable products
|
||||
- Variable product add-to-cart template now passes required variables (`available_variations`, `attributes`, `selected_attributes`)
|
||||
- Variants tab no longer disappears when saving attributes on licensed variable products
|
||||
- Added WooCommerce AJAX event listeners to maintain tab visibility during attribute operations
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved JavaScript event handling for licensed-variable product type in admin
|
||||
- Added listeners for `woocommerce_variations_loaded`, `woocommerce_variations_added`, `woocommerce_variations_saved` events
|
||||
- Added AJAX complete handler for attribute save operations
|
||||
|
||||
## [0.5.7] - 2026-01-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed "Default" prefix from setting labels on Default Settings page for cleaner UI
|
||||
- Labels now read "Max Activations", "License Validity (Days)", and "Bind to Major Version"
|
||||
|
||||
## [0.5.6] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- License Settings tab now only shows for Licensed Product and Licensed Variable Product types
|
||||
- Previously the tab was visible on all product types due to CSS `!important` override
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved JavaScript for License Settings tab visibility handling on product type change
|
||||
- Updated README.md with complete feature documentation for v0.5.x features:
|
||||
- Variable Licensed Products
|
||||
- Multi-Domain Licensing
|
||||
- Per-License Customer Secrets
|
||||
- Download Statistics
|
||||
- Configurable Rate Limiting
|
||||
|
||||
## [0.5.5] - 2026-01-26
|
||||
|
||||
### Fixed
|
||||
|
||||
106
CLAUDE.md
106
CLAUDE.md
@@ -1437,3 +1437,109 @@ Critical bug fix for response signing. The key derivation algorithm was incompat
|
||||
- Length: 32 bytes (256 bits)
|
||||
- Info: license_key (context-specific info)
|
||||
- **Breaking change for existing signatures** - customer secrets will change after upgrade
|
||||
|
||||
### 2026-01-27 - Version 0.5.6 - License Settings Tab Visibility Fix
|
||||
|
||||
**Overview:**
|
||||
|
||||
Fixed License Settings tab visibility for non-licensed product types and updated README with v0.5.x features.
|
||||
|
||||
**Bug Fix:**
|
||||
|
||||
- License Settings tab now only shows for Licensed Product and Licensed Variable Product types
|
||||
- Previously the tab was visible on all product types due to CSS `!important` override forcing `display: block`
|
||||
|
||||
**Modified files:**
|
||||
|
||||
- `assets/css/admin.css` - Changed from `display: block !important` to `display: none` for `.show_if_licensed` and `.show_if_licensed-variable`
|
||||
- `src/Product/LicensedProductType.php` - Added consolidated `toggleLicensedProductOptions()` JavaScript function
|
||||
- `README.md` - Updated with complete feature documentation for v0.5.x features
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
- CSS now hides License Settings tab by default
|
||||
- JavaScript `toggleLicensedProductOptions()` function shows/hides tab based on product type selector
|
||||
- Function is called both on page load and on product type change
|
||||
- README updated with: Variable Licensed Products, Multi-Domain Licensing, Per-License Secrets, Download Statistics, Configurable Rate Limiting
|
||||
|
||||
**Release v0.5.6:**
|
||||
|
||||
- Created release package: `releases/wc-licensed-product-0.5.6.zip` (1.1 MB)
|
||||
- SHA256: `4d35a319fe4cb4e7055bae17fc030487ca05e5e9ac905f76d0ac62002bde4336`
|
||||
- Tagged as `v0.5.6` and pushed to `main` branch
|
||||
|
||||
### 2026-01-27 - Version 0.5.7 - Settings UI Cleanup
|
||||
|
||||
**Overview:**
|
||||
|
||||
Removed redundant "Default" prefix from setting labels on the Default Settings page for cleaner UI.
|
||||
|
||||
**Changed:**
|
||||
|
||||
- "Max Activations" (was "Default Max Activations")
|
||||
- "License Validity (Days)" (was "Default License Validity (Days)")
|
||||
- "Bind to Major Version" (was "Default Bind to Major Version")
|
||||
|
||||
**Modified files:**
|
||||
|
||||
- `src/Admin/SettingsController.php` - Removed "Default" prefix from three setting labels
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
- Labels are cleaner since the page section itself is already named "Default Settings"
|
||||
- No functional changes, purely UI improvement
|
||||
- Updated all translations (388 strings)
|
||||
|
||||
**Release v0.5.7:**
|
||||
|
||||
- Created release package: `releases/wc-licensed-product-0.5.7.zip` (856 KB)
|
||||
- SHA256: `ceb4d57598f576f4f172153ff80df8c180ecd4dca873cf109327fc5ac718930f`
|
||||
- Tagged as `v0.5.7` and pushed to `main` branch
|
||||
|
||||
### 2026-01-27 - Version 0.5.8-0.5.11 - Licensed Variable Product Fixes
|
||||
|
||||
**Overview:**
|
||||
|
||||
Series of bug fixes for licensed variable products that were showing frontend errors and not displaying properly.
|
||||
|
||||
**v0.5.8 - Initial Fix:**
|
||||
|
||||
- Fixed critical error on frontend product pages for licensed variable products
|
||||
- Variable product add-to-cart template now passes required variables (`available_variations`, `attributes`, `selected_attributes`)
|
||||
- Added JavaScript event listeners for WooCommerce AJAX events to maintain admin variants tab visibility
|
||||
|
||||
**v0.5.9 - Null Checks:**
|
||||
|
||||
- Added null checks for `get_variation_attributes()`, `get_available_variations()`, and `get_default_attributes()`
|
||||
- Show informative message instead of error when product has no variations configured
|
||||
- Changed product type check from `instanceof` to `is_type()` for better compatibility
|
||||
|
||||
**v0.5.10 - Product Loading:**
|
||||
|
||||
- Re-load product via `wc_get_product()` to ensure correct class instance is used
|
||||
- Removed overly strict type check that was preventing variations from displaying
|
||||
|
||||
**v0.5.11 - Final Fix:**
|
||||
|
||||
- **CRITICAL:** Fixed "sold out" message on licensed variable products
|
||||
- `LicensedVariableProduct::is_purchasable()` now delegates to parent `WC_Product_Variable` class (variable products don't have direct prices - only variations do)
|
||||
- Fixed `getProductClass()` filter to accept all 4 WooCommerce parameters and use product_id for reliable variation parent detection
|
||||
- Added fallback to global `$post` when product_id not available
|
||||
|
||||
**Modified files:**
|
||||
|
||||
- `src/Product/LicensedProductType.php` - Fixed `variableAddToCartTemplate()` and `getProductClass()` methods
|
||||
- `src/Product/LicensedVariableProduct.php` - Fixed `is_purchasable()` method
|
||||
- `wc-licensed-product.php` - Version bumps
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
- WooCommerce `woocommerce_product_class` filter has 4 parameters: `$className`, `$productType`, `$postType`, `$productId`
|
||||
- Variable products delegate purchasability to their variations - checking `get_price()` on parent is incorrect
|
||||
- Variation parent detection must use product ID, not global `$post` which may not be set on frontend
|
||||
|
||||
**Release v0.5.11:**
|
||||
|
||||
- Created release package: `releases/wc-licensed-product-0.5.11.zip` (857 KB)
|
||||
- SHA256: `32571178bfa8f0d0a03ed05b498d5f9b3c860104393a96732e86a03b6de298d2`
|
||||
- Committed to `dev` branch
|
||||
|
||||
46
README.md
46
README.md
@@ -11,29 +11,34 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
|
||||
### Core Features
|
||||
|
||||
- **Licensed Product Type**: New WooCommerce product type for software sales
|
||||
- **Variable Licensed Products**: Create product variations with different license durations (monthly, yearly, lifetime)
|
||||
- **Automatic License Generation**: License keys generated on order completion (format: XXXX-XXXX-XXXX-XXXX)
|
||||
- **Domain Binding**: Licenses are bound to customer-specified domains
|
||||
- **Multi-Domain Licensing**: Customers can purchase multiple licenses for different domains in a single order
|
||||
- **REST API**: Public endpoints for license validation and management
|
||||
- **Response Signing**: Optional HMAC-SHA256 cryptographic signatures for API responses
|
||||
- **Per-License Secrets**: Each customer receives a unique verification secret for their license
|
||||
- **Version Binding**: Optional binding to major software versions
|
||||
- **Expiration Support**: Set license validity periods or lifetime licenses
|
||||
- **Rate Limiting**: API endpoints protected with rate limiting (30 requests/minute)
|
||||
- **Rate Limiting**: API endpoints protected with configurable rate limiting (default: 30 requests/minute)
|
||||
- **Trusted Proxy Support**: Configurable trusted proxies for accurate rate limiting behind CDNs
|
||||
- **Checkout Blocks**: Full support for WooCommerce Checkout Blocks (default since WC 8.3+)
|
||||
- **Self-Licensing**: The plugin can validate its own license (for commercial distribution)
|
||||
|
||||
### Customer Features
|
||||
|
||||
- **My Account Licenses**: Customers can view their licenses in My Account
|
||||
- **My Account Licenses**: Customers can view their licenses in My Account (grouped by product)
|
||||
- **License Transfers**: Customers can transfer licenses to new domains
|
||||
- **Secure Downloads**: Download purchased software versions with license verification
|
||||
- **Version History**: Access to older versions with collapsible download section
|
||||
- **Copy to Clipboard**: Easy license key copying
|
||||
- **API Verification Secret**: Per-license secret displayed for secure API integration
|
||||
|
||||
### Admin Features
|
||||
|
||||
- **License Management**: Full CRUD interface for license management
|
||||
- **License Dashboard**: Statistics and analytics (WooCommerce > Reports > Licenses)
|
||||
- **Dashboard Widget**: License statistics on WordPress admin dashboard
|
||||
- **Dashboard Widgets**: License statistics and download statistics on WordPress admin dashboard
|
||||
- **Search & Filtering**: Search by license key, domain, status, or product
|
||||
- **Live Search**: AJAX-powered instant search results
|
||||
- **Inline Editing**: Edit license status, expiry, and domain directly in the list
|
||||
@@ -41,10 +46,12 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
|
||||
- **License Transfer**: Transfer licenses to new domains
|
||||
- **CSV Export/Import**: Export and import licenses via CSV
|
||||
- **Order Integration**: View and manage licenses directly from order pages
|
||||
- **Generate Licenses**: Manually generate licenses for admin-created orders
|
||||
- **Expiration Warnings**: Automatic email notifications before license expiration
|
||||
- **Auto-Expire**: Daily cron job automatically expires licenses past their expiration date
|
||||
- **License Testing**: Test licenses against the API directly from admin interface
|
||||
- **Version Management**: Manage multiple versions per product with file attachments
|
||||
- **Download Tracking**: Track download counts per version with statistics widget
|
||||
- **SHA256 Checksums**: File integrity verification with SHA256 hash display
|
||||
- **Global Settings**: Default license settings via WooCommerce settings tab
|
||||
- **WooCommerce HPOS**: Compatible with High-Performance Order Storage
|
||||
@@ -66,13 +73,27 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
|
||||
### Creating a Licensed Product
|
||||
|
||||
1. Go to Products > Add New
|
||||
2. Select "Licensed Product" from the product type dropdown
|
||||
2. Select "Licensed Product" from the product type dropdown (or "Licensed Variable Product" for different license durations)
|
||||
3. Configure the product price in the General tab
|
||||
4. Set license options in the "License Settings" tab:
|
||||
- **Max Activations**: Number of domains allowed per license
|
||||
- **License Validity**: Days until expiration (empty = lifetime)
|
||||
- **Bind to Major Version**: Lock license to current major version
|
||||
|
||||
### Creating Variable Licensed Products
|
||||
|
||||
For selling licenses with different durations (monthly, yearly, lifetime):
|
||||
|
||||
1. Go to Products > Add New
|
||||
2. Select "Licensed Variable Product" from the product type dropdown
|
||||
3. Create variations as you would for any variable product (e.g., by "License Duration")
|
||||
4. For each variation, set:
|
||||
- **Variation Price**: Different prices for different durations
|
||||
- **License Duration (Days)**: Days until expiration (0 = lifetime)
|
||||
- **Max Activations**: Override parent product setting if needed
|
||||
|
||||
Duration labels (Monthly, Yearly, Lifetime) are automatically displayed at checkout.
|
||||
|
||||
### Managing Product Versions
|
||||
|
||||
1. Edit a Licensed Product
|
||||
@@ -84,7 +105,8 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
|
||||
|
||||
1. Go to WooCommerce > Settings > Licensed Products
|
||||
2. Set default values for Max Activations, License Validity, and Version Binding
|
||||
3. Per-product settings override these defaults
|
||||
3. Enable Multi-Domain Licensing to allow multiple licenses per cart item
|
||||
4. Per-product settings override these defaults
|
||||
|
||||
### Customer Checkout
|
||||
|
||||
@@ -144,6 +166,18 @@ define('WC_LICENSE_TRUSTED_PROXIES', 'CLOUDFLARE,10.0.0.1');
|
||||
|
||||
**Note**: Only configure trusted proxies if you actually use them. Without this configuration, rate limiting is more secure against IP spoofing attacks.
|
||||
|
||||
### Configurable Rate Limiting
|
||||
|
||||
The default rate limit is 30 requests per 60 seconds. You can customize this:
|
||||
|
||||
```php
|
||||
// Requests allowed per window (default: 30)
|
||||
define('WC_LICENSE_RATE_LIMIT', 60);
|
||||
|
||||
// Window duration in seconds (default: 60)
|
||||
define('WC_LICENSE_RATE_WINDOW', 120);
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
|
||||
@@ -173,6 +207,8 @@ openssl rand -hex 32
|
||||
|
||||
The signature prevents man-in-the-middle attacks and ensures response integrity. Use the `magdev/wc-licensed-product-client` Composer package with the `SecureLicenseClient` class to automatically verify signatures.
|
||||
|
||||
**Per-License Customer Secrets**: Each customer receives a unique verification secret derived from their license key. This secret is displayed in their account page under "API Verification Secret" and can be used with the client library instead of sharing the master server secret.
|
||||
|
||||
### Client Libraries & Examples
|
||||
|
||||
**PHP (Recommended):** Install the official client library via Composer:
|
||||
|
||||
@@ -50,9 +50,10 @@ code.file-hash {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* License Product Tab */
|
||||
#woocommerce-product-data .show_if_licensed {
|
||||
display: block !important;
|
||||
/* License Product Tab - Hidden by default, shown via JS based on product type */
|
||||
#woocommerce-product-data .show_if_licensed,
|
||||
#woocommerce-product-data .show_if_licensed-variable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#woocommerce-product-data .hide_if_licensed {
|
||||
|
||||
Binary file not shown.
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WC Licensed Product 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 17:06+0100\n"
|
||||
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
|
||||
"POT-Creation-Date: 2026-01-27 14:41+0100\n"
|
||||
"PO-Revision-Date: 2026-01-25T18:30:00+00:00\n"
|
||||
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
|
||||
"Language-Team: German (Switzerland) <de_CH@li.org>\n"
|
||||
@@ -310,10 +310,11 @@ msgstr "Speichern"
|
||||
#: src/Admin/AdminController.php:1373 src/Admin/AdminController.php:1613
|
||||
#: src/Admin/DashboardWidgetController.php:136
|
||||
#: src/Admin/OrderLicenseController.php:260
|
||||
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:136
|
||||
#: src/Product/LicensedProductType.php:184
|
||||
#: src/Product/LicensedProductType.php:379
|
||||
#: src/Product/LicensedProductVariation.php:139
|
||||
#: src/Admin/SettingsController.php:192
|
||||
#: src/Product/LicensedProductVariation.php:194
|
||||
#: src/Product/LicensedProductType.php:164
|
||||
#: src/Product/LicensedProductType.php:212
|
||||
#: src/Product/LicensedProductType.php:553
|
||||
#: src/Frontend/AccountController.php:286
|
||||
msgid "Lifetime"
|
||||
msgstr "Lebenslang"
|
||||
@@ -491,6 +492,7 @@ msgstr "Lizenz erfolgreich verlängert."
|
||||
msgid "License set to lifetime successfully."
|
||||
msgstr "Lizenz erfolgreich auf lebenslang gesetzt."
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1106
|
||||
#, php-format
|
||||
msgid "%d license activated."
|
||||
@@ -498,6 +500,7 @@ msgid_plural "%d licenses activated."
|
||||
msgstr[0] "%d Lizenz aktiviert."
|
||||
msgstr[1] "%d Lizenzen aktiviert."
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1114
|
||||
#, php-format
|
||||
msgid "%d license deactivated."
|
||||
@@ -505,6 +508,7 @@ msgid_plural "%d licenses deactivated."
|
||||
msgstr[0] "%d Lizenz deaktiviert."
|
||||
msgstr[1] "%d Lizenzen deaktiviert."
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1122
|
||||
#, php-format
|
||||
msgid "%d license revoked."
|
||||
@@ -512,6 +516,7 @@ msgid_plural "%d licenses revoked."
|
||||
msgstr[0] "%d Lizenz widerrufen."
|
||||
msgstr[1] "%d Lizenzen widerrufen."
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1130
|
||||
#, php-format
|
||||
msgid "%d license deleted."
|
||||
@@ -519,6 +524,7 @@ msgid_plural "%d licenses deleted."
|
||||
msgstr[0] "%d Lizenz gelöscht."
|
||||
msgstr[1] "%d Lizenzen gelöscht."
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1138
|
||||
#, php-format
|
||||
msgid "%d license extended."
|
||||
@@ -540,6 +546,7 @@ msgstr ""
|
||||
msgid "No licenses to export."
|
||||
msgstr "Keine Lizenzen zum Exportieren."
|
||||
|
||||
#. translators: %d: number of licenses imported
|
||||
#: src/Admin/AdminController.php:1159
|
||||
#, php-format
|
||||
msgid "%d license imported."
|
||||
@@ -547,6 +554,7 @@ msgid_plural "%d licenses imported."
|
||||
msgstr[0] "%d Lizenz importiert."
|
||||
msgstr[1] "%d Lizenzen importiert."
|
||||
|
||||
#. translators: %d: number of licenses updated
|
||||
#: src/Admin/AdminController.php:1166
|
||||
#, php-format
|
||||
msgid "%d updated."
|
||||
@@ -554,6 +562,7 @@ msgid_plural "%d updated."
|
||||
msgstr[0] "%d aktualisiert."
|
||||
msgstr[1] "%d aktualisiert."
|
||||
|
||||
#. translators: %d: number of licenses skipped
|
||||
#: src/Admin/AdminController.php:1174
|
||||
#, php-format
|
||||
msgid "%d skipped."
|
||||
@@ -561,6 +570,7 @@ msgid_plural "%d skipped."
|
||||
msgstr[0] "%d übersprungen."
|
||||
msgstr[1] "%d übersprungen."
|
||||
|
||||
#. translators: %d: number of errors
|
||||
#: src/Admin/AdminController.php:1182
|
||||
#, php-format
|
||||
msgid "%d error."
|
||||
@@ -1019,6 +1029,7 @@ msgstr "Domain bearbeiten"
|
||||
msgid "View in Licenses"
|
||||
msgstr "In Lizenzen anzeigen"
|
||||
|
||||
#. translators: %s: Link to licenses page
|
||||
#: src/Admin/OrderLicenseController.php:280
|
||||
#, php-format
|
||||
msgid "For more actions (revoke, extend, delete), go to the %s page."
|
||||
@@ -1165,17 +1176,18 @@ msgstr ""
|
||||
"Diese Einstellungen dienen als Standard für neue lizensierte Produkte. "
|
||||
"Individuelle Produkteinstellungen überschreiben diese Standards."
|
||||
|
||||
#: src/Admin/SettingsController.php:176
|
||||
msgid "Default Max Activations"
|
||||
msgstr "Standard Max. Aktivierungen"
|
||||
#: src/Admin/SettingsController.php:176 src/Product/LicensedProductType.php:182
|
||||
#: src/Product/LicensedProductType.php:570
|
||||
msgid "Max Activations"
|
||||
msgstr "Max. Aktivierungen"
|
||||
|
||||
#: src/Admin/SettingsController.php:178
|
||||
msgid "Default maximum number of domain activations per license."
|
||||
msgstr "Standard maximale Anzahl der Domain-Aktivierungen pro Lizenz."
|
||||
|
||||
#: src/Admin/SettingsController.php:187
|
||||
msgid "Default License Validity (Days)"
|
||||
msgstr "Standard Lizenz-Gültigkeit (Tage)"
|
||||
#: src/Admin/SettingsController.php:187 src/Product/LicensedProductType.php:200
|
||||
msgid "License Validity (Days)"
|
||||
msgstr "Lizenz-Gültigkeit (Tage)"
|
||||
|
||||
#: src/Admin/SettingsController.php:189
|
||||
msgid ""
|
||||
@@ -1185,9 +1197,9 @@ msgstr ""
|
||||
"Standard Anzahl Tage, die eine Lizenz gültig ist. Leer lassen oder auf 0 "
|
||||
"setzen für lebenslange Lizenzen."
|
||||
|
||||
#: src/Admin/SettingsController.php:199
|
||||
msgid "Default Bind to Major Version"
|
||||
msgstr "Standard An Hauptversion binden"
|
||||
#: src/Admin/SettingsController.php:199 src/Product/LicensedProductType.php:218
|
||||
msgid "Bind to Major Version"
|
||||
msgstr "An Hauptversion binden"
|
||||
|
||||
#: src/Admin/SettingsController.php:201
|
||||
msgid ""
|
||||
@@ -1213,6 +1225,7 @@ msgstr ""
|
||||
msgid "Expiration Warning Schedule"
|
||||
msgstr "Ablaufwarnung Zeitplan"
|
||||
|
||||
#. translators: %s: URL to WooCommerce email settings
|
||||
#: src/Admin/SettingsController.php:230
|
||||
#, php-format
|
||||
msgid ""
|
||||
@@ -1344,6 +1357,7 @@ msgstr "Lizenz-Domains"
|
||||
msgid "Each license requires a unique domain."
|
||||
msgstr "Jede Lizenz erfordert eine eindeutige Domain."
|
||||
|
||||
#. translators: %d: license number
|
||||
#: src/Checkout/CheckoutBlocksIntegration.php:129
|
||||
#: src/Checkout/CheckoutController.php:224
|
||||
#, php-format
|
||||
@@ -1355,6 +1369,16 @@ msgstr "Lizenz %d:"
|
||||
msgid "required"
|
||||
msgstr "erforderlich"
|
||||
|
||||
#: src/Checkout/CheckoutController.php:215
|
||||
#, php-format
|
||||
msgid "licensed_domains[%s][%d]"
|
||||
msgstr "licensed_domains[%s][%d]"
|
||||
|
||||
#: src/Checkout/CheckoutController.php:216
|
||||
#, php-format
|
||||
msgid "licensed_domain_%s_%d"
|
||||
msgstr "licensed_domain_%s_%d"
|
||||
|
||||
#: src/Checkout/CheckoutController.php:323
|
||||
msgid "Please enter a domain for your license."
|
||||
msgstr "Bitte geben Sie eine Domain für Ihre Lizenz ein."
|
||||
@@ -1363,16 +1387,19 @@ msgstr "Bitte geben Sie eine Domain für Ihre Lizenz ein."
|
||||
msgid "Please enter a valid domain for your license."
|
||||
msgstr "Bitte geben Sie eine gültige Domain für Ihre Lizenz ein."
|
||||
|
||||
#. translators: 1: product name, 2: license number
|
||||
#: src/Checkout/CheckoutController.php:356
|
||||
#, php-format
|
||||
msgid "Please enter a domain for %1$s (License %2$d)."
|
||||
msgstr "Bitte geben Sie eine Domain für %1$s (Lizenz %2$d) ein."
|
||||
|
||||
#. translators: 1: product name, 2: license number
|
||||
#: src/Checkout/CheckoutController.php:371
|
||||
#, php-format
|
||||
msgid "Please enter a valid domain for %1$s (License %2$d)."
|
||||
msgstr "Bitte geben Sie eine gültige Domain für %1$s (Lizenz %2$d) ein."
|
||||
|
||||
#. translators: 1: domain name, 2: product name
|
||||
#: src/Checkout/CheckoutController.php:385
|
||||
#, php-format
|
||||
msgid ""
|
||||
@@ -1436,62 +1463,74 @@ msgstr "Diese Lizenz ist für diese Domain nicht gültig."
|
||||
msgid "Attachment file not found."
|
||||
msgstr "Anhangs-Datei nicht gefunden."
|
||||
|
||||
#. translators: 1: provided hash, 2: calculated hash
|
||||
#: src/Product/VersionManager.php:177
|
||||
#, php-format
|
||||
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
|
||||
msgstr "Datei-Prüfsumme stimmt nicht überein. Erwartet: %1$s, Erhalten: %2$s"
|
||||
|
||||
#: src/Product/LicensedProductType.php:72
|
||||
#: src/Product/LicensedProductVariation.php:198
|
||||
msgid "Monthly"
|
||||
msgstr "Monatlich"
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:202
|
||||
msgid "Quarterly"
|
||||
msgstr "Vierteljährlich"
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:206
|
||||
msgid "Yearly"
|
||||
msgstr "Jährlich"
|
||||
|
||||
#. translators: %d: number of days
|
||||
#: src/Product/LicensedProductVariation.php:211
|
||||
#, php-format
|
||||
msgid "%d day"
|
||||
msgid_plural "%d days"
|
||||
msgstr[0] "%d Tag"
|
||||
msgstr[1] "%d Tage"
|
||||
|
||||
#: src/Product/LicensedProductType.php:82
|
||||
msgid "Licensed Product"
|
||||
msgstr "Lizensiertes Produkt"
|
||||
|
||||
#: src/Product/LicensedProductType.php:73
|
||||
#: src/Product/LicensedProductType.php:83
|
||||
msgid "Licensed Variable Product"
|
||||
msgstr "Lizensiertes variables Produkt"
|
||||
|
||||
#: src/Product/LicensedProductType.php:108
|
||||
#: src/Product/LicensedProductType.php:136
|
||||
msgid "License Settings"
|
||||
msgstr "Lizenz-Einstellungen"
|
||||
|
||||
#: src/Product/LicensedProductType.php:135
|
||||
#: src/Product/LicensedProductType.php:378
|
||||
#: src/Product/LicensedProductType.php:163
|
||||
#: src/Product/LicensedProductType.php:552
|
||||
#, php-format
|
||||
msgid "%d days"
|
||||
msgstr "%d Tage"
|
||||
|
||||
#: src/Product/LicensedProductType.php:145
|
||||
#. translators: %s: URL to settings page
|
||||
#: src/Product/LicensedProductType.php:173
|
||||
#, php-format
|
||||
msgid "Leave fields empty to use default settings from %s."
|
||||
msgstr "Felder leer lassen, um Standardeinstellungen von %s zu verwenden."
|
||||
|
||||
#: src/Product/LicensedProductType.php:147
|
||||
#: src/Product/LicensedProductType.php:175
|
||||
msgid "WooCommerce > Settings > Licensed Products"
|
||||
msgstr "WooCommerce > Einstellungen > Lizensierte Produkte"
|
||||
|
||||
#: src/Product/LicensedProductType.php:154
|
||||
#: src/Product/LicensedProductType.php:396
|
||||
msgid "Max Activations"
|
||||
msgstr "Max. Aktivierungen"
|
||||
|
||||
#: src/Product/LicensedProductType.php:157
|
||||
#. translators: %d: default max activations value
|
||||
#: src/Product/LicensedProductType.php:185
|
||||
#, php-format
|
||||
msgid "Maximum number of domain activations per license. Default: %d"
|
||||
msgstr "Maximale Anzahl der Domain-Aktivierungen pro Lizenz. Standard: %d"
|
||||
|
||||
#: src/Product/LicensedProductType.php:172
|
||||
msgid "License Validity (Days)"
|
||||
msgstr "Lizenz-Gültigkeit (Tage)"
|
||||
|
||||
#: src/Product/LicensedProductType.php:175
|
||||
#. translators: %s: default validity value
|
||||
#: src/Product/LicensedProductType.php:203
|
||||
#, php-format
|
||||
msgid "Number of days the license is valid. Leave empty for default (%s)."
|
||||
msgstr "Anzahl Tage, die die Lizenz gültig ist. Leer lassen für Standard (%s)."
|
||||
|
||||
#: src/Product/LicensedProductType.php:190
|
||||
msgid "Bind to Major Version"
|
||||
msgstr "An Hauptversion binden"
|
||||
|
||||
#: src/Product/LicensedProductType.php:193
|
||||
#. translators: %s: default bind to version value (Yes/No)
|
||||
#: src/Product/LicensedProductType.php:221
|
||||
#, php-format
|
||||
msgid ""
|
||||
"If enabled, licenses are bound to the major version at purchase time. "
|
||||
@@ -1500,57 +1539,38 @@ msgstr ""
|
||||
"Falls aktiviert, werden Lizenzen an die Hauptversion zum Kaufzeitpunkt "
|
||||
"gebunden. Standard: %s"
|
||||
|
||||
#: src/Product/LicensedProductType.php:194
|
||||
#: src/Product/LicensedProductType.php:222
|
||||
msgid "Yes"
|
||||
msgstr "Ja"
|
||||
|
||||
#: src/Product/LicensedProductType.php:194
|
||||
#: src/Product/LicensedProductType.php:222
|
||||
msgid "No"
|
||||
msgstr "Nein"
|
||||
|
||||
#: src/Product/LicensedProductType.php:321
|
||||
#: src/Product/LicensedProductType.php:447
|
||||
msgid "Version:"
|
||||
msgstr "Version:"
|
||||
|
||||
#: src/Product/LicensedProductType.php:349
|
||||
#: src/Product/LicensedProductType.php:523
|
||||
msgid "Licensed products are always virtual"
|
||||
msgstr "Lizenzierte Produkte sind immer virtuell"
|
||||
|
||||
#: src/Product/LicensedProductType.php:351
|
||||
#: src/Product/LicensedProductType.php:525
|
||||
msgid "Virtual"
|
||||
msgstr "Virtuell"
|
||||
|
||||
#: src/Product/LicensedProductType.php:384
|
||||
#: src/Product/LicensedProductType.php:558
|
||||
msgid "License Duration (Days)"
|
||||
msgstr "Lizenz-Gültigkeit (Tage)"
|
||||
|
||||
#: src/Product/LicensedProductType.php:393
|
||||
#: src/Product/LicensedProductType.php:567
|
||||
msgid "Leave empty for parent default. 0 = Lifetime."
|
||||
msgstr "Leer lassen für übergeordneten Standard. 0 = Lebenslang."
|
||||
|
||||
#: src/Product/LicensedProductType.php:405
|
||||
#: src/Product/LicensedProductType.php:579
|
||||
msgid "Leave empty for parent default."
|
||||
msgstr "Leer lassen für übergeordneten Standard."
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:143
|
||||
msgid "Monthly"
|
||||
msgstr "Monatlich"
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:147
|
||||
msgid "Quarterly"
|
||||
msgstr "Vierteljährlich"
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:151
|
||||
msgid "Yearly"
|
||||
msgstr "Jährlich"
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:156
|
||||
#, php-format
|
||||
msgid "%d day"
|
||||
msgid_plural "%d days"
|
||||
msgstr[0] "%d Tag"
|
||||
msgstr[1] "%d Tage"
|
||||
|
||||
#: src/Frontend/DownloadController.php:77
|
||||
#: src/Frontend/DownloadController.php:101
|
||||
msgid "Invalid download link."
|
||||
@@ -1610,6 +1630,7 @@ msgstr "Bitte melden Sie sich an, um Ihre Lizenzen zu sehen."
|
||||
msgid "You have no licenses yet."
|
||||
msgstr "Sie haben noch keine Lizenzen."
|
||||
|
||||
#. translators: %s: order number
|
||||
#: src/Frontend/AccountController.php:245
|
||||
#, php-format
|
||||
msgid "Order #%s"
|
||||
@@ -1767,6 +1788,7 @@ msgstr ""
|
||||
"Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz vor "
|
||||
"dem Ablaufdatum."
|
||||
|
||||
#. translators: %s: list of placeholders
|
||||
#: src/Email/LicenseExpirationEmail.php:301
|
||||
#: src/Email/LicenseExpiredEmail.php:288
|
||||
#, php-format
|
||||
@@ -1905,6 +1927,7 @@ msgstr ""
|
||||
msgid "Configure License"
|
||||
msgstr "Lizenz konfigurieren"
|
||||
|
||||
#. translators: %s: WooCommerce plugin name
|
||||
#: wc-licensed-product.php:61
|
||||
#, php-format
|
||||
msgid "%s requires WooCommerce to be installed and active."
|
||||
@@ -1916,6 +1939,15 @@ msgstr ""
|
||||
"WC Licensed Product benötigt WooCommerce als installierte und aktivierte "
|
||||
"Erweiterung."
|
||||
|
||||
#~ msgid "Default Max Activations"
|
||||
#~ msgstr "Standard Max. Aktivierungen"
|
||||
|
||||
#~ msgid "Default License Validity (Days)"
|
||||
#~ msgstr "Standard Lizenz-Gültigkeit (Tage)"
|
||||
|
||||
#~ msgid "Default Bind to Major Version"
|
||||
#~ msgstr "Standard An Hauptversion binden"
|
||||
|
||||
#~ msgid "API Verification Secret"
|
||||
#~ msgstr "API-Verifizierungs-Secret"
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the wc-licensed-product package.
|
||||
# This file is distributed under the same license as the WC Licensed Product package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: wc-licensed-product 0.5.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 17:06+0100\n"
|
||||
"Project-Id-Version: WC Licensed Product 0.5.12\n"
|
||||
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
|
||||
"POT-Creation-Date: 2026-01-27 14:41+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -303,10 +303,11 @@ msgstr ""
|
||||
#: src/Admin/AdminController.php:1373 src/Admin/AdminController.php:1613
|
||||
#: src/Admin/DashboardWidgetController.php:136
|
||||
#: src/Admin/OrderLicenseController.php:260
|
||||
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:136
|
||||
#: src/Product/LicensedProductType.php:184
|
||||
#: src/Product/LicensedProductType.php:379
|
||||
#: src/Product/LicensedProductVariation.php:139
|
||||
#: src/Admin/SettingsController.php:192
|
||||
#: src/Product/LicensedProductVariation.php:194
|
||||
#: src/Product/LicensedProductType.php:164
|
||||
#: src/Product/LicensedProductType.php:212
|
||||
#: src/Product/LicensedProductType.php:553
|
||||
#: src/Frontend/AccountController.php:286
|
||||
msgid "Lifetime"
|
||||
msgstr ""
|
||||
@@ -484,6 +485,7 @@ msgstr ""
|
||||
msgid "License set to lifetime successfully."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1106
|
||||
#, php-format
|
||||
msgid "%d license activated."
|
||||
@@ -491,6 +493,7 @@ msgid_plural "%d licenses activated."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1114
|
||||
#, php-format
|
||||
msgid "%d license deactivated."
|
||||
@@ -498,6 +501,7 @@ msgid_plural "%d licenses deactivated."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1122
|
||||
#, php-format
|
||||
msgid "%d license revoked."
|
||||
@@ -505,6 +509,7 @@ msgid_plural "%d licenses revoked."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1130
|
||||
#, php-format
|
||||
msgid "%d license deleted."
|
||||
@@ -512,6 +517,7 @@ msgid_plural "%d licenses deleted."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of licenses
|
||||
#: src/Admin/AdminController.php:1138
|
||||
#, php-format
|
||||
msgid "%d license extended."
|
||||
@@ -531,6 +537,7 @@ msgstr ""
|
||||
msgid "No licenses to export."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: number of licenses imported
|
||||
#: src/Admin/AdminController.php:1159
|
||||
#, php-format
|
||||
msgid "%d license imported."
|
||||
@@ -538,6 +545,7 @@ msgid_plural "%d licenses imported."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of licenses updated
|
||||
#: src/Admin/AdminController.php:1166
|
||||
#, php-format
|
||||
msgid "%d updated."
|
||||
@@ -545,6 +553,7 @@ msgid_plural "%d updated."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of licenses skipped
|
||||
#: src/Admin/AdminController.php:1174
|
||||
#, php-format
|
||||
msgid "%d skipped."
|
||||
@@ -552,6 +561,7 @@ msgid_plural "%d skipped."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %d: number of errors
|
||||
#: src/Admin/AdminController.php:1182
|
||||
#, php-format
|
||||
msgid "%d error."
|
||||
@@ -996,6 +1006,7 @@ msgstr ""
|
||||
msgid "View in Licenses"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Link to licenses page
|
||||
#: src/Admin/OrderLicenseController.php:280
|
||||
#, php-format
|
||||
msgid "For more actions (revoke, extend, delete), go to the %s page."
|
||||
@@ -1132,16 +1143,17 @@ msgid ""
|
||||
"product settings override these defaults."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/SettingsController.php:176
|
||||
msgid "Default Max Activations"
|
||||
#: src/Admin/SettingsController.php:176 src/Product/LicensedProductType.php:182
|
||||
#: src/Product/LicensedProductType.php:570
|
||||
msgid "Max Activations"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/SettingsController.php:178
|
||||
msgid "Default maximum number of domain activations per license."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/SettingsController.php:187
|
||||
msgid "Default License Validity (Days)"
|
||||
#: src/Admin/SettingsController.php:187 src/Product/LicensedProductType.php:200
|
||||
msgid "License Validity (Days)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/SettingsController.php:189
|
||||
@@ -1150,8 +1162,8 @@ msgid ""
|
||||
"lifetime licenses."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/SettingsController.php:199
|
||||
msgid "Default Bind to Major Version"
|
||||
#: src/Admin/SettingsController.php:199 src/Product/LicensedProductType.php:218
|
||||
msgid "Bind to Major Version"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/SettingsController.php:201
|
||||
@@ -1174,6 +1186,7 @@ msgstr ""
|
||||
msgid "Expiration Warning Schedule"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: URL to WooCommerce email settings
|
||||
#: src/Admin/SettingsController.php:230
|
||||
#, php-format
|
||||
msgid ""
|
||||
@@ -1297,6 +1310,7 @@ msgstr ""
|
||||
msgid "Each license requires a unique domain."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: license number
|
||||
#: src/Checkout/CheckoutBlocksIntegration.php:129
|
||||
#: src/Checkout/CheckoutController.php:224
|
||||
#, php-format
|
||||
@@ -1308,6 +1322,16 @@ msgstr ""
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: src/Checkout/CheckoutController.php:215
|
||||
#, php-format
|
||||
msgid "licensed_domains[%s][%d]"
|
||||
msgstr ""
|
||||
|
||||
#: src/Checkout/CheckoutController.php:216
|
||||
#, php-format
|
||||
msgid "licensed_domain_%s_%d"
|
||||
msgstr ""
|
||||
|
||||
#: src/Checkout/CheckoutController.php:323
|
||||
msgid "Please enter a domain for your license."
|
||||
msgstr ""
|
||||
@@ -1316,16 +1340,19 @@ msgstr ""
|
||||
msgid "Please enter a valid domain for your license."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: product name, 2: license number
|
||||
#: src/Checkout/CheckoutController.php:356
|
||||
#, php-format
|
||||
msgid "Please enter a domain for %1$s (License %2$d)."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: product name, 2: license number
|
||||
#: src/Checkout/CheckoutController.php:371
|
||||
#, php-format
|
||||
msgid "Please enter a valid domain for %1$s (License %2$d)."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: domain name, 2: product name
|
||||
#: src/Checkout/CheckoutController.php:385
|
||||
#, php-format
|
||||
msgid ""
|
||||
@@ -1387,119 +1414,112 @@ msgstr ""
|
||||
msgid "Attachment file not found."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: provided hash, 2: calculated hash
|
||||
#: src/Product/VersionManager.php:177
|
||||
#, php-format
|
||||
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:72
|
||||
#: src/Product/LicensedProductVariation.php:198
|
||||
msgid "Monthly"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:202
|
||||
msgid "Quarterly"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:206
|
||||
msgid "Yearly"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: number of days
|
||||
#: src/Product/LicensedProductVariation.php:211
|
||||
#, php-format
|
||||
msgid "%d day"
|
||||
msgid_plural "%d days"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:82
|
||||
msgid "Licensed Product"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:73
|
||||
#: src/Product/LicensedProductType.php:83
|
||||
msgid "Licensed Variable Product"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:108
|
||||
#: src/Product/LicensedProductType.php:136
|
||||
msgid "License Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:135
|
||||
#: src/Product/LicensedProductType.php:378
|
||||
#: src/Product/LicensedProductType.php:163
|
||||
#: src/Product/LicensedProductType.php:552
|
||||
#, php-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:145
|
||||
#. translators: %s: URL to settings page
|
||||
#: src/Product/LicensedProductType.php:173
|
||||
#, php-format
|
||||
msgid "Leave fields empty to use default settings from %s."
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:147
|
||||
#: src/Product/LicensedProductType.php:175
|
||||
msgid "WooCommerce > Settings > Licensed Products"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:154
|
||||
#: src/Product/LicensedProductType.php:396
|
||||
msgid "Max Activations"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:157
|
||||
#. translators: %d: default max activations value
|
||||
#: src/Product/LicensedProductType.php:185
|
||||
#, php-format
|
||||
msgid "Maximum number of domain activations per license. Default: %d"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:172
|
||||
msgid "License Validity (Days)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:175
|
||||
#. translators: %s: default validity value
|
||||
#: src/Product/LicensedProductType.php:203
|
||||
#, php-format
|
||||
msgid "Number of days the license is valid. Leave empty for default (%s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:190
|
||||
msgid "Bind to Major Version"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:193
|
||||
#. translators: %s: default bind to version value (Yes/No)
|
||||
#: src/Product/LicensedProductType.php:221
|
||||
#, php-format
|
||||
msgid ""
|
||||
"If enabled, licenses are bound to the major version at purchase time. "
|
||||
"Default: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:194
|
||||
#: src/Product/LicensedProductType.php:222
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:194
|
||||
#: src/Product/LicensedProductType.php:222
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:321
|
||||
#: src/Product/LicensedProductType.php:447
|
||||
msgid "Version:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:349
|
||||
#: src/Product/LicensedProductType.php:523
|
||||
msgid "Licensed products are always virtual"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:351
|
||||
#: src/Product/LicensedProductType.php:525
|
||||
msgid "Virtual"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:384
|
||||
#: src/Product/LicensedProductType.php:558
|
||||
msgid "License Duration (Days)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:393
|
||||
#: src/Product/LicensedProductType.php:567
|
||||
msgid "Leave empty for parent default. 0 = Lifetime."
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductType.php:405
|
||||
#: src/Product/LicensedProductType.php:579
|
||||
msgid "Leave empty for parent default."
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:143
|
||||
msgid "Monthly"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:147
|
||||
msgid "Quarterly"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:151
|
||||
msgid "Yearly"
|
||||
msgstr ""
|
||||
|
||||
#: src/Product/LicensedProductVariation.php:156
|
||||
#, php-format
|
||||
msgid "%d day"
|
||||
msgid_plural "%d days"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/Frontend/DownloadController.php:77
|
||||
#: src/Frontend/DownloadController.php:101
|
||||
msgid "Invalid download link."
|
||||
@@ -1559,6 +1579,7 @@ msgstr ""
|
||||
msgid "You have no licenses yet."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: order number
|
||||
#: src/Frontend/AccountController.php:245
|
||||
#, php-format
|
||||
msgid "Order #%s"
|
||||
@@ -1708,6 +1729,7 @@ msgid ""
|
||||
"expiration date."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: list of placeholders
|
||||
#: src/Email/LicenseExpirationEmail.php:301
|
||||
#: src/Email/LicenseExpiredEmail.php:288
|
||||
#, php-format
|
||||
@@ -1838,6 +1860,7 @@ msgstr ""
|
||||
msgid "Configure License"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: WooCommerce plugin name
|
||||
#: wc-licensed-product.php:61
|
||||
#, php-format
|
||||
msgid "%s requires WooCommerce to be installed and active."
|
||||
|
||||
BIN
releases/wc-licensed-product-0.5.10.zip
Normal file
BIN
releases/wc-licensed-product-0.5.10.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.10.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.10.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
2bbc0655f724e201367247f0e40974ddce6d7c559987e661f2b06b43294fc99f wc-licensed-product-0.5.10.zip
|
||||
BIN
releases/wc-licensed-product-0.5.11.zip
Normal file
BIN
releases/wc-licensed-product-0.5.11.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.11.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.11.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
32571178bfa8f0d0a03ed05b498d5f9b3c860104393a96732e86a03b6de298d2 wc-licensed-product-0.5.11.zip
|
||||
1
releases/wc-licensed-product-0.5.12.sha256
Normal file
1
releases/wc-licensed-product-0.5.12.sha256
Normal file
@@ -0,0 +1 @@
|
||||
20bb5cd453de9bca781864430ebd152c82f660b6f9fc3f09107ba03489a71d75 /home/magdev/workspaces/php/wordpress/wp-content/plugins/wc-licensed-product/releases/wc-licensed-product-0.5.12.zip
|
||||
BIN
releases/wc-licensed-product-0.5.12.zip
Normal file
BIN
releases/wc-licensed-product-0.5.12.zip
Normal file
Binary file not shown.
BIN
releases/wc-licensed-product-0.5.5.zip
Normal file
BIN
releases/wc-licensed-product-0.5.5.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.5.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.5.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
8c37e1c68eb6031c37d35adc516a492abdbea8498bdc3e3fc7d93eda380a4fe0 wc-licensed-product-0.5.5.zip
|
||||
BIN
releases/wc-licensed-product-0.5.6.zip
Normal file
BIN
releases/wc-licensed-product-0.5.6.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.6.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.6.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
4d35a319fe4cb4e7055bae17fc030487ca05e5e9ac905f76d0ac62002bde4336 releases/wc-licensed-product-0.5.6.zip
|
||||
BIN
releases/wc-licensed-product-0.5.7.zip
Normal file
BIN
releases/wc-licensed-product-0.5.7.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.7.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.7.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
ceb4d57598f576f4f172153ff80df8c180ecd4dca873cf109327fc5ac718930f wc-licensed-product-0.5.7.zip
|
||||
BIN
releases/wc-licensed-product-0.5.8.zip
Normal file
BIN
releases/wc-licensed-product-0.5.8.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.8.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.8.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
670c2f5182ea7140ccf9533c2b4179daf7890019a244973f467f2a5c7622b9f4 wc-licensed-product-0.5.8.zip
|
||||
BIN
releases/wc-licensed-product-0.5.9.zip
Normal file
BIN
releases/wc-licensed-product-0.5.9.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.9.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.9.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
fae77dab56cb8f46693cf44fe6a1dc38ad0526d881cab2cd1f0878b234afaa8b wc-licensed-product-0.5.9.zip
|
||||
@@ -173,7 +173,7 @@ final class SettingsController
|
||||
'id' => 'wc_licensed_product_section_defaults',
|
||||
],
|
||||
'default_max_activations' => [
|
||||
'name' => __('Default Max Activations', 'wc-licensed-product'),
|
||||
'name' => __('Max Activations', 'wc-licensed-product'),
|
||||
'type' => 'number',
|
||||
'desc' => __('Default maximum number of domain activations per license.', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_default_max_activations',
|
||||
@@ -184,7 +184,7 @@ final class SettingsController
|
||||
],
|
||||
],
|
||||
'default_validity_days' => [
|
||||
'name' => __('Default License Validity (Days)', 'wc-licensed-product'),
|
||||
'name' => __('License Validity (Days)', 'wc-licensed-product'),
|
||||
'type' => 'number',
|
||||
'desc' => __('Default number of days a license is valid. Leave empty or set to 0 for lifetime licenses.', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_default_validity_days',
|
||||
@@ -196,7 +196,7 @@ final class SettingsController
|
||||
],
|
||||
],
|
||||
'default_bind_to_version' => [
|
||||
'name' => __('Default Bind to Major Version', 'wc-licensed-product'),
|
||||
'name' => __('Bind to Major Version', 'wc-licensed-product'),
|
||||
'type' => 'checkbox',
|
||||
'desc' => __('If enabled, licenses are bound to the major version at purchase time by default.', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_default_bind_to_version',
|
||||
|
||||
@@ -55,6 +55,14 @@ class LicensedProduct extends WC_Product
|
||||
return $this->exists() && $this->get_price() !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Licensed products are always in stock (virtual, no inventory)
|
||||
*/
|
||||
public function is_in_stock(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max activations for this product
|
||||
* Falls back to default settings if not set on product
|
||||
|
||||
@@ -32,7 +32,7 @@ final class LicensedProductType
|
||||
{
|
||||
// Register product types
|
||||
add_filter('product_type_selector', [$this, 'addProductType']);
|
||||
add_filter('woocommerce_product_class', [$this, 'getProductClass'], 10, 2);
|
||||
add_filter('woocommerce_product_class', [$this, 'getProductClass'], 10, 4);
|
||||
|
||||
// Add product data tabs
|
||||
add_filter('woocommerce_product_data_tabs', [$this, 'addProductDataTab']);
|
||||
@@ -46,9 +46,19 @@ final class LicensedProductType
|
||||
add_action('woocommerce_licensed_add_to_cart', [$this, 'addToCartTemplate']);
|
||||
add_action('woocommerce_licensed-variable_add_to_cart', [$this, 'variableAddToCartTemplate']);
|
||||
|
||||
// Use variable product add-to-cart handler for licensed-variable products
|
||||
add_filter('woocommerce_add_to_cart_handler', [$this, 'addToCartHandler'], 10, 2);
|
||||
|
||||
// Make product virtual by default
|
||||
add_filter('woocommerce_product_is_virtual', [$this, 'isVirtual'], 10, 2);
|
||||
|
||||
// Hide stock HTML for licensed products
|
||||
add_filter('woocommerce_get_stock_html', [$this, 'hideStockHtml'], 10, 2);
|
||||
add_filter('woocommerce_get_availability', [$this, 'hideAvailability'], 10, 2);
|
||||
add_filter('woocommerce_get_availability_text', [$this, 'hideAvailabilityText'], 10, 2);
|
||||
add_filter('woocommerce_product_get_stock_quantity', [$this, 'hideStockQuantity'], 10, 2);
|
||||
add_filter('woocommerce_product_variation_get_stock_quantity', [$this, 'hideStockQuantity'], 10, 2);
|
||||
|
||||
// Display current version under product title on single product page
|
||||
add_action('woocommerce_single_product_summary', [$this, 'displayCurrentVersion'], 6);
|
||||
|
||||
@@ -76,8 +86,13 @@ final class LicensedProductType
|
||||
|
||||
/**
|
||||
* Get product class for licensed types
|
||||
*
|
||||
* @param string $className Default class name
|
||||
* @param string $productType Product type
|
||||
* @param string $postType Post type (usually 'product' or 'product_variation')
|
||||
* @param mixed $productId Product ID (can be int or string)
|
||||
*/
|
||||
public function getProductClass(string $className, string $productType): string
|
||||
public function getProductClass(string $className, string $productType, string $postType = '', $productId = 0): string
|
||||
{
|
||||
if ($productType === 'licensed') {
|
||||
return LicensedProduct::class;
|
||||
@@ -86,11 +101,24 @@ final class LicensedProductType
|
||||
return LicensedVariableProduct::class;
|
||||
}
|
||||
// Handle variations of licensed-variable products
|
||||
if ($productType === 'variation') {
|
||||
// Check if parent is licensed-variable
|
||||
// Check both by product type and by post type for variations
|
||||
if ($productType === 'variation' || $postType === 'product_variation') {
|
||||
// Get parent ID from the product post
|
||||
$parentId = 0;
|
||||
$productIdInt = (int) $productId;
|
||||
if ($productIdInt > 0) {
|
||||
$parentId = wp_get_post_parent_id($productIdInt);
|
||||
}
|
||||
// Fallback to global $post if product ID not available
|
||||
if (!$parentId) {
|
||||
global $post;
|
||||
if ($post && $post->post_parent) {
|
||||
$parentType = \WC_Product_Factory::get_product_type($post->post_parent);
|
||||
$parentId = (int) $post->post_parent;
|
||||
}
|
||||
}
|
||||
|
||||
if ($parentId) {
|
||||
$parentType = \WC_Product_Factory::get_product_type($parentId);
|
||||
if ($parentType === 'licensed-variable') {
|
||||
return LicensedProductVariation::class;
|
||||
}
|
||||
@@ -201,23 +229,31 @@ final class LicensedProductType
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function($) {
|
||||
// Show/hide panels based on product type
|
||||
$('select#product-type').change(function() {
|
||||
if ($(this).val() === 'licensed') {
|
||||
// Show/hide panels based on product type for license settings tab
|
||||
function toggleLicensedProductOptions() {
|
||||
var productType = $('#product-type').val();
|
||||
var isLicensed = productType === 'licensed';
|
||||
var isLicensedVariable = productType === 'licensed-variable';
|
||||
|
||||
if (isLicensed || isLicensedVariable) {
|
||||
// Show license settings tab
|
||||
$('.show_if_licensed').show();
|
||||
$('.show_if_licensed-variable').show();
|
||||
$('.general_options').show();
|
||||
$('.pricing').show();
|
||||
} else {
|
||||
$('.show_if_licensed').hide();
|
||||
}
|
||||
}).change();
|
||||
|
||||
// Show general tab for licensed products
|
||||
$('#product-type').on('change', function() {
|
||||
if ($(this).val() === 'licensed') {
|
||||
$('.general_tab').show();
|
||||
} else {
|
||||
// Hide license settings tab for other product types
|
||||
$('.show_if_licensed').hide();
|
||||
$('.show_if_licensed-variable').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial state on page load
|
||||
toggleLicensedProductOptions();
|
||||
|
||||
// On product type change
|
||||
$('#product-type').on('change', toggleLicensedProductOptions);
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
@@ -257,26 +293,111 @@ final class LicensedProductType
|
||||
wc_get_template('single-product/add-to-cart/simple.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the variable product add-to-cart handler for licensed-variable products
|
||||
* WooCommerce uses product type to determine which handler to use
|
||||
*/
|
||||
public function addToCartHandler(string $handler, \WC_Product $product): string
|
||||
{
|
||||
if ($product->is_type('licensed-variable')) {
|
||||
return 'variable';
|
||||
}
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide stock HTML for licensed products (they're always virtual/in-stock)
|
||||
*/
|
||||
public function hideStockHtml(string $html, \WC_Product $product): string
|
||||
{
|
||||
if ($this->isLicensedProductOrVariation($product)) {
|
||||
return '';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide availability data for licensed products (they're always virtual/in-stock)
|
||||
*/
|
||||
public function hideAvailability(array $availability, \WC_Product $product): array
|
||||
{
|
||||
if ($this->isLicensedProductOrVariation($product)) {
|
||||
return [
|
||||
'availability' => '',
|
||||
'class' => '',
|
||||
];
|
||||
}
|
||||
return $availability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide availability text for licensed products
|
||||
*/
|
||||
public function hideAvailabilityText(string $availability, \WC_Product $product): string
|
||||
{
|
||||
if ($this->isLicensedProductOrVariation($product)) {
|
||||
return '';
|
||||
}
|
||||
return $availability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide stock quantity for licensed products (return null = no stock display)
|
||||
*
|
||||
* @param int|null $quantity
|
||||
* @param \WC_Product $product
|
||||
* @return int|null
|
||||
*/
|
||||
public function hideStockQuantity($quantity, \WC_Product $product)
|
||||
{
|
||||
if ($this->isLicensedProductOrVariation($product)) {
|
||||
return null;
|
||||
}
|
||||
return $quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product is a licensed product or variation of one
|
||||
*/
|
||||
private function isLicensedProductOrVariation(\WC_Product $product): bool
|
||||
{
|
||||
// Direct licensed products
|
||||
if ($product->is_type('licensed') || $product->is_type('licensed-variable')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check by class name for our custom variation class
|
||||
if ($product instanceof LicensedProductVariation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if this is a variation with a licensed-variable parent
|
||||
// Use WC_Product_Factory::get_product_type() to get parent type directly from DB
|
||||
// This is more reliable than loading the full product object
|
||||
$parentId = $product->get_parent_id();
|
||||
if ($parentId) {
|
||||
$parentType = \WC_Product_Factory::get_product_type($parentId);
|
||||
if ($parentType === 'licensed-variable') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make licensed products virtual by default
|
||||
*/
|
||||
public function isVirtual(bool $isVirtual, \WC_Product $product): bool
|
||||
{
|
||||
if ($product->is_type('licensed') || $product->is_type('licensed-variable')) {
|
||||
if ($this->isLicensedProductOrVariation($product)) {
|
||||
return true;
|
||||
}
|
||||
// Also handle variations of licensed-variable products
|
||||
if ($product->is_type('variation') && $product->get_parent_id()) {
|
||||
$parent = wc_get_product($product->get_parent_id());
|
||||
if ($parent && $parent->is_type('licensed-variable')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return $isVirtual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend styles for licensed products on single product pages
|
||||
* Enqueue frontend styles and scripts for licensed products on single product pages
|
||||
*/
|
||||
public function enqueueFrontendStyles(): void
|
||||
{
|
||||
@@ -296,6 +417,11 @@ final class LicensedProductType
|
||||
[],
|
||||
WC_LICENSED_PRODUCT_VERSION
|
||||
);
|
||||
|
||||
// For licensed-variable products, enqueue WooCommerce variation scripts
|
||||
if ($product->is_type('licensed-variable')) {
|
||||
wp_enqueue_script('wc-add-to-cart-variation');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,10 +451,58 @@ final class LicensedProductType
|
||||
|
||||
/**
|
||||
* Add to cart template for variable licensed products
|
||||
* This mirrors WooCommerce's woocommerce_variable_add_to_cart() function
|
||||
*/
|
||||
public function variableAddToCartTemplate(): void
|
||||
{
|
||||
wc_get_template('single-product/add-to-cart/variable.php');
|
||||
global $product;
|
||||
|
||||
// The hook woocommerce_licensed-variable_add_to_cart only fires for this product type
|
||||
// so we just need to verify the product exists
|
||||
if (!$product) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we're working with a product that has variable product methods
|
||||
// Re-load the product to ensure we get the correct class instance
|
||||
$productId = $product->get_id();
|
||||
$variableProduct = wc_get_product($productId);
|
||||
|
||||
if (!$variableProduct || !method_exists($variableProduct, 'get_variation_attributes')) {
|
||||
// Fallback to simple add to cart if not a variable product
|
||||
wc_get_template('single-product/add-to-cart/simple.php');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update global $product to use the correctly loaded instance
|
||||
// This ensures the template has the right product type
|
||||
$product = $variableProduct;
|
||||
|
||||
// Get variations count to determine if we should load them via AJAX
|
||||
$children = $variableProduct->get_children();
|
||||
$getVariations = count($children) <= apply_filters('woocommerce_ajax_variation_threshold', 30, $variableProduct);
|
||||
|
||||
// Get template variables - WooCommerce expects these to be set
|
||||
$availableVariations = $getVariations ? $variableProduct->get_available_variations() : false;
|
||||
$attributes = $variableProduct->get_variation_attributes();
|
||||
$selectedAttributes = $variableProduct->get_default_attributes();
|
||||
|
||||
// Ensure arrays (WooCommerce template expects arrays, not null)
|
||||
if (!is_array($attributes)) {
|
||||
$attributes = [];
|
||||
}
|
||||
if (!is_array($selectedAttributes)) {
|
||||
$selectedAttributes = [];
|
||||
}
|
||||
|
||||
wc_get_template(
|
||||
'single-product/add-to-cart/variable.php',
|
||||
[
|
||||
'available_variations' => $availableVariations,
|
||||
'attributes' => $attributes,
|
||||
'selected_attributes' => $selectedAttributes,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -501,9 +675,13 @@ final class LicensedProductType
|
||||
// Show general and variations tabs
|
||||
$('.general_tab').show();
|
||||
$('.variations_tab').show();
|
||||
$('.variations_options').show();
|
||||
|
||||
// Hide shipping tab (virtual products)
|
||||
$('.shipping_tab').hide();
|
||||
|
||||
// Ensure the variations panel can be displayed
|
||||
$('#variable_product_options').show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,8 +690,29 @@ final class LicensedProductType
|
||||
|
||||
// On product type change
|
||||
$('#product-type').on('change', function() {
|
||||
toggleLicensedVariableOptions();
|
||||
// Use setTimeout to let WooCommerce finish its own processing first
|
||||
setTimeout(toggleLicensedVariableOptions, 100);
|
||||
});
|
||||
|
||||
// Re-apply after WooCommerce AJAX operations that may reset visibility
|
||||
$(document).on('woocommerce_variations_loaded', toggleLicensedVariableOptions);
|
||||
$(document).on('woocommerce_variations_added', toggleLicensedVariableOptions);
|
||||
$(document).on('woocommerce_variations_saved', toggleLicensedVariableOptions);
|
||||
|
||||
// Handle AJAX complete events for attribute saving
|
||||
$(document).ajaxComplete(function(event, xhr, settings) {
|
||||
// Check if this was a product data save or attribute action
|
||||
if (settings.data && (
|
||||
settings.data.indexOf('action=woocommerce_save_attributes') !== -1 ||
|
||||
settings.data.indexOf('action=woocommerce_load_variations') !== -1 ||
|
||||
settings.data.indexOf('action=woocommerce_add_variation') !== -1
|
||||
)) {
|
||||
setTimeout(toggleLicensedVariableOptions, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// Also listen for the WooCommerce product type show/hide trigger
|
||||
$('body').on('woocommerce-product-type-change', toggleLicensedVariableOptions);
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
||||
@@ -35,6 +35,61 @@ class LicensedProductVariation extends WC_Product_Variation
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Licensed products are always in stock (virtual, no inventory)
|
||||
*/
|
||||
public function is_in_stock(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get availability - empty for licensed products (no stock indicator)
|
||||
*/
|
||||
public function get_availability(): array
|
||||
{
|
||||
return [
|
||||
'availability' => '',
|
||||
'class' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't manage stock for licensed products
|
||||
*/
|
||||
public function managing_stock(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if variation is purchasable
|
||||
* Override to handle custom parent product type
|
||||
*/
|
||||
public function is_purchasable(): bool
|
||||
{
|
||||
// Check if variation exists
|
||||
if (!$this->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check parent product status
|
||||
$parentId = $this->get_parent_id();
|
||||
$parentStatus = get_post_status($parentId);
|
||||
|
||||
if ($parentStatus !== 'publish' && !current_user_can('edit_post', $parentId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if variation has a price
|
||||
$price = $this->get_price();
|
||||
if ($price === '' || $price === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return apply_filters('woocommerce_variation_is_purchasable', true, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max activations for this variation
|
||||
* Falls back to parent product, then to default settings
|
||||
|
||||
@@ -41,6 +41,19 @@ class LicensedVariableProduct extends WC_Product_Variable
|
||||
return 'licensed-variable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product is of a certain type
|
||||
* Override to return true for 'variable' as well, so WooCommerce internal
|
||||
* checks pass (many methods in WC_Product_Variable check is_type('variable'))
|
||||
*/
|
||||
public function is_type($type): bool
|
||||
{
|
||||
if (is_array($type)) {
|
||||
return in_array($this->get_type(), $type, true) || in_array('variable', $type, true);
|
||||
}
|
||||
return $this->get_type() === $type || 'variable' === $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Licensed products are always virtual
|
||||
*/
|
||||
@@ -50,11 +63,197 @@ class LicensedVariableProduct extends WC_Product_Variable
|
||||
}
|
||||
|
||||
/**
|
||||
* Licensed products are purchasable
|
||||
* Licensed variable products are purchasable if the parent check passes
|
||||
* Variable products don't have a direct price - their variations do
|
||||
*/
|
||||
public function is_purchasable(): bool
|
||||
{
|
||||
return $this->exists() && $this->get_price() !== '';
|
||||
// Use the parent WC_Product_Variable logic
|
||||
// which checks exists() and status, not price
|
||||
return parent::is_purchasable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Licensed products are always in stock (virtual, no inventory)
|
||||
*/
|
||||
public function is_in_stock(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get children (variations) for this product
|
||||
* Override because WC_Product_Variable::get_children() checks is_type('variable')
|
||||
* which fails for our 'licensed-variable' type
|
||||
*/
|
||||
public function get_children($context = 'view'): array
|
||||
{
|
||||
if (!$this->get_id()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Query variations directly from database since WooCommerce's data store
|
||||
// doesn't work properly with custom variable product types
|
||||
global $wpdb;
|
||||
$children = $wpdb->get_col($wpdb->prepare(
|
||||
"SELECT ID FROM {$wpdb->posts}
|
||||
WHERE post_parent = %d
|
||||
AND post_type = 'product_variation'
|
||||
AND post_status IN ('publish', 'private')
|
||||
ORDER BY menu_order ASC, ID ASC",
|
||||
$this->get_id()
|
||||
));
|
||||
|
||||
$children = array_map('intval', $children);
|
||||
|
||||
if ('view' === $context) {
|
||||
$children = apply_filters('woocommerce_get_children', $children, $this, false);
|
||||
}
|
||||
|
||||
return is_array($children) ? $children : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variation attributes for this product
|
||||
* Override because WC_Product_Variable uses data_store which doesn't work
|
||||
* properly with custom variable product types
|
||||
*/
|
||||
public function get_variation_attributes(): array
|
||||
{
|
||||
$attributes = $this->get_attributes();
|
||||
|
||||
if (!$attributes || !is_array($attributes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$variation_attributes = [];
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
// For WC_Product_Attribute objects
|
||||
if ($attribute instanceof \WC_Product_Attribute) {
|
||||
if ($attribute->get_variation()) {
|
||||
$attribute_name = $attribute->get_name();
|
||||
|
||||
// For taxonomy attributes, get term slugs
|
||||
if ($attribute->is_taxonomy()) {
|
||||
$attribute_terms = wc_get_product_terms(
|
||||
$this->get_id(),
|
||||
$attribute_name,
|
||||
['fields' => 'slugs']
|
||||
);
|
||||
$variation_attributes[$attribute_name] = $attribute_terms;
|
||||
} else {
|
||||
// For custom attributes, get options directly
|
||||
$variation_attributes[$attribute_name] = $attribute->get_options();
|
||||
}
|
||||
}
|
||||
}
|
||||
// For array-based attributes (older format)
|
||||
elseif (is_array($attribute) && !empty($attribute['is_variation'])) {
|
||||
$attribute_name = $attribute['name'];
|
||||
$values = isset($attribute['value']) ? explode('|', $attribute['value']) : [];
|
||||
$variation_attributes[$attribute_name] = array_map('trim', $values);
|
||||
}
|
||||
}
|
||||
|
||||
return $variation_attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variation prices (regular, sale, and final prices)
|
||||
* Override because WC_Product_Variable uses data_store which doesn't work
|
||||
* properly with custom variable product types
|
||||
*/
|
||||
public function get_variation_prices($for_display = false): array
|
||||
{
|
||||
$children = $this->get_children();
|
||||
|
||||
if (empty($children)) {
|
||||
return [
|
||||
'price' => [],
|
||||
'regular_price' => [],
|
||||
'sale_price' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$prices = [
|
||||
'price' => [],
|
||||
'regular_price' => [],
|
||||
'sale_price' => [],
|
||||
];
|
||||
|
||||
foreach ($children as $child_id) {
|
||||
$variation = wc_get_product($child_id);
|
||||
if ($variation) {
|
||||
$price = $variation->get_price();
|
||||
$regular_price = $variation->get_regular_price();
|
||||
$sale_price = $variation->get_sale_price();
|
||||
|
||||
if ('' !== $price) {
|
||||
$prices['price'][$child_id] = $price;
|
||||
}
|
||||
if ('' !== $regular_price) {
|
||||
$prices['regular_price'][$child_id] = $regular_price;
|
||||
}
|
||||
if ('' !== $sale_price) {
|
||||
$prices['sale_price'][$child_id] = $sale_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort prices
|
||||
asort($prices['price']);
|
||||
asort($prices['regular_price']);
|
||||
asort($prices['sale_price']);
|
||||
|
||||
$this->prices_array = $prices;
|
||||
|
||||
return $this->prices_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available variations for this product
|
||||
* Override because WC_Product_Variable uses data_store which doesn't work
|
||||
* properly with custom variable product types
|
||||
*/
|
||||
public function get_available_variations($return = 'array')
|
||||
{
|
||||
$children = $this->get_children();
|
||||
$available_variations = [];
|
||||
|
||||
foreach ($children as $child_id) {
|
||||
$variation = wc_get_product($child_id);
|
||||
|
||||
if (!$variation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if variation should be available
|
||||
if (!$variation->exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if purchasable (has price)
|
||||
if (!$variation->is_purchasable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build variation data
|
||||
if ($return === 'array') {
|
||||
$variationData = $this->get_available_variation($variation);
|
||||
// Override availability_html to be empty for licensed products
|
||||
$variationData['availability_html'] = '';
|
||||
$available_variations[] = $variationData;
|
||||
} else {
|
||||
$available_variations[] = $variation;
|
||||
}
|
||||
}
|
||||
|
||||
if ($return === 'array') {
|
||||
$available_variations = array_values(array_filter($available_variations));
|
||||
}
|
||||
|
||||
return $available_variations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: WooCommerce Licensed Product
|
||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
|
||||
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
|
||||
* Version: 0.5.5
|
||||
* Version: 0.5.12
|
||||
* Author: Marco Graetsch
|
||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||
* License: GPL-2.0-or-later
|
||||
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
|
||||
}
|
||||
|
||||
// Plugin constants
|
||||
define('WC_LICENSED_PRODUCT_VERSION', '0.5.5');
|
||||
define('WC_LICENSED_PRODUCT_VERSION', '0.5.12');
|
||||
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
||||
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
Reference in New Issue
Block a user