1 Commits

Author SHA1 Message Date
35d802c2b8 Security improvements and API compatibility fixes (v0.3.6)
- Add recursive key sorting for response signing compatibility
- Fix IP header spoofing in rate limiting with trusted proxy support
- Add CSRF protection to CSV export with nonce verification
- Explicit Twig autoescape for XSS prevention
- Escape status values in CSS classes
- Update README with security documentation and trusted proxy config
- Update translations for v0.3.6

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:18:32 +01:00
11 changed files with 669 additions and 392 deletions

View File

@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.3.6] - 2026-01-23
### Security
- Added CSRF protection (nonce verification) to CSV export functionality
- Fixed IP header spoofing vulnerability in rate limiting - now requires explicit trusted proxy configuration
- Enabled explicit Twig autoescape for XSS protection
- Fixed unescaped status values in CSS classes in Twig templates
### Fixed
- Fixed response signing to use recursive key sorting for client compatibility
- ResponseSigner now recursively sorts nested array keys alphabetically as required by client implementation
### Changed
- Rate limiting now only trusts proxy headers when `WC_LICENSE_TRUSTED_PROXIES` constant is defined
- Added Cloudflare IP range support via `WC_LICENSE_TRUSTED_PROXIES = 'CLOUDFLARE'` configuration
- Improved IP detection with CIDR notation support for trusted proxy ranges
### Technical Details
- Added `recursiveKeySort()` method to `ResponseSigner` for proper response signing
- Added `isTrustedProxy()`, `isCloudflareIp()`, and `ipMatchesCidr()` methods to `RestApiController`
- Twig environment now explicitly sets `autoescape => 'html'`
- Export CSV link now includes nonce via `wp_nonce_url()`
- Added `export_csv_url()` Twig function for generating export URL with nonce
## [0.3.5] - 2026-01-23
### Added

View File

@@ -14,10 +14,13 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
- **Automatic License Generation**: License keys generated on order completion (format: XXXX-XXXX-XXXX-XXXX)
- **Domain Binding**: Licenses are bound to customer-specified domains
- **REST API**: Public endpoints for license validation and management
- **Response Signing**: Optional HMAC-SHA256 cryptographic signatures for API responses
- **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)
- **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
@@ -30,6 +33,7 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
- **License Management**: Full CRUD interface for license management
- **License Dashboard**: Statistics and analytics (WooCommerce > Reports > Licenses)
- **Dashboard Widget**: License 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
@@ -38,7 +42,10 @@ WC Licensed Product adds a new product type "Licensed Product" to WooCommerce, e
- **CSV Export/Import**: Export and import licenses via CSV
- **Order Integration**: View and manage licenses directly from order pages
- **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
- **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
@@ -103,6 +110,40 @@ When a customer purchases a licensed product, they must enter the domain where t
3. Upload a CSV file (supports exported format or simplified format)
4. Choose options: skip header row, update existing licenses
## Security
The plugin implements several security best practices:
- **Input Sanitization**: All user inputs are sanitized using WordPress functions
- **Output Escaping**: All output is escaped to prevent XSS attacks
- **CSRF Protection**: Nonce verification on all forms and AJAX requests
- **SQL Injection Prevention**: All database queries use prepared statements
- **Capability Checks**: Admin functions require `manage_woocommerce` capability
- **Secure Downloads**: File downloads use hash-verified URLs with user authentication
- **Response Signing**: Optional HMAC-SHA256 signatures for API tamper protection
### Trusted Proxy Configuration
If your server is behind a load balancer, reverse proxy, or CDN (like Cloudflare), you need to configure trusted proxies for accurate rate limiting. Without this, the rate limiter uses the direct connection IP which may be your proxy's IP.
**Configuration (wp-config.php):**
```php
// For Cloudflare (includes all Cloudflare IP ranges)
define('WC_LICENSE_TRUSTED_PROXIES', 'CLOUDFLARE');
// For specific proxy IPs
define('WC_LICENSE_TRUSTED_PROXIES', '10.0.0.1,10.0.0.2');
// For CIDR ranges
define('WC_LICENSE_TRUSTED_PROXIES', '10.0.0.0/8,192.168.1.0/24');
// Combine multiple methods
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.
## REST API
Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
@@ -117,6 +158,12 @@ When the server is configured with a shared secret, all API responses include cr
define('WC_LICENSE_SERVER_SECRET', 'your-secure-random-string-min-32-chars');
```
Generate a secure secret using:
```bash
openssl rand -hex 32
```
**Response Headers:**
| Header | Description |
@@ -256,11 +303,12 @@ Content-Type: application/json
## Email Notifications
The plugin sends automatic email notifications:
The plugin sends automatic email notifications (configurable via WooCommerce > Settings > Emails):
- **Order Completion**: License keys included in order confirmation emails
- **Expiration Warning (7 days)**: Reminder sent 7 days before expiration
- **Expiration Warning (1 day)**: Urgent reminder sent 1 day before expiration
- **License Expired**: Notification when a license auto-expires
## Changelog

View File

@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: WC Licensed Product 0.3.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-23 16:05+0100\n"
"POT-Creation-Date: 2026-01-23 21:09+0100\n"
"PO-Revision-Date: 2026-01-22T17:15:00+00:00\n"
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
"Language-Team: German (Switzerland) <de_CH@li.org>\n"
@@ -48,15 +48,15 @@ msgstr "beispiel.ch"
#: src/Admin/OrderLicenseController.php:118
#: src/Admin/OrderLicenseController.php:182 src/Admin/AdminController.php:150
#: src/Admin/AdminController.php:1329 src/Admin/AdminController.php:1349
#: src/Admin/AdminController.php:1370
#: src/Admin/AdminController.php:1340 src/Admin/AdminController.php:1360
#: src/Admin/AdminController.php:1381
msgid "Save"
msgstr "Speichern"
#: src/Admin/OrderLicenseController.php:127
#: src/Admin/OrderLicenseController.php:222 src/Admin/AdminController.php:76
#: src/Admin/AdminController.php:77 src/Admin/AdminController.php:90
#: src/Admin/AdminController.php:1189 src/Frontend/AccountController.php:90
#: src/Admin/AdminController.php:1200 src/Frontend/AccountController.php:90
msgid "Licenses"
msgstr "Lizenzen"
@@ -80,41 +80,41 @@ msgstr ""
"markiert wird."
#: src/Admin/OrderLicenseController.php:144
#: src/Admin/SettingsController.php:142 src/Admin/AdminController.php:1280
#: src/Admin/AdminController.php:1431 src/Admin/AdminController.php:1480
#: src/Admin/SettingsController.php:142 src/Admin/AdminController.php:1291
#: src/Admin/AdminController.php:1442 src/Admin/AdminController.php:1491
#: src/Email/LicenseEmailController.php:269
msgid "License Key"
msgstr "Lizenzschlüssel"
#: src/Admin/OrderLicenseController.php:145 src/Admin/AdminController.php:1281
#: src/Admin/AdminController.php:1432 src/Admin/AdminController.php:1597
#: src/Admin/OrderLicenseController.php:145 src/Admin/AdminController.php:1292
#: src/Admin/AdminController.php:1443 src/Admin/AdminController.php:1608
#: src/Email/LicenseEmailController.php:268
msgid "Product"
msgstr "Produkt"
#: src/Admin/OrderLicenseController.php:146 src/Admin/AdminController.php:1283
#: src/Admin/AdminController.php:1434 src/Admin/AdminController.php:1484
#: src/Admin/OrderLicenseController.php:146 src/Admin/AdminController.php:1294
#: src/Admin/AdminController.php:1445 src/Admin/AdminController.php:1495
msgid "Domain"
msgstr "Domain"
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1284
#: src/Admin/AdminController.php:1435 src/Admin/VersionAdminController.php:140
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1295
#: src/Admin/AdminController.php:1446 src/Admin/VersionAdminController.php:140
msgid "Status"
msgstr "Status"
#: src/Admin/OrderLicenseController.php:148 src/Admin/AdminController.php:1286
#: src/Admin/AdminController.php:1437 src/Admin/AdminController.php:1600
#: src/Admin/AdminController.php:1602 src/Email/LicenseEmailController.php:270
#: src/Admin/OrderLicenseController.php:148 src/Admin/AdminController.php:1297
#: src/Admin/AdminController.php:1448 src/Admin/AdminController.php:1611
#: src/Admin/AdminController.php:1613 src/Email/LicenseEmailController.php:270
msgid "Expires"
msgstr "Läuft ab"
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1287
#: src/Admin/AdminController.php:1438 src/Admin/VersionAdminController.php:142
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1298
#: src/Admin/AdminController.php:1449 src/Admin/VersionAdminController.php:142
msgid "Actions"
msgstr "Aktionen"
#: src/Admin/OrderLicenseController.php:168 src/Admin/AdminController.php:195
#: src/Admin/AdminController.php:1014
#: src/Admin/AdminController.php:1019
msgid "Unknown"
msgstr "Unbekannt"
@@ -123,16 +123,16 @@ msgid "Edit domain"
msgstr "Domain bearbeiten"
#: src/Admin/OrderLicenseController.php:185 src/Admin/AdminController.php:149
#: src/Admin/AdminController.php:1330 src/Admin/AdminController.php:1350
#: src/Admin/AdminController.php:1371 src/Admin/AdminController.php:1526
#: src/Admin/AdminController.php:1341 src/Admin/AdminController.php:1361
#: src/Admin/AdminController.php:1382 src/Admin/AdminController.php:1537
#: src/Frontend/AccountController.php:271
msgid "Cancel"
msgstr "Abbrechen"
#: src/Admin/OrderLicenseController.php:201
#: src/Admin/SettingsController.php:192 src/Admin/AdminController.php:151
#: src/Admin/AdminController.php:266 src/Admin/AdminController.php:1362
#: src/Admin/AdminController.php:1602 src/Product/LicensedProductType.php:110
#: src/Admin/AdminController.php:266 src/Admin/AdminController.php:1373
#: src/Admin/AdminController.php:1613 src/Product/LicensedProductType.php:110
#: src/Product/LicensedProductType.php:158
msgid "Lifetime"
msgstr "Lebenslang"
@@ -141,6 +141,7 @@ msgstr "Lebenslang"
msgid "View in Licenses"
msgstr "In Lizenzen anzeigen"
#. translators: %s: Link to licenses page
#: src/Admin/OrderLicenseController.php:221
#, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page."
@@ -309,6 +310,7 @@ msgstr ""
msgid "Expiration Warning Schedule"
msgstr "Ablaufwarnung Zeitplan"
#. translators: %s: URL to WooCommerce email settings
#: src/Admin/SettingsController.php:223
#, php-format
msgid ""
@@ -369,8 +371,9 @@ msgstr "Anfrage fehlgeschlagen."
#: src/Admin/SettingsController.php:454 src/Admin/AdminController.php:455
#: src/Admin/AdminController.php:475 src/Admin/AdminController.php:493
#: src/Admin/AdminController.php:511 src/Admin/AdminController.php:531
#: src/Admin/AdminController.php:549 src/Admin/AdminController.php:616
#: src/Admin/AdminController.php:806 src/Frontend/AccountController.php:326
#: src/Admin/AdminController.php:549 src/Admin/AdminController.php:577
#: src/Admin/AdminController.php:621 src/Admin/AdminController.php:811
#: src/Frontend/AccountController.php:326
msgid "Security check failed."
msgstr "Sicherheitsüberprüfung fehlgeschlagen."
@@ -417,8 +420,8 @@ msgstr ""
"Sind Sie sicher, dass Sie diese Lizenz widerrufen möchten? Diese Aktion kann "
"nicht rückgängig gemacht werden."
#: src/Admin/AdminController.php:148 src/Admin/AdminController.php:1324
#: src/Admin/AdminController.php:1339 src/Admin/AdminController.php:1365
#: src/Admin/AdminController.php:148 src/Admin/AdminController.php:1335
#: src/Admin/AdminController.php:1350 src/Admin/AdminController.php:1376
msgid "Edit"
msgstr "Bearbeiten"
@@ -430,34 +433,34 @@ msgstr "Kopiert!"
msgid "Copy failed"
msgstr "Kopieren fehlgeschlagen"
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:902
#: src/Admin/AdminController.php:1221 src/Admin/AdminController.php:1344
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:907
#: src/Admin/AdminController.php:1232 src/Admin/AdminController.php:1355
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:151
msgid "Active"
msgstr "Aktiv"
#: src/Admin/AdminController.php:157 src/Admin/AdminController.php:909
#: src/Admin/AdminController.php:1222 src/Admin/AdminController.php:1345
#: src/Admin/AdminController.php:157 src/Admin/AdminController.php:914
#: src/Admin/AdminController.php:1233 src/Admin/AdminController.php:1356
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
msgid "Inactive"
msgstr "Inaktiv"
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:916
#: src/Admin/AdminController.php:1223 src/Admin/AdminController.php:1346
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:921
#: src/Admin/AdminController.php:1234 src/Admin/AdminController.php:1357
#: src/Admin/DashboardWidgetController.php:159
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr "Abgelaufen"
#: src/Admin/AdminController.php:159 src/Admin/AdminController.php:923
#: src/Admin/AdminController.php:1224 src/Admin/AdminController.php:1347
#: src/Admin/AdminController.php:159 src/Admin/AdminController.php:928
#: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1358
msgid "Revoked"
msgstr "Widerrufen"
#: src/Admin/AdminController.php:196 src/Admin/AdminController.php:1018
#: src/Admin/AdminController.php:196 src/Admin/AdminController.php:1023
msgid "Guest"
msgstr "Gast"
@@ -497,7 +500,7 @@ msgstr "Domain erfolgreich aktualisiert."
msgid "Failed to update domain."
msgstr "Domain konnte nicht aktualisiert werden."
#: src/Admin/AdminController.php:352 src/Admin/AdminController.php:1083
#: src/Admin/AdminController.php:352 src/Admin/AdminController.php:1094
msgid "License revoked successfully."
msgstr "Lizenz erfolgreich widerrufen."
@@ -509,407 +512,416 @@ msgstr "Lizenz konnte nicht widerrufen werden."
msgid "License key and domain are required."
msgstr "Lizenzschlüssel und Domain sind erforderlich."
#: src/Admin/AdminController.php:576
#: src/Admin/AdminController.php:581
msgid "You do not have permission to export licenses."
msgstr "Sie haben keine Berechtigung, Lizenzen zu exportieren."
#: src/Admin/AdminController.php:620
#: src/Admin/AdminController.php:625
msgid "You do not have permission to import licenses."
msgstr "Sie haben keine Berechtigung, Lizenzen zu importieren."
#: src/Admin/AdminController.php:740
#: src/Admin/AdminController.php:745
msgid "Row missing domain"
msgstr "Zeile ohne Domain"
#: src/Admin/AdminController.php:744
#: src/Admin/AdminController.php:749
msgid "Row missing valid product ID"
msgstr "Zeile ohne gültige Produkt-ID"
#: src/Admin/AdminController.php:797
#: src/Admin/AdminController.php:802
#, php-format
msgid "Failed to import license for domain %s"
msgstr "Import der Lizenz für Domain %s fehlgeschlagen"
#: src/Admin/AdminController.php:887
#: src/Admin/AdminController.php:892
msgid "License Dashboard"
msgstr "Lizenz-Dashboard"
#: src/Admin/AdminController.php:895
#: src/Admin/AdminController.php:900
#: src/Admin/DashboardWidgetController.php:147
msgid "Total Licenses"
msgstr "Lizenzen insgesamt"
#: src/Admin/AdminController.php:932
#: src/Admin/AdminController.php:937
msgid "Attention:"
msgstr "Achtung:"
#: src/Admin/AdminController.php:937
#: src/Admin/AdminController.php:942
#, php-format
msgid "%d license is expiring within the next 30 days."
msgid_plural "%d licenses are expiring within the next 30 days."
msgstr[0] "%d Lizenz läuft innerhalb der nächsten 30 Tage ab."
msgstr[1] "%d Lizenzen laufen innerhalb der nächsten 30 Tage ab."
#: src/Admin/AdminController.php:945
#: src/Admin/AdminController.php:950
msgid "View Licenses"
msgstr "Lizenzen anzeigen"
#: src/Admin/AdminController.php:951
#: src/Admin/AdminController.php:956
msgid "Quick Actions"
msgstr "Schnellaktionen"
#: src/Admin/AdminController.php:955
#: src/Admin/AdminController.php:960
msgid "Manage Licenses"
msgstr "Lizenzen verwalten"
#: src/Admin/AdminController.php:959
#: src/Admin/AdminController.php:964
msgid "Export to CSV"
msgstr "Als CSV exportieren"
#: src/Admin/AdminController.php:963 wc-licensed-product.php:137
#: src/Admin/AdminController.php:968 wc-licensed-product.php:137
msgid "Settings"
msgstr "Einstellungen"
#: src/Admin/AdminController.php:1077
#: src/Admin/AdminController.php:1088
msgid "License updated successfully."
msgstr "Lizenz erfolgreich aktualisiert."
#: src/Admin/AdminController.php:1080
#: src/Admin/AdminController.php:1091
msgid "License deleted successfully."
msgstr "Lizenz erfolgreich gelöscht."
#: src/Admin/AdminController.php:1086
#: src/Admin/AdminController.php:1097
msgid "License extended successfully."
msgstr "Lizenz erfolgreich verlängert."
#: src/Admin/AdminController.php:1089
#: src/Admin/AdminController.php:1100
msgid "License set to lifetime successfully."
msgstr "Lizenz erfolgreich auf lebenslang gesetzt."
#: src/Admin/AdminController.php:1095
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1106
#, php-format
msgid "%d license activated."
msgid_plural "%d licenses activated."
msgstr[0] "%d Lizenz aktiviert."
msgstr[1] "%d Lizenzen aktiviert."
#: src/Admin/AdminController.php:1103
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1114
#, php-format
msgid "%d license deactivated."
msgid_plural "%d licenses deactivated."
msgstr[0] "%d Lizenz deaktiviert."
msgstr[1] "%d Lizenzen deaktiviert."
#: src/Admin/AdminController.php:1111
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1122
#, php-format
msgid "%d license revoked."
msgid_plural "%d licenses revoked."
msgstr[0] "%d Lizenz widerrufen."
msgstr[1] "%d Lizenzen widerrufen."
#: src/Admin/AdminController.php:1119
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1130
#, php-format
msgid "%d license deleted."
msgid_plural "%d licenses deleted."
msgstr[0] "%d Lizenz gelöscht."
msgstr[1] "%d Lizenzen gelöscht."
#: src/Admin/AdminController.php:1127
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1138
#, php-format
msgid "%d license extended."
msgid_plural "%d licenses extended."
msgstr[0] "%d Lizenz verlängert."
msgstr[1] "%d Lizenzen verlängert."
#: src/Admin/AdminController.php:1132
#: src/Admin/AdminController.php:1143
msgid "License transferred to new domain successfully."
msgstr "Lizenz erfolgreich auf neue Domain übertragen."
#: src/Admin/AdminController.php:1135
#: src/Admin/AdminController.php:1146
msgid "Failed to transfer license. The license may be revoked or invalid."
msgstr ""
"Lizenzübertragung fehlgeschlagen. Die Lizenz könnte widerrufen oder ungültig "
"sein."
#: src/Admin/AdminController.php:1138
#: src/Admin/AdminController.php:1149
msgid "No licenses to export."
msgstr "Keine Lizenzen zum Exportieren."
#: src/Admin/AdminController.php:1148
#. translators: %d: number of licenses imported
#: src/Admin/AdminController.php:1159
#, php-format
msgid "%d license imported."
msgid_plural "%d licenses imported."
msgstr[0] "%d Lizenz importiert."
msgstr[1] "%d Lizenzen importiert."
#: src/Admin/AdminController.php:1155
#. translators: %d: number of licenses updated
#: src/Admin/AdminController.php:1166
#, php-format
msgid "%d updated."
msgid_plural "%d updated."
msgstr[0] "%d aktualisiert."
msgstr[1] "%d aktualisiert."
#: src/Admin/AdminController.php:1163
#. translators: %d: number of licenses skipped
#: src/Admin/AdminController.php:1174
#, php-format
msgid "%d skipped."
msgid_plural "%d skipped."
msgstr[0] "%d übersprungen."
msgstr[1] "%d übersprungen."
#: src/Admin/AdminController.php:1171
#. translators: %d: number of errors
#: src/Admin/AdminController.php:1182
#, php-format
msgid "%d error."
msgid_plural "%d errors."
msgstr[0] "%d Fehler."
msgstr[1] "%d Fehler."
#: src/Admin/AdminController.php:1192
#: src/Admin/AdminController.php:1203
msgid "Export CSV"
msgstr "CSV exportieren"
#: src/Admin/AdminController.php:1196
#: src/Admin/AdminController.php:1207
msgid "Import CSV"
msgstr "CSV importieren"
#: src/Admin/AdminController.php:1211
#: src/Admin/AdminController.php:1222
msgid "Search Licenses"
msgstr "Lizenzen durchsuchen"
#: src/Admin/AdminController.php:1213
#: src/Admin/AdminController.php:1224
msgid "Search license key or domain..."
msgstr "Lizenzschlüssel oder Domain suchen..."
#: src/Admin/AdminController.php:1214
#: src/Admin/AdminController.php:1225
msgid "Search"
msgstr "Suchen"
#: src/Admin/AdminController.php:1220
#: src/Admin/AdminController.php:1231
msgid "All Statuses"
msgstr "Alle Status"
#: src/Admin/AdminController.php:1228
#: src/Admin/AdminController.php:1239
msgid "All Products"
msgstr "Alle Produkte"
#: src/Admin/AdminController.php:1234
#: src/Admin/AdminController.php:1245
msgid "Filter"
msgstr "Filtern"
#: src/Admin/AdminController.php:1237
#: src/Admin/AdminController.php:1248
msgid "Clear"
msgstr "Zurücksetzen"
#: src/Admin/AdminController.php:1242
#: src/Admin/AdminController.php:1253
msgid "item"
msgstr "Eintrag"
#: src/Admin/AdminController.php:1242
#: src/Admin/AdminController.php:1253
msgid "items"
msgstr "Einträge"
#: src/Admin/AdminController.php:1248
#: src/Admin/AdminController.php:1259
msgid "Showing"
msgstr "Anzeige"
#: src/Admin/AdminController.php:1248
#: src/Admin/AdminController.php:1259
msgid "license"
msgstr "Lizenz"
#: src/Admin/AdminController.php:1248
#: src/Admin/AdminController.php:1259
msgid "licenses"
msgstr "Lizenzen"
#: src/Admin/AdminController.php:1250
#: src/Admin/AdminController.php:1261
msgid "filtered"
msgstr "gefiltert"
#: src/Admin/AdminController.php:1252
#: src/Admin/AdminController.php:1263
msgid "View Dashboard"
msgstr "Dashboard anzeigen"
#: src/Admin/AdminController.php:1261 src/Admin/AdminController.php:1446
#: src/Admin/AdminController.php:1272 src/Admin/AdminController.php:1457
msgid "Bulk Actions"
msgstr "Massenaktionen"
#: src/Admin/AdminController.php:1262 src/Admin/AdminController.php:1447
#: src/Admin/AdminController.php:1273 src/Admin/AdminController.php:1458
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Activate"
msgstr "Aktivieren"
#: src/Admin/AdminController.php:1263 src/Admin/AdminController.php:1448
#: src/Admin/AdminController.php:1274 src/Admin/AdminController.php:1459
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Deactivate"
msgstr "Deaktivieren"
#: src/Admin/AdminController.php:1264 src/Admin/AdminController.php:1408
#: src/Admin/AdminController.php:1449
#: src/Admin/AdminController.php:1275 src/Admin/AdminController.php:1419
#: src/Admin/AdminController.php:1460
msgid "Revoke"
msgstr "Widerrufen"
#: src/Admin/AdminController.php:1265 src/Admin/AdminController.php:1450
#: src/Admin/AdminController.php:1276 src/Admin/AdminController.php:1461
msgid "Extend 30 days"
msgstr "30 Tage verlängern"
#: src/Admin/AdminController.php:1266 src/Admin/AdminController.php:1451
#: src/Admin/AdminController.php:1277 src/Admin/AdminController.php:1462
msgid "Extend 90 days"
msgstr "90 Tage verlängern"
#: src/Admin/AdminController.php:1267 src/Admin/AdminController.php:1452
#: src/Admin/AdminController.php:1278 src/Admin/AdminController.php:1463
msgid "Extend 1 year"
msgstr "1 Jahr verlängern"
#: src/Admin/AdminController.php:1268 src/Admin/AdminController.php:1417
#: src/Admin/AdminController.php:1453 src/Admin/VersionAdminController.php:191
#: src/Admin/AdminController.php:1279 src/Admin/AdminController.php:1428
#: src/Admin/AdminController.php:1464 src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:422
msgid "Delete"
msgstr "Löschen"
#: src/Admin/AdminController.php:1270 src/Admin/AdminController.php:1455
#: src/Admin/AdminController.php:1281 src/Admin/AdminController.php:1466
msgid "Apply"
msgstr "Anwenden"
#: src/Admin/AdminController.php:1282 src/Admin/AdminController.php:1433
#: src/Admin/AdminController.php:1293 src/Admin/AdminController.php:1444
#: src/Email/LicenseExpirationEmail.php:104
#: src/Email/LicenseExpiredEmail.php:96
msgid "Customer"
msgstr "Kunde"
#: src/Admin/AdminController.php:1285 src/Admin/AdminController.php:1436
#: src/Admin/AdminController.php:1296 src/Admin/AdminController.php:1447
msgid "Created"
msgstr "Erstellt"
#: src/Admin/AdminController.php:1293
#: src/Admin/AdminController.php:1304
msgid "No licenses found."
msgstr "Keine Lizenzen gefunden."
#: src/Admin/AdminController.php:1303 src/Frontend/AccountController.php:194
#: src/Admin/AdminController.php:1314 src/Frontend/AccountController.php:194
msgid "Copy to clipboard"
msgstr "In Zwischenablage kopieren"
#: src/Admin/AdminController.php:1369
#: src/Admin/AdminController.php:1380
msgid "Leave empty for lifetime"
msgstr "Leer lassen für lebenslang"
#: src/Admin/AdminController.php:1372 src/Admin/AdminController.php:1401
#: src/Admin/AdminController.php:1383 src/Admin/AdminController.php:1412
msgid "Set to lifetime"
msgstr "Auf lebenslang setzen"
#: src/Admin/AdminController.php:1382
#: src/Admin/AdminController.php:1393
msgid "Test license against API"
msgstr "Lizenz gegen API testen"
#: src/Admin/AdminController.php:1382
#: src/Admin/AdminController.php:1393
msgid "Test"
msgstr "Testen"
#: src/Admin/AdminController.php:1389 src/Frontend/AccountController.php:207
#: src/Admin/AdminController.php:1400 src/Frontend/AccountController.php:207
msgid "Transfer to new domain"
msgstr "Auf neue Domain übertragen"
#: src/Admin/AdminController.php:1389 src/Frontend/AccountController.php:209
#: src/Admin/AdminController.php:1400 src/Frontend/AccountController.php:209
msgid "Transfer"
msgstr "Übertragen"
#: src/Admin/AdminController.php:1395
#: src/Admin/AdminController.php:1406
msgid "Extend by 30 days"
msgstr "Um 30 Tage verlängern"
#: src/Admin/AdminController.php:1407
#: src/Admin/AdminController.php:1418
msgid "Are you sure?"
msgstr "Sind Sie sicher?"
#: src/Admin/AdminController.php:1416
#: src/Admin/AdminController.php:1427
msgid "Are you sure you want to delete this license?"
msgstr "Sind Sie sicher, dass Sie diese Lizenz löschen möchten?"
#: src/Admin/AdminController.php:1476
#: src/Admin/AdminController.php:1487
msgid "License Validation Test"
msgstr "Lizenzvalidierungstest"
#: src/Admin/AdminController.php:1491
#: src/Admin/AdminController.php:1502
msgid "Testing license..."
msgstr "Lizenz wird geprüft..."
#: src/Admin/AdminController.php:1497 src/Frontend/AccountController.php:249
#: src/Admin/AdminController.php:1508 src/Frontend/AccountController.php:249
msgid "Close"
msgstr "Schliessen"
#: src/Admin/AdminController.php:1506 src/Frontend/AccountController.php:250
#: src/Admin/AdminController.php:1517 src/Frontend/AccountController.php:250
msgid "Transfer License to New Domain"
msgstr "Lizenz auf neue Domain übertragen"
#: src/Admin/AdminController.php:1513 src/Frontend/AccountController.php:255
#: src/Admin/AdminController.php:1524 src/Frontend/AccountController.php:255
msgid "Current Domain"
msgstr "Aktuelle Domain"
#: src/Admin/AdminController.php:1517 src/Frontend/AccountController.php:260
#: src/Admin/AdminController.php:1528 src/Frontend/AccountController.php:260
msgid "New Domain"
msgstr "Neue Domain"
#: src/Admin/AdminController.php:1520 src/Frontend/AccountController.php:264
#: src/Admin/AdminController.php:1531 src/Frontend/AccountController.php:264
msgid "Enter the new domain without http:// or www."
msgstr "Geben Sie die neue Domain ohne http:// oder www ein."
#: src/Admin/AdminController.php:1525 src/Frontend/AccountController.php:269
#: src/Admin/AdminController.php:1536 src/Frontend/AccountController.php:269
msgid "Transfer License"
msgstr "Lizenz übertragen"
#: src/Admin/AdminController.php:1595
#: src/Admin/AdminController.php:1606
msgid "License is VALID"
msgstr "Lizenz ist GÜLTIG"
#: src/Admin/AdminController.php:1598 src/Admin/VersionAdminController.php:81
#: src/Admin/AdminController.php:1609 src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:136
msgid "Version"
msgstr "Version"
#: src/Admin/AdminController.php:1606
#: src/Admin/AdminController.php:1617
msgid "License is INVALID"
msgstr "Lizenz ist UNGÜLTIG"
#: src/Admin/AdminController.php:1608
#: src/Admin/AdminController.php:1619
msgid "Error Code"
msgstr "Fehlercode"
#: src/Admin/AdminController.php:1609
#: src/Admin/AdminController.php:1620
msgid "Message"
msgstr "Meldung"
#: src/Admin/AdminController.php:1622
#: src/Admin/AdminController.php:1633
msgid "Failed to test license. Please try again."
msgstr "Lizenztest fehlgeschlagen. Bitte versuchen Sie es erneut."
#: src/Admin/AdminController.php:1660 src/Admin/AdminController.php:1753
#: src/Admin/AdminController.php:1671 src/Admin/AdminController.php:1764
msgid "Import Licenses"
msgstr "Lizenzen importieren"
#: src/Admin/AdminController.php:1662
#: src/Admin/AdminController.php:1673
msgid "Back to Licenses"
msgstr "Zurück zu Lizenzen"
#: src/Admin/AdminController.php:1672
#: src/Admin/AdminController.php:1683
msgid "Error uploading file. Please try again."
msgstr "Fehler beim Hochladen der Datei. Bitte versuchen Sie es erneut."
#: src/Admin/AdminController.php:1675
#: src/Admin/AdminController.php:1686
msgid "Invalid file type. Please upload a CSV file."
msgstr "Ungültiger Dateityp. Bitte laden Sie eine CSV-Datei hoch."
#: src/Admin/AdminController.php:1678
#: src/Admin/AdminController.php:1689
msgid "Error reading file. Please check the file format."
msgstr "Fehler beim Lesen der Datei. Bitte überprüfen Sie das Dateiformat."
#: src/Admin/AdminController.php:1681
#: src/Admin/AdminController.php:1692
msgid "An error occurred during import."
msgstr "Beim Import ist ein Fehler aufgetreten."
#: src/Admin/AdminController.php:1689
#: src/Admin/AdminController.php:1700
msgid "Import Licenses from CSV"
msgstr "Lizenzen aus CSV importieren"
#: src/Admin/AdminController.php:1692
#: src/Admin/AdminController.php:1703
msgid ""
"Upload a CSV file to import licenses. You can use the exported CSV format or "
"a simplified format."
@@ -917,71 +929,71 @@ msgstr ""
"Laden Sie eine CSV-Datei hoch, um Lizenzen zu importieren. Sie können das "
"exportierte CSV-Format oder ein vereinfachtes Format verwenden."
#: src/Admin/AdminController.php:1695
#: src/Admin/AdminController.php:1706
msgid "CSV Format"
msgstr "CSV-Format"
#: src/Admin/AdminController.php:1697
#: src/Admin/AdminController.php:1708
msgid "The CSV file should contain the following columns:"
msgstr "Die CSV-Datei sollte die folgenden Spalten enthalten:"
#: src/Admin/AdminController.php:1701
#: src/Admin/AdminController.php:1712
msgid "Full Format (from Export):"
msgstr "Vollständiges Format (vom Export):"
#: src/Admin/AdminController.php:1704
#: src/Admin/AdminController.php:1715
msgid "Simplified Format:"
msgstr "Vereinfachtes Format:"
#: src/Admin/AdminController.php:1709
#: src/Admin/AdminController.php:1720
msgid "Notes:"
msgstr "Hinweise:"
#: src/Admin/AdminController.php:1710
#: src/Admin/AdminController.php:1721
msgid "Leave License Key empty to auto-generate."
msgstr "Lizenzschlüssel leer lassen für automatische Generierung."
#: src/Admin/AdminController.php:1711
#: src/Admin/AdminController.php:1722
msgid "Status can be: active, inactive, expired, revoked (defaults to active)."
msgstr ""
"Status kann sein: active, inactive, expired, revoked (Standard: active)."
#: src/Admin/AdminController.php:1712
#: src/Admin/AdminController.php:1723
msgid "Expires At should be in YYYY-MM-DD format or \"Lifetime\"."
msgstr "Ablaufdatum sollte im Format JJJJ-MM-TT oder \"Lifetime\" sein."
#: src/Admin/AdminController.php:1724
#: src/Admin/AdminController.php:1735
msgid "CSV File"
msgstr "CSV-Datei"
#: src/Admin/AdminController.php:1728
#: src/Admin/AdminController.php:1739
msgid "Select a CSV file to import."
msgstr "Wählen Sie eine CSV-Datei zum Importieren."
#: src/Admin/AdminController.php:1732
#: src/Admin/AdminController.php:1743
msgid "Options"
msgstr "Optionen"
#: src/Admin/AdminController.php:1736
#: src/Admin/AdminController.php:1747
msgid "Skip first row (header row)"
msgstr "Erste Zeile überspringen (Kopfzeile)"
#: src/Admin/AdminController.php:1741
#: src/Admin/AdminController.php:1752
msgid "Update existing licenses (by license key)"
msgstr "Bestehende Lizenzen aktualisieren (nach Lizenzschlüssel)"
#: src/Admin/AdminController.php:1744
#: src/Admin/AdminController.php:1755
msgid ""
"If enabled, licenses with matching keys will be updated instead of skipped."
msgstr ""
"Falls aktiviert, werden Lizenzen mit übereinstimmenden Schlüsseln "
"aktualisiert statt übersprungen."
#: src/Admin/AdminController.php:1771
#: src/Admin/AdminController.php:1782
msgid "License"
msgstr "Lizenz"
#: src/Admin/AdminController.php:1830
#: src/Admin/AdminController.php:1841
msgid "No domain specified"
msgstr "Keine Domain angegeben"
@@ -1209,28 +1221,28 @@ msgstr "Alle Lizenzen anzeigen"
msgid "Too many requests. Please try again later."
msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
#: src/Api/RestApiController.php:222 src/Api/RestApiController.php:255
#: src/Api/RestApiController.php:345 src/Api/RestApiController.php:378
#: src/License/LicenseManager.php:357
msgid "License key not found."
msgstr "Lizenzschlüssel nicht gefunden."
#: src/Api/RestApiController.php:263
#: src/Api/RestApiController.php:386
msgid "This license is not valid."
msgstr "Diese Lizenz ist ungültig."
#: src/Api/RestApiController.php:273
#: src/Api/RestApiController.php:396
msgid "License is already activated for this domain."
msgstr "Die Lizenz ist bereits für diese Domain aktiviert."
#: src/Api/RestApiController.php:282
#: src/Api/RestApiController.php:405
msgid "Maximum number of activations reached."
msgstr "Maximale Anzahl der Aktivierungen erreicht."
#: src/Api/RestApiController.php:293
#: src/Api/RestApiController.php:416
msgid "Failed to activate license."
msgstr "Lizenz konnte nicht aktiviert werden."
#: src/Api/RestApiController.php:299
#: src/Api/RestApiController.php:422
msgid "License activated successfully."
msgstr "Lizenz erfolgreich aktiviert."
@@ -1278,6 +1290,14 @@ msgstr "Bitte geben Sie eine gültige Domain für Ihre Lizenz-Aktivierung ein."
msgid "Domain for license activation"
msgstr "Domain für Lizenz-Aktivierung"
#: src/License/PluginLicenseChecker.php:117
msgid "License settings not configured."
msgstr "Lizenzeinstellungen nicht konfiguriert."
#: src/License/PluginLicenseChecker.php:153
msgid "Could not connect to license server."
msgstr "Verbindung zum Lizenzserver konnte nicht hergestellt werden."
#: src/License/LicenseManager.php:366
msgid "This license has been revoked."
msgstr "Diese Lizenz wurde widerrufen."
@@ -1300,18 +1320,11 @@ msgstr "Diese Lizenz ist für diese Domain nicht gültig."
msgid "Unknown Product"
msgstr "Unbekanntes Produkt"
#: src/License/PluginLicenseChecker.php:117
msgid "License settings not configured."
msgstr "Lizenzeinstellungen nicht konfiguriert."
#: src/License/PluginLicenseChecker.php:153
msgid "Could not connect to license server."
msgstr "Verbindung zum Lizenzserver konnte nicht hergestellt werden."
#: src/Product/VersionManager.php:166
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"
@@ -1330,6 +1343,7 @@ msgstr "Lizenz-Einstellungen"
msgid "%d days"
msgstr "%d Tage"
#. translators: %s: URL to settings page
#: src/Product/LicensedProductType.php:119
#, php-format
msgid "Leave fields empty to use default settings from %s."
@@ -1343,6 +1357,7 @@ msgstr "WooCommerce > Einstellungen > Lizensierte Produkte"
msgid "Max Activations"
msgstr "Max. Aktivierungen"
#. translators: %d: default max activations value
#: src/Product/LicensedProductType.php:131
#, php-format
msgid "Maximum number of domain activations per license. Default: %d"
@@ -1352,6 +1367,7 @@ msgstr "Maximale Anzahl der Domain-Aktivierungen pro Lizenz. Standard: %d"
msgid "License Validity (Days)"
msgstr "Lizenz-Gültigkeit (Tage)"
#. translators: %s: default validity value
#: src/Product/LicensedProductType.php:149
#, php-format
msgid "Number of days the license is valid. Leave empty for default (%s)."
@@ -1361,6 +1377,7 @@ msgstr "Anzahl Tage, die die Lizenz gültig ist. Leer lassen für Standard (%s).
msgid "Bind to Major Version"
msgstr "An Hauptversion binden"
#. translators: %s: default bind to version value (Yes/No)
#: src/Product/LicensedProductType.php:167
#, php-format
msgid ""
@@ -1442,11 +1459,11 @@ msgid "You have no licenses yet."
msgstr "Sie haben noch keine Lizenzen."
#: src/Frontend/AccountController.php:190
#: src/Email/LicenseExpirationEmail.php:207
#: src/Email/LicenseExpirationEmail.php:270
#: src/Email/LicenseEmailController.php:212
#: src/Email/LicenseEmailController.php:216
#: src/Email/LicenseEmailController.php:320
#: src/Email/LicenseExpirationEmail.php:207
#: src/Email/LicenseExpirationEmail.php:270
#: src/Email/LicenseExpiredEmail.php:191 src/Email/LicenseExpiredEmail.php:256
msgid "License Key:"
msgstr "Lizenzschlüssel:"
@@ -1459,9 +1476,9 @@ msgid "Domain:"
msgstr "Domain:"
#: src/Frontend/AccountController.php:213
#: src/Email/LicenseEmailController.php:323
#: src/Email/LicenseExpirationEmail.php:219
#: src/Email/LicenseExpirationEmail.php:272
#: src/Email/LicenseEmailController.php:323
msgid "Expires:"
msgstr "Läuft ab:"
@@ -1525,25 +1542,6 @@ msgstr "Die neue Domain ist dieselbe wie die aktuelle Domain."
msgid "Failed to transfer license. Please try again."
msgstr "Lizenzübertragung fehlgeschlagen. Bitte versuchen Sie es erneut."
#: src/Email/LicenseEmailController.php:256
msgid "Your License Keys"
msgstr "Ihre Lizenzschlüssel"
#: src/Email/LicenseEmailController.php:260
#: src/Email/LicenseEmailController.php:315
msgid "Licensed Domain:"
msgstr "Lizensierte Domain:"
#: src/Email/LicenseEmailController.php:296
#: src/Email/LicenseEmailController.php:330
msgid "You can also view your licenses in your account under \"Licenses\"."
msgstr ""
"Sie können Ihre Lizenzen auch in Ihrem Konto unter \"Lizenzen\" einsehen."
#: src/Email/LicenseEmailController.php:311
msgid "YOUR LICENSE KEYS"
msgstr "IHRE LIZENZSCHLÜSSEL"
#: src/Email/LicenseExpirationEmail.php:55
msgid "License Expiration Warning"
msgstr "Lizenzablauf-Warnung"
@@ -1612,6 +1610,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
@@ -1658,6 +1657,25 @@ msgstr "E-Mail-Typ"
msgid "Choose which format of email to send."
msgstr "Wählen Sie, welches E-Mail-Format gesendet werden soll."
#: src/Email/LicenseEmailController.php:256
msgid "Your License Keys"
msgstr "Ihre Lizenzschlüssel"
#: src/Email/LicenseEmailController.php:260
#: src/Email/LicenseEmailController.php:315
msgid "Licensed Domain:"
msgstr "Lizensierte Domain:"
#: src/Email/LicenseEmailController.php:296
#: src/Email/LicenseEmailController.php:330
msgid "You can also view your licenses in your account under \"Licenses\"."
msgstr ""
"Sie können Ihre Lizenzen auch in Ihrem Konto unter \"Lizenzen\" einsehen."
#: src/Email/LicenseEmailController.php:311
msgid "YOUR LICENSE KEYS"
msgstr "IHRE LIZENZSCHLÜSSEL"
#: src/Email/LicenseExpiredEmail.php:50 src/Email/LicenseExpiredEmail.php:76
msgid "License Expired"
msgstr "Lizenz abgelaufen"
@@ -1701,23 +1719,25 @@ msgstr "Status:"
#: src/Email/LicenseExpiredEmail.php:278
msgid "To continue using this product, please renew your license."
msgstr "Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz."
msgstr ""
"Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz."
#: src/Plugin.php:257
#: src/Plugin.php:258
msgid "WC Licensed Product"
msgstr "WC Licensed Product"
#: src/Plugin.php:258
#: src/Plugin.php:259
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
"Plugin-Lizenz ist nicht konfiguriert oder ungültig. Frontend-Funktionen sind "
"deaktiviert."
#: src/Plugin.php:259
#: src/Plugin.php:260
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."

View File

@@ -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 PACKAGE 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: PACKAGE VERSION\n"
"Project-Id-Version: wc-licensed-product 0.3.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-23 16:05+0100\n"
"POT-Creation-Date: 2026-01-23 21:09+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"
@@ -49,15 +49,15 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:118
#: src/Admin/OrderLicenseController.php:182 src/Admin/AdminController.php:150
#: src/Admin/AdminController.php:1329 src/Admin/AdminController.php:1349
#: src/Admin/AdminController.php:1370
#: src/Admin/AdminController.php:1340 src/Admin/AdminController.php:1360
#: src/Admin/AdminController.php:1381
msgid "Save"
msgstr ""
#: src/Admin/OrderLicenseController.php:127
#: src/Admin/OrderLicenseController.php:222 src/Admin/AdminController.php:76
#: src/Admin/AdminController.php:77 src/Admin/AdminController.php:90
#: src/Admin/AdminController.php:1189 src/Frontend/AccountController.php:90
#: src/Admin/AdminController.php:1200 src/Frontend/AccountController.php:90
msgid "Licenses"
msgstr ""
@@ -76,41 +76,41 @@ msgid "Licenses will be generated when the order is marked as paid/completed."
msgstr ""
#: src/Admin/OrderLicenseController.php:144
#: src/Admin/SettingsController.php:142 src/Admin/AdminController.php:1280
#: src/Admin/AdminController.php:1431 src/Admin/AdminController.php:1480
#: src/Admin/SettingsController.php:142 src/Admin/AdminController.php:1291
#: src/Admin/AdminController.php:1442 src/Admin/AdminController.php:1491
#: src/Email/LicenseEmailController.php:269
msgid "License Key"
msgstr ""
#: src/Admin/OrderLicenseController.php:145 src/Admin/AdminController.php:1281
#: src/Admin/AdminController.php:1432 src/Admin/AdminController.php:1597
#: src/Admin/OrderLicenseController.php:145 src/Admin/AdminController.php:1292
#: src/Admin/AdminController.php:1443 src/Admin/AdminController.php:1608
#: src/Email/LicenseEmailController.php:268
msgid "Product"
msgstr ""
#: src/Admin/OrderLicenseController.php:146 src/Admin/AdminController.php:1283
#: src/Admin/AdminController.php:1434 src/Admin/AdminController.php:1484
#: src/Admin/OrderLicenseController.php:146 src/Admin/AdminController.php:1294
#: src/Admin/AdminController.php:1445 src/Admin/AdminController.php:1495
msgid "Domain"
msgstr ""
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1284
#: src/Admin/AdminController.php:1435 src/Admin/VersionAdminController.php:140
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1295
#: src/Admin/AdminController.php:1446 src/Admin/VersionAdminController.php:140
msgid "Status"
msgstr ""
#: src/Admin/OrderLicenseController.php:148 src/Admin/AdminController.php:1286
#: src/Admin/AdminController.php:1437 src/Admin/AdminController.php:1600
#: src/Admin/AdminController.php:1602 src/Email/LicenseEmailController.php:270
#: src/Admin/OrderLicenseController.php:148 src/Admin/AdminController.php:1297
#: src/Admin/AdminController.php:1448 src/Admin/AdminController.php:1611
#: src/Admin/AdminController.php:1613 src/Email/LicenseEmailController.php:270
msgid "Expires"
msgstr ""
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1287
#: src/Admin/AdminController.php:1438 src/Admin/VersionAdminController.php:142
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1298
#: src/Admin/AdminController.php:1449 src/Admin/VersionAdminController.php:142
msgid "Actions"
msgstr ""
#: src/Admin/OrderLicenseController.php:168 src/Admin/AdminController.php:195
#: src/Admin/AdminController.php:1014
#: src/Admin/AdminController.php:1019
msgid "Unknown"
msgstr ""
@@ -119,16 +119,16 @@ msgid "Edit domain"
msgstr ""
#: src/Admin/OrderLicenseController.php:185 src/Admin/AdminController.php:149
#: src/Admin/AdminController.php:1330 src/Admin/AdminController.php:1350
#: src/Admin/AdminController.php:1371 src/Admin/AdminController.php:1526
#: src/Admin/AdminController.php:1341 src/Admin/AdminController.php:1361
#: src/Admin/AdminController.php:1382 src/Admin/AdminController.php:1537
#: src/Frontend/AccountController.php:271
msgid "Cancel"
msgstr ""
#: src/Admin/OrderLicenseController.php:201
#: src/Admin/SettingsController.php:192 src/Admin/AdminController.php:151
#: src/Admin/AdminController.php:266 src/Admin/AdminController.php:1362
#: src/Admin/AdminController.php:1602 src/Product/LicensedProductType.php:110
#: src/Admin/AdminController.php:266 src/Admin/AdminController.php:1373
#: src/Admin/AdminController.php:1613 src/Product/LicensedProductType.php:110
#: src/Product/LicensedProductType.php:158
msgid "Lifetime"
msgstr ""
@@ -137,6 +137,7 @@ msgstr ""
msgid "View in Licenses"
msgstr ""
#. translators: %s: Link to licenses page
#: src/Admin/OrderLicenseController.php:221
#, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page."
@@ -293,6 +294,7 @@ msgstr ""
msgid "Expiration Warning Schedule"
msgstr ""
#. translators: %s: URL to WooCommerce email settings
#: src/Admin/SettingsController.php:223
#, php-format
msgid ""
@@ -348,8 +350,9 @@ msgstr ""
#: src/Admin/SettingsController.php:454 src/Admin/AdminController.php:455
#: src/Admin/AdminController.php:475 src/Admin/AdminController.php:493
#: src/Admin/AdminController.php:511 src/Admin/AdminController.php:531
#: src/Admin/AdminController.php:549 src/Admin/AdminController.php:616
#: src/Admin/AdminController.php:806 src/Frontend/AccountController.php:326
#: src/Admin/AdminController.php:549 src/Admin/AdminController.php:577
#: src/Admin/AdminController.php:621 src/Admin/AdminController.php:811
#: src/Frontend/AccountController.php:326
msgid "Security check failed."
msgstr ""
@@ -394,8 +397,8 @@ msgid ""
"Are you sure you want to revoke this license? This action cannot be undone."
msgstr ""
#: src/Admin/AdminController.php:148 src/Admin/AdminController.php:1324
#: src/Admin/AdminController.php:1339 src/Admin/AdminController.php:1365
#: src/Admin/AdminController.php:148 src/Admin/AdminController.php:1335
#: src/Admin/AdminController.php:1350 src/Admin/AdminController.php:1376
msgid "Edit"
msgstr ""
@@ -407,34 +410,34 @@ msgstr ""
msgid "Copy failed"
msgstr ""
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:902
#: src/Admin/AdminController.php:1221 src/Admin/AdminController.php:1344
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:907
#: src/Admin/AdminController.php:1232 src/Admin/AdminController.php:1355
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:151
msgid "Active"
msgstr ""
#: src/Admin/AdminController.php:157 src/Admin/AdminController.php:909
#: src/Admin/AdminController.php:1222 src/Admin/AdminController.php:1345
#: src/Admin/AdminController.php:157 src/Admin/AdminController.php:914
#: src/Admin/AdminController.php:1233 src/Admin/AdminController.php:1356
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
msgid "Inactive"
msgstr ""
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:916
#: src/Admin/AdminController.php:1223 src/Admin/AdminController.php:1346
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:921
#: src/Admin/AdminController.php:1234 src/Admin/AdminController.php:1357
#: src/Admin/DashboardWidgetController.php:159
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr ""
#: src/Admin/AdminController.php:159 src/Admin/AdminController.php:923
#: src/Admin/AdminController.php:1224 src/Admin/AdminController.php:1347
#: src/Admin/AdminController.php:159 src/Admin/AdminController.php:928
#: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1358
msgid "Revoked"
msgstr ""
#: src/Admin/AdminController.php:196 src/Admin/AdminController.php:1018
#: src/Admin/AdminController.php:196 src/Admin/AdminController.php:1023
msgid "Guest"
msgstr ""
@@ -474,7 +477,7 @@ msgstr ""
msgid "Failed to update domain."
msgstr ""
#: src/Admin/AdminController.php:352 src/Admin/AdminController.php:1083
#: src/Admin/AdminController.php:352 src/Admin/AdminController.php:1094
msgid "License revoked successfully."
msgstr ""
@@ -486,472 +489,481 @@ msgstr ""
msgid "License key and domain are required."
msgstr ""
#: src/Admin/AdminController.php:576
#: src/Admin/AdminController.php:581
msgid "You do not have permission to export licenses."
msgstr ""
#: src/Admin/AdminController.php:620
#: src/Admin/AdminController.php:625
msgid "You do not have permission to import licenses."
msgstr ""
#: src/Admin/AdminController.php:740
#: src/Admin/AdminController.php:745
msgid "Row missing domain"
msgstr ""
#: src/Admin/AdminController.php:744
#: src/Admin/AdminController.php:749
msgid "Row missing valid product ID"
msgstr ""
#: src/Admin/AdminController.php:797
#: src/Admin/AdminController.php:802
#, php-format
msgid "Failed to import license for domain %s"
msgstr ""
#: src/Admin/AdminController.php:887
#: src/Admin/AdminController.php:892
msgid "License Dashboard"
msgstr ""
#: src/Admin/AdminController.php:895
#: src/Admin/AdminController.php:900
#: src/Admin/DashboardWidgetController.php:147
msgid "Total Licenses"
msgstr ""
#: src/Admin/AdminController.php:932
#: src/Admin/AdminController.php:937
msgid "Attention:"
msgstr ""
#: src/Admin/AdminController.php:937
#: src/Admin/AdminController.php:942
#, php-format
msgid "%d license is expiring within the next 30 days."
msgid_plural "%d licenses are expiring within the next 30 days."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:945
#: src/Admin/AdminController.php:950
msgid "View Licenses"
msgstr ""
#: src/Admin/AdminController.php:951
#: src/Admin/AdminController.php:956
msgid "Quick Actions"
msgstr ""
#: src/Admin/AdminController.php:955
#: src/Admin/AdminController.php:960
msgid "Manage Licenses"
msgstr ""
#: src/Admin/AdminController.php:959
#: src/Admin/AdminController.php:964
msgid "Export to CSV"
msgstr ""
#: src/Admin/AdminController.php:963 wc-licensed-product.php:137
#: src/Admin/AdminController.php:968 wc-licensed-product.php:137
msgid "Settings"
msgstr ""
#: src/Admin/AdminController.php:1077
#: src/Admin/AdminController.php:1088
msgid "License updated successfully."
msgstr ""
#: src/Admin/AdminController.php:1080
#: src/Admin/AdminController.php:1091
msgid "License deleted successfully."
msgstr ""
#: src/Admin/AdminController.php:1086
#: src/Admin/AdminController.php:1097
msgid "License extended successfully."
msgstr ""
#: src/Admin/AdminController.php:1089
#: src/Admin/AdminController.php:1100
msgid "License set to lifetime successfully."
msgstr ""
#: src/Admin/AdminController.php:1095
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1106
#, php-format
msgid "%d license activated."
msgid_plural "%d licenses activated."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1103
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1114
#, php-format
msgid "%d license deactivated."
msgid_plural "%d licenses deactivated."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1111
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1122
#, php-format
msgid "%d license revoked."
msgid_plural "%d licenses revoked."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1119
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1130
#, php-format
msgid "%d license deleted."
msgid_plural "%d licenses deleted."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1127
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1138
#, php-format
msgid "%d license extended."
msgid_plural "%d licenses extended."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1132
#: src/Admin/AdminController.php:1143
msgid "License transferred to new domain successfully."
msgstr ""
#: src/Admin/AdminController.php:1135
#: src/Admin/AdminController.php:1146
msgid "Failed to transfer license. The license may be revoked or invalid."
msgstr ""
#: src/Admin/AdminController.php:1138
#: src/Admin/AdminController.php:1149
msgid "No licenses to export."
msgstr ""
#: src/Admin/AdminController.php:1148
#. translators: %d: number of licenses imported
#: src/Admin/AdminController.php:1159
#, php-format
msgid "%d license imported."
msgid_plural "%d licenses imported."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1155
#. translators: %d: number of licenses updated
#: src/Admin/AdminController.php:1166
#, php-format
msgid "%d updated."
msgid_plural "%d updated."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1163
#. translators: %d: number of licenses skipped
#: src/Admin/AdminController.php:1174
#, php-format
msgid "%d skipped."
msgid_plural "%d skipped."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1171
#. translators: %d: number of errors
#: src/Admin/AdminController.php:1182
#, php-format
msgid "%d error."
msgid_plural "%d errors."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/AdminController.php:1192
#: src/Admin/AdminController.php:1203
msgid "Export CSV"
msgstr ""
#: src/Admin/AdminController.php:1196
#: src/Admin/AdminController.php:1207
msgid "Import CSV"
msgstr ""
#: src/Admin/AdminController.php:1211
#: src/Admin/AdminController.php:1222
msgid "Search Licenses"
msgstr ""
#: src/Admin/AdminController.php:1213
#: src/Admin/AdminController.php:1224
msgid "Search license key or domain..."
msgstr ""
#: src/Admin/AdminController.php:1214
#: src/Admin/AdminController.php:1225
msgid "Search"
msgstr ""
#: src/Admin/AdminController.php:1220
#: src/Admin/AdminController.php:1231
msgid "All Statuses"
msgstr ""
#: src/Admin/AdminController.php:1228
#: src/Admin/AdminController.php:1239
msgid "All Products"
msgstr ""
#: src/Admin/AdminController.php:1234
#: src/Admin/AdminController.php:1245
msgid "Filter"
msgstr ""
#: src/Admin/AdminController.php:1237
#: src/Admin/AdminController.php:1248
msgid "Clear"
msgstr ""
#: src/Admin/AdminController.php:1242
#: src/Admin/AdminController.php:1253
msgid "item"
msgstr ""
#: src/Admin/AdminController.php:1242
#: src/Admin/AdminController.php:1253
msgid "items"
msgstr ""
#: src/Admin/AdminController.php:1248
#: src/Admin/AdminController.php:1259
msgid "Showing"
msgstr ""
#: src/Admin/AdminController.php:1248
#: src/Admin/AdminController.php:1259
msgid "license"
msgstr ""
#: src/Admin/AdminController.php:1248
#: src/Admin/AdminController.php:1259
msgid "licenses"
msgstr ""
#: src/Admin/AdminController.php:1250
#: src/Admin/AdminController.php:1261
msgid "filtered"
msgstr ""
#: src/Admin/AdminController.php:1252
#: src/Admin/AdminController.php:1263
msgid "View Dashboard"
msgstr ""
#: src/Admin/AdminController.php:1261 src/Admin/AdminController.php:1446
#: src/Admin/AdminController.php:1272 src/Admin/AdminController.php:1457
msgid "Bulk Actions"
msgstr ""
#: src/Admin/AdminController.php:1262 src/Admin/AdminController.php:1447
#: src/Admin/AdminController.php:1273 src/Admin/AdminController.php:1458
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Activate"
msgstr ""
#: src/Admin/AdminController.php:1263 src/Admin/AdminController.php:1448
#: src/Admin/AdminController.php:1274 src/Admin/AdminController.php:1459
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Deactivate"
msgstr ""
#: src/Admin/AdminController.php:1264 src/Admin/AdminController.php:1408
#: src/Admin/AdminController.php:1449
#: src/Admin/AdminController.php:1275 src/Admin/AdminController.php:1419
#: src/Admin/AdminController.php:1460
msgid "Revoke"
msgstr ""
#: src/Admin/AdminController.php:1265 src/Admin/AdminController.php:1450
#: src/Admin/AdminController.php:1276 src/Admin/AdminController.php:1461
msgid "Extend 30 days"
msgstr ""
#: src/Admin/AdminController.php:1266 src/Admin/AdminController.php:1451
#: src/Admin/AdminController.php:1277 src/Admin/AdminController.php:1462
msgid "Extend 90 days"
msgstr ""
#: src/Admin/AdminController.php:1267 src/Admin/AdminController.php:1452
#: src/Admin/AdminController.php:1278 src/Admin/AdminController.php:1463
msgid "Extend 1 year"
msgstr ""
#: src/Admin/AdminController.php:1268 src/Admin/AdminController.php:1417
#: src/Admin/AdminController.php:1453 src/Admin/VersionAdminController.php:191
#: src/Admin/AdminController.php:1279 src/Admin/AdminController.php:1428
#: src/Admin/AdminController.php:1464 src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:422
msgid "Delete"
msgstr ""
#: src/Admin/AdminController.php:1270 src/Admin/AdminController.php:1455
#: src/Admin/AdminController.php:1281 src/Admin/AdminController.php:1466
msgid "Apply"
msgstr ""
#: src/Admin/AdminController.php:1282 src/Admin/AdminController.php:1433
#: src/Admin/AdminController.php:1293 src/Admin/AdminController.php:1444
#: src/Email/LicenseExpirationEmail.php:104
#: src/Email/LicenseExpiredEmail.php:96
msgid "Customer"
msgstr ""
#: src/Admin/AdminController.php:1285 src/Admin/AdminController.php:1436
#: src/Admin/AdminController.php:1296 src/Admin/AdminController.php:1447
msgid "Created"
msgstr ""
#: src/Admin/AdminController.php:1293
#: src/Admin/AdminController.php:1304
msgid "No licenses found."
msgstr ""
#: src/Admin/AdminController.php:1303 src/Frontend/AccountController.php:194
#: src/Admin/AdminController.php:1314 src/Frontend/AccountController.php:194
msgid "Copy to clipboard"
msgstr ""
#: src/Admin/AdminController.php:1369
#: src/Admin/AdminController.php:1380
msgid "Leave empty for lifetime"
msgstr ""
#: src/Admin/AdminController.php:1372 src/Admin/AdminController.php:1401
#: src/Admin/AdminController.php:1383 src/Admin/AdminController.php:1412
msgid "Set to lifetime"
msgstr ""
#: src/Admin/AdminController.php:1382
#: src/Admin/AdminController.php:1393
msgid "Test license against API"
msgstr ""
#: src/Admin/AdminController.php:1382
#: src/Admin/AdminController.php:1393
msgid "Test"
msgstr ""
#: src/Admin/AdminController.php:1389 src/Frontend/AccountController.php:207
#: src/Admin/AdminController.php:1400 src/Frontend/AccountController.php:207
msgid "Transfer to new domain"
msgstr ""
#: src/Admin/AdminController.php:1389 src/Frontend/AccountController.php:209
#: src/Admin/AdminController.php:1400 src/Frontend/AccountController.php:209
msgid "Transfer"
msgstr ""
#: src/Admin/AdminController.php:1395
#: src/Admin/AdminController.php:1406
msgid "Extend by 30 days"
msgstr ""
#: src/Admin/AdminController.php:1407
#: src/Admin/AdminController.php:1418
msgid "Are you sure?"
msgstr ""
#: src/Admin/AdminController.php:1416
#: src/Admin/AdminController.php:1427
msgid "Are you sure you want to delete this license?"
msgstr ""
#: src/Admin/AdminController.php:1476
#: src/Admin/AdminController.php:1487
msgid "License Validation Test"
msgstr ""
#: src/Admin/AdminController.php:1491
#: src/Admin/AdminController.php:1502
msgid "Testing license..."
msgstr ""
#: src/Admin/AdminController.php:1497 src/Frontend/AccountController.php:249
#: src/Admin/AdminController.php:1508 src/Frontend/AccountController.php:249
msgid "Close"
msgstr ""
#: src/Admin/AdminController.php:1506 src/Frontend/AccountController.php:250
#: src/Admin/AdminController.php:1517 src/Frontend/AccountController.php:250
msgid "Transfer License to New Domain"
msgstr ""
#: src/Admin/AdminController.php:1513 src/Frontend/AccountController.php:255
#: src/Admin/AdminController.php:1524 src/Frontend/AccountController.php:255
msgid "Current Domain"
msgstr ""
#: src/Admin/AdminController.php:1517 src/Frontend/AccountController.php:260
#: src/Admin/AdminController.php:1528 src/Frontend/AccountController.php:260
msgid "New Domain"
msgstr ""
#: src/Admin/AdminController.php:1520 src/Frontend/AccountController.php:264
#: src/Admin/AdminController.php:1531 src/Frontend/AccountController.php:264
msgid "Enter the new domain without http:// or www."
msgstr ""
#: src/Admin/AdminController.php:1525 src/Frontend/AccountController.php:269
#: src/Admin/AdminController.php:1536 src/Frontend/AccountController.php:269
msgid "Transfer License"
msgstr ""
#: src/Admin/AdminController.php:1595
#: src/Admin/AdminController.php:1606
msgid "License is VALID"
msgstr ""
#: src/Admin/AdminController.php:1598 src/Admin/VersionAdminController.php:81
#: src/Admin/AdminController.php:1609 src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:136
msgid "Version"
msgstr ""
#: src/Admin/AdminController.php:1606
#: src/Admin/AdminController.php:1617
msgid "License is INVALID"
msgstr ""
#: src/Admin/AdminController.php:1608
#: src/Admin/AdminController.php:1619
msgid "Error Code"
msgstr ""
#: src/Admin/AdminController.php:1609
#: src/Admin/AdminController.php:1620
msgid "Message"
msgstr ""
#: src/Admin/AdminController.php:1622
#: src/Admin/AdminController.php:1633
msgid "Failed to test license. Please try again."
msgstr ""
#: src/Admin/AdminController.php:1660 src/Admin/AdminController.php:1753
#: src/Admin/AdminController.php:1671 src/Admin/AdminController.php:1764
msgid "Import Licenses"
msgstr ""
#: src/Admin/AdminController.php:1662
#: src/Admin/AdminController.php:1673
msgid "Back to Licenses"
msgstr ""
#: src/Admin/AdminController.php:1672
#: src/Admin/AdminController.php:1683
msgid "Error uploading file. Please try again."
msgstr ""
#: src/Admin/AdminController.php:1675
#: src/Admin/AdminController.php:1686
msgid "Invalid file type. Please upload a CSV file."
msgstr ""
#: src/Admin/AdminController.php:1678
#: src/Admin/AdminController.php:1689
msgid "Error reading file. Please check the file format."
msgstr ""
#: src/Admin/AdminController.php:1681
#: src/Admin/AdminController.php:1692
msgid "An error occurred during import."
msgstr ""
#: src/Admin/AdminController.php:1689
#: src/Admin/AdminController.php:1700
msgid "Import Licenses from CSV"
msgstr ""
#: src/Admin/AdminController.php:1692
#: src/Admin/AdminController.php:1703
msgid ""
"Upload a CSV file to import licenses. You can use the exported CSV format or "
"a simplified format."
msgstr ""
#: src/Admin/AdminController.php:1695
#: src/Admin/AdminController.php:1706
msgid "CSV Format"
msgstr ""
#: src/Admin/AdminController.php:1697
#: src/Admin/AdminController.php:1708
msgid "The CSV file should contain the following columns:"
msgstr ""
#: src/Admin/AdminController.php:1701
#: src/Admin/AdminController.php:1712
msgid "Full Format (from Export):"
msgstr ""
#: src/Admin/AdminController.php:1704
#: src/Admin/AdminController.php:1715
msgid "Simplified Format:"
msgstr ""
#: src/Admin/AdminController.php:1709
#: src/Admin/AdminController.php:1720
msgid "Notes:"
msgstr ""
#: src/Admin/AdminController.php:1710
#: src/Admin/AdminController.php:1721
msgid "Leave License Key empty to auto-generate."
msgstr ""
#: src/Admin/AdminController.php:1711
#: src/Admin/AdminController.php:1722
msgid "Status can be: active, inactive, expired, revoked (defaults to active)."
msgstr ""
#: src/Admin/AdminController.php:1712
#: src/Admin/AdminController.php:1723
msgid "Expires At should be in YYYY-MM-DD format or \"Lifetime\"."
msgstr ""
#: src/Admin/AdminController.php:1724
#: src/Admin/AdminController.php:1735
msgid "CSV File"
msgstr ""
#: src/Admin/AdminController.php:1728
#: src/Admin/AdminController.php:1739
msgid "Select a CSV file to import."
msgstr ""
#: src/Admin/AdminController.php:1732
#: src/Admin/AdminController.php:1743
msgid "Options"
msgstr ""
#: src/Admin/AdminController.php:1736
#: src/Admin/AdminController.php:1747
msgid "Skip first row (header row)"
msgstr ""
#: src/Admin/AdminController.php:1741
#: src/Admin/AdminController.php:1752
msgid "Update existing licenses (by license key)"
msgstr ""
#: src/Admin/AdminController.php:1744
#: src/Admin/AdminController.php:1755
msgid ""
"If enabled, licenses with matching keys will be updated instead of skipped."
msgstr ""
#: src/Admin/AdminController.php:1771
#: src/Admin/AdminController.php:1782
msgid "License"
msgstr ""
#: src/Admin/AdminController.php:1830
#: src/Admin/AdminController.php:1841
msgid "No domain specified"
msgstr ""
@@ -1171,28 +1183,28 @@ msgstr ""
msgid "Too many requests. Please try again later."
msgstr ""
#: src/Api/RestApiController.php:222 src/Api/RestApiController.php:255
#: src/Api/RestApiController.php:345 src/Api/RestApiController.php:378
#: src/License/LicenseManager.php:357
msgid "License key not found."
msgstr ""
#: src/Api/RestApiController.php:263
#: src/Api/RestApiController.php:386
msgid "This license is not valid."
msgstr ""
#: src/Api/RestApiController.php:273
#: src/Api/RestApiController.php:396
msgid "License is already activated for this domain."
msgstr ""
#: src/Api/RestApiController.php:282
#: src/Api/RestApiController.php:405
msgid "Maximum number of activations reached."
msgstr ""
#: src/Api/RestApiController.php:293
#: src/Api/RestApiController.php:416
msgid "Failed to activate license."
msgstr ""
#: src/Api/RestApiController.php:299
#: src/Api/RestApiController.php:422
msgid "License activated successfully."
msgstr ""
@@ -1238,6 +1250,14 @@ msgstr ""
msgid "Domain for license activation"
msgstr ""
#: src/License/PluginLicenseChecker.php:117
msgid "License settings not configured."
msgstr ""
#: src/License/PluginLicenseChecker.php:153
msgid "Could not connect to license server."
msgstr ""
#: src/License/LicenseManager.php:366
msgid "This license has been revoked."
msgstr ""
@@ -1260,18 +1280,11 @@ msgstr ""
msgid "Unknown Product"
msgstr ""
#: src/License/PluginLicenseChecker.php:117
msgid "License settings not configured."
msgstr ""
#: src/License/PluginLicenseChecker.php:153
msgid "Could not connect to license server."
msgstr ""
#: src/Product/VersionManager.php:166
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"
@@ -1290,6 +1303,7 @@ msgstr ""
msgid "%d days"
msgstr ""
#. translators: %s: URL to settings page
#: src/Product/LicensedProductType.php:119
#, php-format
msgid "Leave fields empty to use default settings from %s."
@@ -1303,6 +1317,7 @@ msgstr ""
msgid "Max Activations"
msgstr ""
#. translators: %d: default max activations value
#: src/Product/LicensedProductType.php:131
#, php-format
msgid "Maximum number of domain activations per license. Default: %d"
@@ -1312,6 +1327,7 @@ msgstr ""
msgid "License Validity (Days)"
msgstr ""
#. translators: %s: default validity value
#: src/Product/LicensedProductType.php:149
#, php-format
msgid "Number of days the license is valid. Leave empty for default (%s)."
@@ -1321,6 +1337,7 @@ msgstr ""
msgid "Bind to Major Version"
msgstr ""
#. translators: %s: default bind to version value (Yes/No)
#: src/Product/LicensedProductType.php:167
#, php-format
msgid ""
@@ -1400,11 +1417,11 @@ msgid "You have no licenses yet."
msgstr ""
#: src/Frontend/AccountController.php:190
#: src/Email/LicenseExpirationEmail.php:207
#: src/Email/LicenseExpirationEmail.php:270
#: src/Email/LicenseEmailController.php:212
#: src/Email/LicenseEmailController.php:216
#: src/Email/LicenseEmailController.php:320
#: src/Email/LicenseExpirationEmail.php:207
#: src/Email/LicenseExpirationEmail.php:270
#: src/Email/LicenseExpiredEmail.php:191 src/Email/LicenseExpiredEmail.php:256
msgid "License Key:"
msgstr ""
@@ -1417,9 +1434,9 @@ msgid "Domain:"
msgstr ""
#: src/Frontend/AccountController.php:213
#: src/Email/LicenseEmailController.php:323
#: src/Email/LicenseExpirationEmail.php:219
#: src/Email/LicenseExpirationEmail.php:272
#: src/Email/LicenseEmailController.php:323
msgid "Expires:"
msgstr ""
@@ -1481,24 +1498,6 @@ msgstr ""
msgid "Failed to transfer license. Please try again."
msgstr ""
#: src/Email/LicenseEmailController.php:256
msgid "Your License Keys"
msgstr ""
#: src/Email/LicenseEmailController.php:260
#: src/Email/LicenseEmailController.php:315
msgid "Licensed Domain:"
msgstr ""
#: src/Email/LicenseEmailController.php:296
#: src/Email/LicenseEmailController.php:330
msgid "You can also view your licenses in your account under \"Licenses\"."
msgstr ""
#: src/Email/LicenseEmailController.php:311
msgid "YOUR LICENSE KEYS"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:55
msgid "License Expiration Warning"
msgstr ""
@@ -1561,6 +1560,7 @@ msgid ""
"expiration date."
msgstr ""
#. translators: %s: list of placeholders
#: src/Email/LicenseExpirationEmail.php:301
#: src/Email/LicenseExpiredEmail.php:288
#, php-format
@@ -1607,6 +1607,24 @@ msgstr ""
msgid "Choose which format of email to send."
msgstr ""
#: src/Email/LicenseEmailController.php:256
msgid "Your License Keys"
msgstr ""
#: src/Email/LicenseEmailController.php:260
#: src/Email/LicenseEmailController.php:315
msgid "Licensed Domain:"
msgstr ""
#: src/Email/LicenseEmailController.php:296
#: src/Email/LicenseEmailController.php:330
msgid "You can also view your licenses in your account under \"Licenses\"."
msgstr ""
#: src/Email/LicenseEmailController.php:311
msgid "YOUR LICENSE KEYS"
msgstr ""
#: src/Email/LicenseExpiredEmail.php:50 src/Email/LicenseExpiredEmail.php:76
msgid "License Expired"
msgstr ""
@@ -1648,19 +1666,20 @@ msgstr ""
msgid "To continue using this product, please renew your license."
msgstr ""
#: src/Plugin.php:257
#: src/Plugin.php:258
msgid "WC Licensed Product"
msgstr ""
#: src/Plugin.php:258
#: src/Plugin.php:259
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
#: src/Plugin.php:259
#: src/Plugin.php:260
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."

View File

@@ -572,6 +572,11 @@ final class AdminController
*/
private function handleCsvExport(): void
{
// Verify nonce for CSRF protection
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'export_licenses_csv')) {
wp_die(__('Security check failed.', 'wc-licensed-product'));
}
if (!current_user_can('manage_woocommerce')) {
wp_die(__('You do not have permission to export licenses.', 'wc-licensed-product'));
}
@@ -954,7 +959,7 @@ final class AdminController
<span class="dashicons dashicons-admin-network"></span>
<?php esc_html_e('Manage Licenses', 'wc-licensed-product'); ?>
</a>
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses&action=export_csv')); ?>" class="button">
<a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=wc-licenses&action=export_csv'), 'export_licenses_csv')); ?>" class="button">
<span class="dashicons dashicons-download"></span>
<?php esc_html_e('Export to CSV', 'wc-licensed-product'); ?>
</a>
@@ -1048,6 +1053,12 @@ final class AdminController
$this->twig->addFunction(new \Twig\TwigFunction('transfer_nonce', function (): string {
return wp_create_nonce('transfer_license');
}));
$this->twig->addFunction(new \Twig\TwigFunction('export_csv_url', function (): string {
return wp_nonce_url(
admin_url('admin.php?page=wc-licenses&action=export_csv'),
'export_licenses_csv'
);
}));
try {
echo $this->twig->render('admin/licenses.html.twig', [
@@ -1187,7 +1198,7 @@ final class AdminController
?>
<div class="wrap">
<h1 class="wp-heading-inline"><?php esc_html_e('Licenses', 'wc-licensed-product'); ?></h1>
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses&action=export_csv')); ?>" class="page-title-action">
<a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=wc-licenses&action=export_csv'), 'export_licenses_csv')); ?>" class="page-title-action">
<span class="dashicons dashicons-download" style="vertical-align: middle;"></span>
<?php esc_html_e('Export CSV', 'wc-licensed-product'); ?>
</a>

View File

@@ -94,8 +94,8 @@ final class ResponseSigner
$timestamp = time();
$signingKey = $this->deriveKey($licenseKey);
// Sort keys for consistent ordering
ksort($data);
// Recursively sort keys for consistent ordering (required by client implementation)
$data = $this->recursiveKeySort($data);
// Build signature payload
$payload = $timestamp . ':' . json_encode(
@@ -109,6 +109,33 @@ final class ResponseSigner
];
}
/**
* Recursively sort array keys alphabetically
*
* @param mixed $data The data to sort
* @return mixed The sorted data
*/
private function recursiveKeySort(mixed $data): mixed
{
if (!is_array($data)) {
return $data;
}
// Check if array is associative (has string keys)
$isAssociative = array_keys($data) !== range(0, count($data) - 1);
if ($isAssociative) {
ksort($data);
}
// Recursively sort nested arrays
foreach ($data as $key => $value) {
$data[$key] = $this->recursiveKeySort($value);
}
return $data;
}
/**
* Derive a unique signing key for a license
*

View File

@@ -95,29 +95,152 @@ final class RestApiController
/**
* Get client IP address
*
* Security note: Only trust proxy headers when explicitly configured.
* Set WC_LICENSE_TRUSTED_PROXIES constant or configure trusted_proxies
* in wp-config.php to enable proxy header support.
*
* @return string Client IP address
*/
private function getClientIp(): string
{
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'REMOTE_ADDR',
];
// Get the direct connection IP first
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
$ip = trim($ips[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
// Only check proxy headers if we're behind a trusted proxy
if ($this->isTrustedProxy($remoteAddr)) {
// Check headers in order of trust preference
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
$ip = trim($ips[0]);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
}
// Validate and return direct connection IP
if (filter_var($remoteAddr, FILTER_VALIDATE_IP)) {
return $remoteAddr;
}
return '0.0.0.0';
}
/**
* Check if the given IP is a trusted proxy
*
* @param string $ip The IP address to check
* @return bool Whether the IP is a trusted proxy
*/
private function isTrustedProxy(string $ip): bool
{
// Check if trusted proxies are configured
if (!defined('WC_LICENSE_TRUSTED_PROXIES')) {
return false;
}
$trustedProxies = WC_LICENSE_TRUSTED_PROXIES;
// Handle string constant (comma-separated list)
if (is_string($trustedProxies)) {
$trustedProxies = array_map('trim', explode(',', $trustedProxies));
}
if (!is_array($trustedProxies)) {
return false;
}
// Check for special keywords
if (in_array('CLOUDFLARE', $trustedProxies, true)) {
// Cloudflare IP ranges (simplified - in production, fetch from Cloudflare API)
if ($this->isCloudflareIp($ip)) {
return true;
}
}
// Check direct IP match or CIDR notation
foreach ($trustedProxies as $proxy) {
if ($proxy === $ip) {
return true;
}
// Support CIDR notation
if (str_contains($proxy, '/') && $this->ipMatchesCidr($ip, $proxy)) {
return true;
}
}
return false;
}
/**
* Check if IP is in Cloudflare range
*
* @param string $ip The IP to check
* @return bool Whether IP belongs to Cloudflare
*/
private function isCloudflareIp(string $ip): bool
{
// Cloudflare IPv4 ranges (as of 2024)
$cloudflareRanges = [
'173.245.48.0/20',
'103.21.244.0/22',
'103.22.200.0/22',
'103.31.4.0/22',
'141.101.64.0/18',
'108.162.192.0/18',
'190.93.240.0/20',
'188.114.96.0/20',
'197.234.240.0/22',
'198.41.128.0/17',
'162.158.0.0/15',
'104.16.0.0/13',
'104.24.0.0/14',
'172.64.0.0/13',
'131.0.72.0/22',
];
foreach ($cloudflareRanges as $range) {
if ($this->ipMatchesCidr($ip, $range)) {
return true;
}
}
return false;
}
/**
* Check if an IP matches a CIDR range
*
* @param string $ip The IP to check
* @param string $cidr The CIDR range (e.g., "192.168.1.0/24")
* @return bool Whether the IP matches the CIDR range
*/
private function ipMatchesCidr(string $ip, string $cidr): bool
{
[$subnet, $bits] = explode('/', $cidr);
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ||
!filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return false;
}
$ipLong = ip2long($ip);
$subnetLong = ip2long($subnet);
$mask = -1 << (32 - (int) $bits);
return ($ipLong & $mask) === ($subnetLong & $mask);
}
/**
* Register REST API routes
*/

View File

@@ -98,6 +98,7 @@ final class Plugin
$this->twig = new Environment($loader, [
'cache' => WP_CONTENT_DIR . '/cache/wc-licensed-product/twig',
'auto_reload' => true, // Always check for template changes
'autoescape' => 'html', // Explicitly enable HTML autoescape for XSS protection
]);
// Add WordPress functions as Twig functions

View File

@@ -1,6 +1,6 @@
<div class="wrap">
<h1 class="wp-heading-inline">{{ __('Licenses') }}</h1>
<a href="{{ admin_url }}?action=export_csv" class="page-title-action">
<a href="{{ export_csv_url() }}" class="page-title-action">
<span class="dashicons dashicons-download" style="vertical-align: middle;"></span>
{{ __('Export CSV') }}
</a>
@@ -143,8 +143,8 @@
</td>
<td class="wclp-editable-cell" data-field="status" data-license-id="{{ item.license.id }}">
<span class="wclp-display-value">
<span class="license-status license-status-{{ item.license.status }}">
{{ item.license.status|capitalize }}
<span class="license-status license-status-{{ esc_attr(item.license.status) }}">
{{ esc_html(item.license.status)|capitalize }}
</span>
</span>
<button type="button" class="wclp-edit-btn button-link" title="{{ __('Edit') }}">

View File

@@ -12,8 +12,8 @@
{{ esc_html(item.product_name) }}
{% endif %}
</h3>
<span class="license-status license-status-{{ item.license.status }}">
{{ item.license.status|capitalize }}
<span class="license-status license-status-{{ esc_attr(item.license.status) }}">
{{ esc_html(item.license.status)|capitalize }}
</span>
</div>

View File

@@ -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.3.5
* Version: 0.3.6
* 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.3.5');
define('WC_LICENSED_PRODUCT_VERSION', '0.3.6');
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__));