You've already forked wc-licensed-product
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9826c8181e | |||
| fa972ceaf0 | |||
| 3abf05cff3 | |||
| 169eed65eb | |||
| 90cb8d97bd | |||
| fc281f7f4a | |||
| 962368d35f | |||
| 4dcace6f06 | |||
| 62aecc0240 | |||
| 1f676556f2 | |||
| 5f51aafe3b | |||
| 279b0d5dd6 | |||
| 086755cb11 | |||
| 0b58de193e | |||
| ae49b262fa | |||
| 5d5bb7e595 | |||
| bee9854c18 |
83
CHANGELOG.md
83
CHANGELOG.md
@@ -7,6 +7,89 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
- **CRITICAL:** Response signing key derivation now uses native `hash_hkdf()` for RFC 5869 compliance
|
||||||
|
- Key derivation now matches client library (`SecureLicenseClient`) exactly
|
||||||
|
- Added missing domain validation to `/activate` endpoint (1-255 characters)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `ResponseSigner::deriveCustomerSecret()` now uses `hash_hkdf('sha256', $serverSecret, 32, $licenseKey)`
|
||||||
|
- Previous custom HKDF-like implementation was incompatible with client library
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Signatures generated by server now verify correctly with `magdev/wc-licensed-product-client`
|
||||||
|
- All three API endpoints now have consistent parameter validation
|
||||||
|
|
||||||
|
## [0.5.4] - 2026-01-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- REST API `/validate` endpoint now returns HTTP 404 for `license_not_found` error (was 403)
|
||||||
|
- License key validation now enforces minimum 8 characters per API documentation
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Configurable rate limiting via `WC_LICENSE_RATE_LIMIT` and `WC_LICENSE_RATE_WINDOW` constants
|
||||||
|
- Rate limit now defaults to 30 requests per 60 second window (configurable)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved HTTP status code mapping: 404 for not found, 500 for server errors, 403 for all other errors
|
||||||
|
- Rate limiting implementation now uses configurable constants instead of hardcoded values
|
||||||
|
|
||||||
## [0.5.3] - 2026-01-26
|
## [0.5.3] - 2026-01-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
117
CLAUDE.md
117
CLAUDE.md
@@ -1378,3 +1378,120 @@ Major feature release adding support for WooCommerce variable products. Customer
|
|||||||
- Order meta `_licensed_product_domains` now includes optional `variation_id` field
|
- Order meta `_licensed_product_domains` now includes optional `variation_id` field
|
||||||
- License generation uses variation settings when `variation_id` is present in order item
|
- License generation uses variation settings when `variation_id` is present in order item
|
||||||
- Backward compatible: existing simple licensed products continue to work unchanged
|
- Backward compatible: existing simple licensed products continue to work unchanged
|
||||||
|
|
||||||
|
### 2026-01-26 - Version 0.5.4 - API Compliance
|
||||||
|
|
||||||
|
**Overview:**
|
||||||
|
|
||||||
|
Bug fix release aligning server implementation with client documentation at `magdev/wc-licensed-product-client`.
|
||||||
|
|
||||||
|
**Fixed:**
|
||||||
|
|
||||||
|
- `/validate` endpoint now returns HTTP 404 for `license_not_found` error (was returning 403)
|
||||||
|
- License key validation now enforces minimum 8 characters across all API endpoints
|
||||||
|
|
||||||
|
**Implemented:**
|
||||||
|
|
||||||
|
- Configurable rate limiting via `WC_LICENSE_RATE_LIMIT` constant (default: 30 requests)
|
||||||
|
- Configurable rate window via `WC_LICENSE_RATE_WINDOW` constant (default: 60 seconds)
|
||||||
|
- HTTP status code mapping: 404 for not found, 500 for server errors, 403 for all other errors
|
||||||
|
|
||||||
|
**Modified files:**
|
||||||
|
|
||||||
|
- `src/Api/RestApiController.php` - Added configurable rate limiting, fixed HTTP status codes, added license_key validation
|
||||||
|
|
||||||
|
**Technical notes:**
|
||||||
|
|
||||||
|
- Rate limiting now uses `getRateLimit()` and `getRateWindow()` methods instead of constants
|
||||||
|
- New `getStatusCodeForResult()` method maps error codes to HTTP status codes
|
||||||
|
- License key validation callback added to all three endpoints (validate, status, activate)
|
||||||
|
- Uses PHP 8 match expression for status code mapping
|
||||||
|
|
||||||
|
### 2026-01-26 - Version 0.5.5 - Critical Signature Fix
|
||||||
|
|
||||||
|
**Overview:**
|
||||||
|
|
||||||
|
Critical bug fix for response signing. The key derivation algorithm was incompatible with the client library, causing signature verification failures.
|
||||||
|
|
||||||
|
**Critical Fix:**
|
||||||
|
|
||||||
|
- Key derivation now uses PHP's native `hash_hkdf()` function per RFC 5869
|
||||||
|
- Previous custom implementation produced different keys than the client library
|
||||||
|
- Signatures now verify correctly with `magdev/wc-licensed-product-client`
|
||||||
|
|
||||||
|
**Additional Fix:**
|
||||||
|
|
||||||
|
- Added missing domain validation to `/activate` endpoint (1-255 characters)
|
||||||
|
|
||||||
|
**Modified files:**
|
||||||
|
|
||||||
|
- `src/Api/ResponseSigner.php` - Fixed key derivation to use `hash_hkdf()`
|
||||||
|
- `src/Api/RestApiController.php` - Added domain validation to `/activate` endpoint
|
||||||
|
|
||||||
|
**Technical notes:**
|
||||||
|
|
||||||
|
- Old implementation: `hash_hmac('sha256', $prk . "\x01", $serverSecret)` - custom HKDF-like
|
||||||
|
- New implementation: `bin2hex(hash_hkdf('sha256', $serverSecret, 32, $licenseKey))` - RFC 5869
|
||||||
|
- Parameters match client's `ResponseSignature::deriveKey()` exactly:
|
||||||
|
- IKM (input keying material): server_secret
|
||||||
|
- 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
|
||||||
|
|||||||
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
|
### Core Features
|
||||||
|
|
||||||
- **Licensed Product Type**: New WooCommerce product type for software sales
|
- **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)
|
- **Automatic License Generation**: License keys generated on order completion (format: XXXX-XXXX-XXXX-XXXX)
|
||||||
- **Domain Binding**: Licenses are bound to customer-specified domains
|
- **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
|
- **REST API**: Public endpoints for license validation and management
|
||||||
- **Response Signing**: Optional HMAC-SHA256 cryptographic signatures for API responses
|
- **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
|
- **Version Binding**: Optional binding to major software versions
|
||||||
- **Expiration Support**: Set license validity periods or lifetime licenses
|
- **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
|
- **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+)
|
- **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)
|
- **Self-Licensing**: The plugin can validate its own license (for commercial distribution)
|
||||||
|
|
||||||
### Customer Features
|
### 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
|
- **License Transfers**: Customers can transfer licenses to new domains
|
||||||
- **Secure Downloads**: Download purchased software versions with license verification
|
- **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
|
- **Copy to Clipboard**: Easy license key copying
|
||||||
|
- **API Verification Secret**: Per-license secret displayed for secure API integration
|
||||||
|
|
||||||
### Admin Features
|
### Admin Features
|
||||||
|
|
||||||
- **License Management**: Full CRUD interface for license management
|
- **License Management**: Full CRUD interface for license management
|
||||||
- **License Dashboard**: Statistics and analytics (WooCommerce > Reports > Licenses)
|
- **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
|
- **Search & Filtering**: Search by license key, domain, status, or product
|
||||||
- **Live Search**: AJAX-powered instant search results
|
- **Live Search**: AJAX-powered instant search results
|
||||||
- **Inline Editing**: Edit license status, expiry, and domain directly in the list
|
- **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
|
- **License Transfer**: Transfer licenses to new domains
|
||||||
- **CSV Export/Import**: Export and import licenses via CSV
|
- **CSV Export/Import**: Export and import licenses via CSV
|
||||||
- **Order Integration**: View and manage licenses directly from order pages
|
- **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
|
- **Expiration Warnings**: Automatic email notifications before license expiration
|
||||||
- **Auto-Expire**: Daily cron job automatically expires licenses past their expiration date
|
- **Auto-Expire**: Daily cron job automatically expires licenses past their expiration date
|
||||||
- **License Testing**: Test licenses against the API directly from admin interface
|
- **License Testing**: Test licenses against the API directly from admin interface
|
||||||
- **Version Management**: Manage multiple versions per product with file attachments
|
- **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
|
- **SHA256 Checksums**: File integrity verification with SHA256 hash display
|
||||||
- **Global Settings**: Default license settings via WooCommerce settings tab
|
- **Global Settings**: Default license settings via WooCommerce settings tab
|
||||||
- **WooCommerce HPOS**: Compatible with High-Performance Order Storage
|
- **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
|
### Creating a Licensed Product
|
||||||
|
|
||||||
1. Go to Products > Add New
|
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
|
3. Configure the product price in the General tab
|
||||||
4. Set license options in the "License Settings" tab:
|
4. Set license options in the "License Settings" tab:
|
||||||
- **Max Activations**: Number of domains allowed per license
|
- **Max Activations**: Number of domains allowed per license
|
||||||
- **License Validity**: Days until expiration (empty = lifetime)
|
- **License Validity**: Days until expiration (empty = lifetime)
|
||||||
- **Bind to Major Version**: Lock license to current major version
|
- **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
|
### Managing Product Versions
|
||||||
|
|
||||||
1. Edit a Licensed Product
|
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
|
1. Go to WooCommerce > Settings > Licensed Products
|
||||||
2. Set default values for Max Activations, License Validity, and Version Binding
|
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
|
### 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.
|
**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
|
## REST API
|
||||||
|
|
||||||
Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
|
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.
|
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
|
### Client Libraries & Examples
|
||||||
|
|
||||||
**PHP (Recommended):** Install the official client library via Composer:
|
**PHP (Recommended):** Install the official client library via Composer:
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ code.file-hash {
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* License Product Tab */
|
/* 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,
|
||||||
display: block !important;
|
#woocommerce-product-data .show_if_licensed-variable {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#woocommerce-product-data .hide_if_licensed {
|
#woocommerce-product-data .hide_if_licensed {
|
||||||
|
|||||||
4
composer.lock
generated
4
composer.lock
generated
@@ -12,7 +12,7 @@
|
|||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
|
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
|
||||||
"reference": "64d215cb265a64ff318cfbb954dd128b0076dc1d"
|
"reference": "5e4b5a970f75d0163c5496581d963a24ade4f276"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
|
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
|
||||||
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
|
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
|
||||||
},
|
},
|
||||||
"time": "2026-01-24T13:32:11+00:00"
|
"time": "2026-01-26T15:54:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
|
|||||||
Binary file not shown.
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: WC Licensed Product 0.5.0\n"
|
"Project-Id-Version: WC Licensed Product 0.5.0\n"
|
||||||
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
|
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
|
||||||
"POT-Creation-Date: 2026-01-26 16:08+0100\n"
|
"POT-Creation-Date: 2026-01-27 13:34+0100\n"
|
||||||
"PO-Revision-Date: 2026-01-25T18:30:00+00:00\n"
|
"PO-Revision-Date: 2026-01-25T18:30:00+00:00\n"
|
||||||
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
|
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
|
||||||
"Language-Team: German (Switzerland) <de_CH@li.org>\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/AdminController.php:1373 src/Admin/AdminController.php:1613
|
||||||
#: src/Admin/DashboardWidgetController.php:136
|
#: src/Admin/DashboardWidgetController.php:136
|
||||||
#: src/Admin/OrderLicenseController.php:260
|
#: src/Admin/OrderLicenseController.php:260
|
||||||
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:136
|
#: src/Admin/SettingsController.php:192
|
||||||
#: src/Product/LicensedProductType.php:184
|
|
||||||
#: src/Product/LicensedProductType.php:379
|
|
||||||
#: src/Product/LicensedProductVariation.php:139
|
#: src/Product/LicensedProductVariation.php:139
|
||||||
|
#: src/Product/LicensedProductType.php:136
|
||||||
|
#: src/Product/LicensedProductType.php:184
|
||||||
|
#: src/Product/LicensedProductType.php:403
|
||||||
#: src/Frontend/AccountController.php:286
|
#: src/Frontend/AccountController.php:286
|
||||||
msgid "Lifetime"
|
msgid "Lifetime"
|
||||||
msgstr "Lebenslang"
|
msgstr "Lebenslang"
|
||||||
@@ -1165,17 +1166,18 @@ msgstr ""
|
|||||||
"Diese Einstellungen dienen als Standard für neue lizensierte Produkte. "
|
"Diese Einstellungen dienen als Standard für neue lizensierte Produkte. "
|
||||||
"Individuelle Produkteinstellungen überschreiben diese Standards."
|
"Individuelle Produkteinstellungen überschreiben diese Standards."
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:176
|
#: src/Admin/SettingsController.php:176 src/Product/LicensedProductType.php:154
|
||||||
msgid "Default Max Activations"
|
#: src/Product/LicensedProductType.php:420
|
||||||
msgstr "Standard Max. Aktivierungen"
|
msgid "Max Activations"
|
||||||
|
msgstr "Max. Aktivierungen"
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:178
|
#: src/Admin/SettingsController.php:178
|
||||||
msgid "Default maximum number of domain activations per license."
|
msgid "Default maximum number of domain activations per license."
|
||||||
msgstr "Standard maximale Anzahl der Domain-Aktivierungen pro Lizenz."
|
msgstr "Standard maximale Anzahl der Domain-Aktivierungen pro Lizenz."
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:187
|
#: src/Admin/SettingsController.php:187 src/Product/LicensedProductType.php:172
|
||||||
msgid "Default License Validity (Days)"
|
msgid "License Validity (Days)"
|
||||||
msgstr "Standard Lizenz-Gültigkeit (Tage)"
|
msgstr "Lizenz-Gültigkeit (Tage)"
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:189
|
#: src/Admin/SettingsController.php:189
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -1185,9 +1187,9 @@ msgstr ""
|
|||||||
"Standard Anzahl Tage, die eine Lizenz gültig ist. Leer lassen oder auf 0 "
|
"Standard Anzahl Tage, die eine Lizenz gültig ist. Leer lassen oder auf 0 "
|
||||||
"setzen für lebenslange Lizenzen."
|
"setzen für lebenslange Lizenzen."
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:199
|
#: src/Admin/SettingsController.php:199 src/Product/LicensedProductType.php:190
|
||||||
msgid "Default Bind to Major Version"
|
msgid "Bind to Major Version"
|
||||||
msgstr "Standard An Hauptversion binden"
|
msgstr "An Hauptversion binden"
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:201
|
#: src/Admin/SettingsController.php:201
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -1282,32 +1284,32 @@ msgstr "Lizenz erfolgreich überprüft!"
|
|||||||
msgid "License validation failed."
|
msgid "License validation failed."
|
||||||
msgstr "Lizenzvalidierung fehlgeschlagen."
|
msgstr "Lizenzvalidierung fehlgeschlagen."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:84
|
#: src/Api/RestApiController.php:106
|
||||||
msgid "Too many requests. Please try again later."
|
msgid "Too many requests. Please try again later."
|
||||||
msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
|
msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:345 src/Api/RestApiController.php:378
|
#: src/Api/RestApiController.php:400 src/Api/RestApiController.php:433
|
||||||
#: src/License/LicenseManager.php:475
|
#: src/License/LicenseManager.php:475
|
||||||
msgid "License key not found."
|
msgid "License key not found."
|
||||||
msgstr "Lizenzschlüssel nicht gefunden."
|
msgstr "Lizenzschlüssel nicht gefunden."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:386
|
#: src/Api/RestApiController.php:441
|
||||||
msgid "This license is not valid."
|
msgid "This license is not valid."
|
||||||
msgstr "Diese Lizenz ist ungültig."
|
msgstr "Diese Lizenz ist ungültig."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:396
|
#: src/Api/RestApiController.php:451
|
||||||
msgid "License is already activated for this domain."
|
msgid "License is already activated for this domain."
|
||||||
msgstr "Die Lizenz ist bereits für diese Domain aktiviert."
|
msgstr "Die Lizenz ist bereits für diese Domain aktiviert."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:405
|
#: src/Api/RestApiController.php:460
|
||||||
msgid "Maximum number of activations reached."
|
msgid "Maximum number of activations reached."
|
||||||
msgstr "Maximale Anzahl der Aktivierungen erreicht."
|
msgstr "Maximale Anzahl der Aktivierungen erreicht."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:416
|
#: src/Api/RestApiController.php:471
|
||||||
msgid "Failed to activate license."
|
msgid "Failed to activate license."
|
||||||
msgstr "Lizenz konnte nicht aktiviert werden."
|
msgstr "Lizenz konnte nicht aktiviert werden."
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:422
|
#: src/Api/RestApiController.php:477
|
||||||
msgid "License activated successfully."
|
msgid "License activated successfully."
|
||||||
msgstr "Lizenz erfolgreich aktiviert."
|
msgstr "Lizenz erfolgreich aktiviert."
|
||||||
|
|
||||||
@@ -1432,97 +1434,6 @@ msgstr "Diese Lizenz ist inaktiv."
|
|||||||
msgid "This license is not valid for this domain."
|
msgid "This license is not valid for this domain."
|
||||||
msgstr "Diese Lizenz ist für diese Domain nicht gültig."
|
msgstr "Diese Lizenz ist für diese Domain nicht gültig."
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:72
|
|
||||||
msgid "Licensed Product"
|
|
||||||
msgstr "Lizensiertes Produkt"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:73
|
|
||||||
msgid "Licensed Variable Product"
|
|
||||||
msgstr "Lizensiertes variables Produkt"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:108
|
|
||||||
msgid "License Settings"
|
|
||||||
msgstr "Lizenz-Einstellungen"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:135
|
|
||||||
#: src/Product/LicensedProductType.php:378
|
|
||||||
#, php-format
|
|
||||||
msgid "%d days"
|
|
||||||
msgstr "%d Tage"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:145
|
|
||||||
#, 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
|
|
||||||
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
|
|
||||||
#, 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
|
|
||||||
#, 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
|
|
||||||
#, php-format
|
|
||||||
msgid ""
|
|
||||||
"If enabled, licenses are bound to the major version at purchase time. "
|
|
||||||
"Default: %s"
|
|
||||||
msgstr ""
|
|
||||||
"Falls aktiviert, werden Lizenzen an die Hauptversion zum Kaufzeitpunkt "
|
|
||||||
"gebunden. Standard: %s"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:194
|
|
||||||
msgid "Yes"
|
|
||||||
msgstr "Ja"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:194
|
|
||||||
msgid "No"
|
|
||||||
msgstr "Nein"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:321
|
|
||||||
msgid "Version:"
|
|
||||||
msgstr "Version:"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:349
|
|
||||||
msgid "Licensed products are always virtual"
|
|
||||||
msgstr "Lizenzierte Produkte sind immer virtuell"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:351
|
|
||||||
msgid "Virtual"
|
|
||||||
msgstr "Virtuell"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:384
|
|
||||||
msgid "License Duration (Days)"
|
|
||||||
msgstr "Lizenz-Gültigkeit (Tage)"
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:393
|
|
||||||
msgid "Leave empty for parent default. 0 = Lifetime."
|
|
||||||
msgstr "Leer lassen für übergeordneten Standard. 0 = Lebenslang."
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:405
|
|
||||||
msgid "Leave empty for parent default."
|
|
||||||
msgstr "Leer lassen für übergeordneten Standard."
|
|
||||||
|
|
||||||
#: src/Product/VersionManager.php:166
|
#: src/Product/VersionManager.php:166
|
||||||
msgid "Attachment file not found."
|
msgid "Attachment file not found."
|
||||||
msgstr "Anhangs-Datei nicht gefunden."
|
msgstr "Anhangs-Datei nicht gefunden."
|
||||||
@@ -1551,6 +1462,84 @@ msgid_plural "%d days"
|
|||||||
msgstr[0] "%d Tag"
|
msgstr[0] "%d Tag"
|
||||||
msgstr[1] "%d Tage"
|
msgstr[1] "%d Tage"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:72
|
||||||
|
msgid "Licensed Product"
|
||||||
|
msgstr "Lizensiertes Produkt"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:73
|
||||||
|
msgid "Licensed Variable Product"
|
||||||
|
msgstr "Lizensiertes variables Produkt"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:108
|
||||||
|
msgid "License Settings"
|
||||||
|
msgstr "Lizenz-Einstellungen"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:135
|
||||||
|
#: src/Product/LicensedProductType.php:402
|
||||||
|
#, php-format
|
||||||
|
msgid "%d days"
|
||||||
|
msgstr "%d Tage"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:145
|
||||||
|
#, 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
|
||||||
|
msgid "WooCommerce > Settings > Licensed Products"
|
||||||
|
msgstr "WooCommerce > Einstellungen > Lizensierte Produkte"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:157
|
||||||
|
#, 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:175
|
||||||
|
#, 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:193
|
||||||
|
#, php-format
|
||||||
|
msgid ""
|
||||||
|
"If enabled, licenses are bound to the major version at purchase time. "
|
||||||
|
"Default: %s"
|
||||||
|
msgstr ""
|
||||||
|
"Falls aktiviert, werden Lizenzen an die Hauptversion zum Kaufzeitpunkt "
|
||||||
|
"gebunden. Standard: %s"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:194
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Ja"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:194
|
||||||
|
msgid "No"
|
||||||
|
msgstr "Nein"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:329
|
||||||
|
msgid "Version:"
|
||||||
|
msgstr "Version:"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:373
|
||||||
|
msgid "Licensed products are always virtual"
|
||||||
|
msgstr "Lizenzierte Produkte sind immer virtuell"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:375
|
||||||
|
msgid "Virtual"
|
||||||
|
msgstr "Virtuell"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:408
|
||||||
|
msgid "License Duration (Days)"
|
||||||
|
msgstr "Lizenz-Gültigkeit (Tage)"
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:417
|
||||||
|
msgid "Leave empty for parent default. 0 = Lifetime."
|
||||||
|
msgstr "Leer lassen für übergeordneten Standard. 0 = Lebenslang."
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:429
|
||||||
|
msgid "Leave empty for parent default."
|
||||||
|
msgstr "Leer lassen für übergeordneten Standard."
|
||||||
|
|
||||||
#: src/Frontend/DownloadController.php:77
|
#: src/Frontend/DownloadController.php:77
|
||||||
#: src/Frontend/DownloadController.php:101
|
#: src/Frontend/DownloadController.php:101
|
||||||
msgid "Invalid download link."
|
msgid "Invalid download link."
|
||||||
@@ -1916,6 +1905,15 @@ msgstr ""
|
|||||||
"WC Licensed Product benötigt WooCommerce als installierte und aktivierte "
|
"WC Licensed Product benötigt WooCommerce als installierte und aktivierte "
|
||||||
"Erweiterung."
|
"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"
|
#~ msgid "API Verification Secret"
|
||||||
#~ msgstr "API-Verifizierungs-Secret"
|
#~ msgstr "API-Verifizierungs-Secret"
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: WC Licensed Product 0.5.3\n"
|
"Project-Id-Version: WC Licensed Product 0.5.8\n"
|
||||||
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
|
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
|
||||||
"POT-Creation-Date: 2026-01-26 16:08+0100\n"
|
"POT-Creation-Date: 2026-01-27 13:34+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\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/AdminController.php:1373 src/Admin/AdminController.php:1613
|
||||||
#: src/Admin/DashboardWidgetController.php:136
|
#: src/Admin/DashboardWidgetController.php:136
|
||||||
#: src/Admin/OrderLicenseController.php:260
|
#: src/Admin/OrderLicenseController.php:260
|
||||||
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:136
|
#: src/Admin/SettingsController.php:192
|
||||||
#: src/Product/LicensedProductType.php:184
|
|
||||||
#: src/Product/LicensedProductType.php:379
|
|
||||||
#: src/Product/LicensedProductVariation.php:139
|
#: src/Product/LicensedProductVariation.php:139
|
||||||
|
#: src/Product/LicensedProductType.php:136
|
||||||
|
#: src/Product/LicensedProductType.php:184
|
||||||
|
#: src/Product/LicensedProductType.php:403
|
||||||
#: src/Frontend/AccountController.php:286
|
#: src/Frontend/AccountController.php:286
|
||||||
msgid "Lifetime"
|
msgid "Lifetime"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1132,16 +1133,17 @@ msgid ""
|
|||||||
"product settings override these defaults."
|
"product settings override these defaults."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:176
|
#: src/Admin/SettingsController.php:176 src/Product/LicensedProductType.php:154
|
||||||
msgid "Default Max Activations"
|
#: src/Product/LicensedProductType.php:420
|
||||||
|
msgid "Max Activations"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:178
|
#: src/Admin/SettingsController.php:178
|
||||||
msgid "Default maximum number of domain activations per license."
|
msgid "Default maximum number of domain activations per license."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:187
|
#: src/Admin/SettingsController.php:187 src/Product/LicensedProductType.php:172
|
||||||
msgid "Default License Validity (Days)"
|
msgid "License Validity (Days)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:189
|
#: src/Admin/SettingsController.php:189
|
||||||
@@ -1150,8 +1152,8 @@ msgid ""
|
|||||||
"lifetime licenses."
|
"lifetime licenses."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:199
|
#: src/Admin/SettingsController.php:199 src/Product/LicensedProductType.php:190
|
||||||
msgid "Default Bind to Major Version"
|
msgid "Bind to Major Version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Admin/SettingsController.php:201
|
#: src/Admin/SettingsController.php:201
|
||||||
@@ -1238,32 +1240,32 @@ msgstr ""
|
|||||||
msgid "License validation failed."
|
msgid "License validation failed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:84
|
#: src/Api/RestApiController.php:106
|
||||||
msgid "Too many requests. Please try again later."
|
msgid "Too many requests. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:345 src/Api/RestApiController.php:378
|
#: src/Api/RestApiController.php:400 src/Api/RestApiController.php:433
|
||||||
#: src/License/LicenseManager.php:475
|
#: src/License/LicenseManager.php:475
|
||||||
msgid "License key not found."
|
msgid "License key not found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:386
|
#: src/Api/RestApiController.php:441
|
||||||
msgid "This license is not valid."
|
msgid "This license is not valid."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:396
|
#: src/Api/RestApiController.php:451
|
||||||
msgid "License is already activated for this domain."
|
msgid "License is already activated for this domain."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:405
|
#: src/Api/RestApiController.php:460
|
||||||
msgid "Maximum number of activations reached."
|
msgid "Maximum number of activations reached."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:416
|
#: src/Api/RestApiController.php:471
|
||||||
msgid "Failed to activate license."
|
msgid "Failed to activate license."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Api/RestApiController.php:422
|
#: src/Api/RestApiController.php:477
|
||||||
msgid "License activated successfully."
|
msgid "License activated successfully."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1383,95 +1385,6 @@ msgstr ""
|
|||||||
msgid "This license is not valid for this domain."
|
msgid "This license is not valid for this domain."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:72
|
|
||||||
msgid "Licensed Product"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:73
|
|
||||||
msgid "Licensed Variable Product"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:108
|
|
||||||
msgid "License Settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:135
|
|
||||||
#: src/Product/LicensedProductType.php:378
|
|
||||||
#, php-format
|
|
||||||
msgid "%d days"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:145
|
|
||||||
#, php-format
|
|
||||||
msgid "Leave fields empty to use default settings from %s."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:147
|
|
||||||
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
|
|
||||||
#, 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
|
|
||||||
#, 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
|
|
||||||
#, php-format
|
|
||||||
msgid ""
|
|
||||||
"If enabled, licenses are bound to the major version at purchase time. "
|
|
||||||
"Default: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:194
|
|
||||||
msgid "Yes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:194
|
|
||||||
msgid "No"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:321
|
|
||||||
msgid "Version:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:349
|
|
||||||
msgid "Licensed products are always virtual"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:351
|
|
||||||
msgid "Virtual"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:384
|
|
||||||
msgid "License Duration (Days)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:393
|
|
||||||
msgid "Leave empty for parent default. 0 = Lifetime."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/LicensedProductType.php:405
|
|
||||||
msgid "Leave empty for parent default."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Product/VersionManager.php:166
|
#: src/Product/VersionManager.php:166
|
||||||
msgid "Attachment file not found."
|
msgid "Attachment file not found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1500,6 +1413,82 @@ msgid_plural "%d days"
|
|||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:72
|
||||||
|
msgid "Licensed Product"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:73
|
||||||
|
msgid "Licensed Variable Product"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:108
|
||||||
|
msgid "License Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:135
|
||||||
|
#: src/Product/LicensedProductType.php:402
|
||||||
|
#, php-format
|
||||||
|
msgid "%d days"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:145
|
||||||
|
#, php-format
|
||||||
|
msgid "Leave fields empty to use default settings from %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:147
|
||||||
|
msgid "WooCommerce > Settings > Licensed Products"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:157
|
||||||
|
#, php-format
|
||||||
|
msgid "Maximum number of domain activations per license. Default: %d"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:175
|
||||||
|
#, php-format
|
||||||
|
msgid "Number of days the license is valid. Leave empty for default (%s)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:193
|
||||||
|
#, php-format
|
||||||
|
msgid ""
|
||||||
|
"If enabled, licenses are bound to the major version at purchase time. "
|
||||||
|
"Default: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:194
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:194
|
||||||
|
msgid "No"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:329
|
||||||
|
msgid "Version:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:373
|
||||||
|
msgid "Licensed products are always virtual"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:375
|
||||||
|
msgid "Virtual"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:408
|
||||||
|
msgid "License Duration (Days)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:417
|
||||||
|
msgid "Leave empty for parent default. 0 = Lifetime."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Product/LicensedProductType.php:429
|
||||||
|
msgid "Leave empty for parent default."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/Frontend/DownloadController.php:77
|
#: src/Frontend/DownloadController.php:77
|
||||||
#: src/Frontend/DownloadController.php:101
|
#: src/Frontend/DownloadController.php:101
|
||||||
msgid "Invalid download link."
|
msgid "Invalid download link."
|
||||||
|
|||||||
BIN
releases/wc-licensed-product-0.5.3.zip
Normal file
BIN
releases/wc-licensed-product-0.5.3.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.5.3.zip.sha256
Normal file
1
releases/wc-licensed-product-0.5.3.zip.sha256
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bbd0fa8888c6990a4ba00ccfb8b2189ee6ac529a34cc11a5d8d8d28518b1f6dd wc-licensed-product-0.5.3.zip
|
||||||
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
|
||||||
@@ -173,7 +173,7 @@ final class SettingsController
|
|||||||
'id' => 'wc_licensed_product_section_defaults',
|
'id' => 'wc_licensed_product_section_defaults',
|
||||||
],
|
],
|
||||||
'default_max_activations' => [
|
'default_max_activations' => [
|
||||||
'name' => __('Default Max Activations', 'wc-licensed-product'),
|
'name' => __('Max Activations', 'wc-licensed-product'),
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'desc' => __('Default maximum number of domain activations per license.', 'wc-licensed-product'),
|
'desc' => __('Default maximum number of domain activations per license.', 'wc-licensed-product'),
|
||||||
'id' => 'wc_licensed_product_default_max_activations',
|
'id' => 'wc_licensed_product_default_max_activations',
|
||||||
@@ -184,7 +184,7 @@ final class SettingsController
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'default_validity_days' => [
|
'default_validity_days' => [
|
||||||
'name' => __('Default License Validity (Days)', 'wc-licensed-product'),
|
'name' => __('License Validity (Days)', 'wc-licensed-product'),
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'desc' => __('Default number of days a license is valid. Leave empty or set to 0 for lifetime licenses.', 'wc-licensed-product'),
|
'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',
|
'id' => 'wc_licensed_product_default_validity_days',
|
||||||
@@ -196,7 +196,7 @@ final class SettingsController
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'default_bind_to_version' => [
|
'default_bind_to_version' => [
|
||||||
'name' => __('Default Bind to Major Version', 'wc-licensed-product'),
|
'name' => __('Bind to Major Version', 'wc-licensed-product'),
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'desc' => __('If enabled, licenses are bound to the major version at purchase time by default.', 'wc-licensed-product'),
|
'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',
|
'id' => 'wc_licensed_product_default_bind_to_version',
|
||||||
|
|||||||
@@ -157,16 +157,23 @@ final class ResponseSigner
|
|||||||
* to verify signed API responses. Each customer gets their own secret
|
* to verify signed API responses. Each customer gets their own secret
|
||||||
* derived from their license key.
|
* derived from their license key.
|
||||||
*
|
*
|
||||||
|
* Uses RFC 5869 HKDF via PHP's native hash_hkdf() function.
|
||||||
|
* Parameters match the client library (SecureLicenseClient):
|
||||||
|
* - IKM (input keying material): server_secret
|
||||||
|
* - Length: 32 bytes (256 bits for SHA-256)
|
||||||
|
* - Info: license_key (context-specific info)
|
||||||
|
*
|
||||||
* @param string $licenseKey The customer's license key
|
* @param string $licenseKey The customer's license key
|
||||||
* @param string $serverSecret The server's master secret
|
* @param string $serverSecret The server's master secret
|
||||||
* @return string The derived secret (64 hex characters)
|
* @return string The derived secret (64 hex characters)
|
||||||
*/
|
*/
|
||||||
public static function deriveCustomerSecret(string $licenseKey, string $serverSecret): string
|
public static function deriveCustomerSecret(string $licenseKey, string $serverSecret): string
|
||||||
{
|
{
|
||||||
// HKDF-like key derivation
|
// RFC 5869 HKDF using PHP's native implementation
|
||||||
$prk = hash_hmac('sha256', $licenseKey, $serverSecret, true);
|
// Must match client's ResponseSignature::deriveKey() exactly
|
||||||
|
$binaryKey = hash_hkdf('sha256', $serverSecret, 32, $licenseKey);
|
||||||
|
|
||||||
return hash_hmac('sha256', $prk . "\x01", $serverSecret);
|
return bin2hex($binaryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,14 +22,34 @@ final class RestApiController
|
|||||||
private const NAMESPACE = 'wc-licensed-product/v1';
|
private const NAMESPACE = 'wc-licensed-product/v1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rate limit: requests per minute per IP
|
* Default rate limit: requests per window per IP
|
||||||
*/
|
*/
|
||||||
private const RATE_LIMIT_REQUESTS = 30;
|
private const DEFAULT_RATE_LIMIT = 30;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rate limit window in seconds
|
* Default rate limit window in seconds
|
||||||
*/
|
*/
|
||||||
private const RATE_LIMIT_WINDOW = 60;
|
private const DEFAULT_RATE_WINDOW = 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured rate limit (requests per window)
|
||||||
|
*/
|
||||||
|
private function getRateLimit(): int
|
||||||
|
{
|
||||||
|
return defined('WC_LICENSE_RATE_LIMIT')
|
||||||
|
? (int) WC_LICENSE_RATE_LIMIT
|
||||||
|
: self::DEFAULT_RATE_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured rate limit window in seconds
|
||||||
|
*/
|
||||||
|
private function getRateWindow(): int
|
||||||
|
{
|
||||||
|
return defined('WC_LICENSE_RATE_WINDOW')
|
||||||
|
? (int) WC_LICENSE_RATE_WINDOW
|
||||||
|
: self::DEFAULT_RATE_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
private LicenseManager $licenseManager;
|
private LicenseManager $licenseManager;
|
||||||
|
|
||||||
@@ -56,12 +76,14 @@ final class RestApiController
|
|||||||
{
|
{
|
||||||
$ip = $this->getClientIp();
|
$ip = $this->getClientIp();
|
||||||
$transientKey = 'wclp_rate_' . md5($ip);
|
$transientKey = 'wclp_rate_' . md5($ip);
|
||||||
|
$rateLimit = $this->getRateLimit();
|
||||||
|
$rateWindow = $this->getRateWindow();
|
||||||
|
|
||||||
$data = get_transient($transientKey);
|
$data = get_transient($transientKey);
|
||||||
|
|
||||||
if ($data === false) {
|
if ($data === false) {
|
||||||
// First request, start counting
|
// First request, start counting
|
||||||
set_transient($transientKey, ['count' => 1, 'start' => time()], self::RATE_LIMIT_WINDOW);
|
set_transient($transientKey, ['count' => 1, 'start' => time()], $rateWindow);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,15 +91,15 @@ final class RestApiController
|
|||||||
$start = (int) ($data['start'] ?? time());
|
$start = (int) ($data['start'] ?? time());
|
||||||
|
|
||||||
// Check if window has expired
|
// Check if window has expired
|
||||||
if (time() - $start >= self::RATE_LIMIT_WINDOW) {
|
if (time() - $start >= $rateWindow) {
|
||||||
// Reset counter
|
// Reset counter
|
||||||
set_transient($transientKey, ['count' => 1, 'start' => time()], self::RATE_LIMIT_WINDOW);
|
set_transient($transientKey, ['count' => 1, 'start' => time()], $rateWindow);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if limit exceeded
|
// Check if limit exceeded
|
||||||
if ($count >= self::RATE_LIMIT_REQUESTS) {
|
if ($count >= $rateLimit) {
|
||||||
$retryAfter = self::RATE_LIMIT_WINDOW - (time() - $start);
|
$retryAfter = $rateWindow - (time() - $start);
|
||||||
$response = new WP_REST_Response([
|
$response = new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'rate_limit_exceeded',
|
'error' => 'rate_limit_exceeded',
|
||||||
@@ -89,7 +111,7 @@ final class RestApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Increment counter
|
// Increment counter
|
||||||
set_transient($transientKey, ['count' => $count + 1, 'start' => $start], self::RATE_LIMIT_WINDOW);
|
set_transient($transientKey, ['count' => $count + 1, 'start' => $start], $rateWindow);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +279,8 @@ final class RestApiController
|
|||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'sanitize_callback' => 'sanitize_text_field',
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
'validate_callback' => function ($value): bool {
|
'validate_callback' => function ($value): bool {
|
||||||
return !empty($value) && strlen($value) <= 64;
|
$len = strlen($value);
|
||||||
|
return !empty($value) && $len >= 8 && $len <= 64;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'domain' => [
|
'domain' => [
|
||||||
@@ -281,6 +304,10 @@ final class RestApiController
|
|||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'sanitize_callback' => 'sanitize_text_field',
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
'validate_callback' => function ($value): bool {
|
||||||
|
$len = strlen($value);
|
||||||
|
return !empty($value) && $len >= 8 && $len <= 64;
|
||||||
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -295,11 +322,18 @@ final class RestApiController
|
|||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'sanitize_callback' => 'sanitize_text_field',
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
'validate_callback' => function ($value): bool {
|
||||||
|
$len = strlen($value);
|
||||||
|
return !empty($value) && $len >= 8 && $len <= 64;
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'sanitize_callback' => 'sanitize_text_field',
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
'validate_callback' => function ($value): bool {
|
||||||
|
return !empty($value) && strlen($value) <= 255;
|
||||||
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -320,11 +354,32 @@ final class RestApiController
|
|||||||
|
|
||||||
$result = $this->licenseManager->validateLicense($licenseKey, $domain);
|
$result = $this->licenseManager->validateLicense($licenseKey, $domain);
|
||||||
|
|
||||||
$statusCode = $result['valid'] ? 200 : 403;
|
$statusCode = $this->getStatusCodeForResult($result);
|
||||||
|
|
||||||
return new WP_REST_Response($result, $statusCode);
|
return new WP_REST_Response($result, $statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTTP status code based on validation result
|
||||||
|
*
|
||||||
|
* @param array $result The validation result
|
||||||
|
* @return int HTTP status code
|
||||||
|
*/
|
||||||
|
private function getStatusCodeForResult(array $result): int
|
||||||
|
{
|
||||||
|
if ($result['valid']) {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = $result['error'] ?? '';
|
||||||
|
|
||||||
|
return match ($error) {
|
||||||
|
'license_not_found' => 404,
|
||||||
|
'activation_failed' => 500,
|
||||||
|
default => 403,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check license status endpoint
|
* Check license status endpoint
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -201,23 +201,31 @@ final class LicensedProductType
|
|||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
jQuery(document).ready(function($) {
|
jQuery(document).ready(function($) {
|
||||||
// Show/hide panels based on product type
|
// Show/hide panels based on product type for license settings tab
|
||||||
$('select#product-type').change(function() {
|
function toggleLicensedProductOptions() {
|
||||||
if ($(this).val() === 'licensed') {
|
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').show();
|
||||||
|
$('.show_if_licensed-variable').show();
|
||||||
$('.general_options').show();
|
$('.general_options').show();
|
||||||
$('.pricing').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();
|
$('.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>
|
</script>
|
||||||
<?php
|
<?php
|
||||||
@@ -328,7 +336,49 @@ final class LicensedProductType
|
|||||||
*/
|
*/
|
||||||
public function variableAddToCartTemplate(): void
|
public function variableAddToCartTemplate(): void
|
||||||
{
|
{
|
||||||
wc_get_template('single-product/add-to-cart/variable.php');
|
global $product;
|
||||||
|
|
||||||
|
// Check if product is a variable type (includes LicensedVariableProduct which extends WC_Product_Variable)
|
||||||
|
if (!$product || !$product->is_type('licensed-variable')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get attributes - ensure we have an array even if no attributes are set
|
||||||
|
$attributes = $product->get_variation_attributes();
|
||||||
|
if (!is_array($attributes)) {
|
||||||
|
$attributes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no attributes defined, show a message instead of broken form
|
||||||
|
if (empty($attributes)) {
|
||||||
|
echo '<p class="woocommerce-info">' . esc_html__('This product has no variations available.', 'wc-licensed-product') . '</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get variations count to determine if we should load them via AJAX
|
||||||
|
$children = $product->get_children();
|
||||||
|
$getVariations = count($children) <= apply_filters('woocommerce_ajax_variation_threshold', 30, $product);
|
||||||
|
|
||||||
|
// Get available variations - ensure we have an array
|
||||||
|
$availableVariations = $getVariations ? $product->get_available_variations() : false;
|
||||||
|
if ($getVariations && !is_array($availableVariations)) {
|
||||||
|
$availableVariations = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get default/selected attributes - ensure we have an array
|
||||||
|
$selectedAttributes = $product->get_default_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 +551,13 @@ final class LicensedProductType
|
|||||||
// Show general and variations tabs
|
// Show general and variations tabs
|
||||||
$('.general_tab').show();
|
$('.general_tab').show();
|
||||||
$('.variations_tab').show();
|
$('.variations_tab').show();
|
||||||
|
$('.variations_options').show();
|
||||||
|
|
||||||
// Hide shipping tab (virtual products)
|
// Hide shipping tab (virtual products)
|
||||||
$('.shipping_tab').hide();
|
$('.shipping_tab').hide();
|
||||||
|
|
||||||
|
// Ensure the variations panel can be displayed
|
||||||
|
$('#variable_product_options').show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,8 +566,29 @@ final class LicensedProductType
|
|||||||
|
|
||||||
// On product type change
|
// On product type change
|
||||||
$('#product-type').on('change', function() {
|
$('#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>
|
</script>
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WooCommerce Licensed Product
|
* Plugin Name: WooCommerce Licensed Product
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-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.
|
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
|
||||||
* Version: 0.5.3
|
* Version: 0.5.9
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
* License: GPL-2.0-or-later
|
* License: GPL-2.0-or-later
|
||||||
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plugin constants
|
// Plugin constants
|
||||||
define('WC_LICENSED_PRODUCT_VERSION', '0.5.3');
|
define('WC_LICENSED_PRODUCT_VERSION', '0.5.9');
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user