9 Commits

Author SHA1 Message Date
2ec3f42b1f Bump version to 0.4.0
- Add CHANGELOG entry for self-licensing prevention feature
- Update plugin header and constant to 0.4.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:42:39 +01:00
4817175f99 Add self-licensing prevention to PluginLicenseChecker
- Add isSelfLicensing() method to detect when license server URL points to same installation
- Bypass license validation when self-licensing detected (prevents circular dependency)
- Add normalizeDomain() helper for domain comparison
- Update translations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:41:56 +01:00
a4561057fa Update CLAUDE.md with v0.3.9 session history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:11:09 +01:00
d15c59b7c3 Add release package v0.3.9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:10:00 +01:00
4a90e6b18b Bump version to 0.3.9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:08:41 +01:00
502a8c7cd7 Update translation template with current line references
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:07:46 +01:00
6b83fce8b2 Fix admin order license generation bug
- Add 'Generate Licenses' button to order meta box for admin-created orders
- Add AJAX handler for manual license generation
- Show warning when domain is not set or order is not paid
- Handle partial license generation (when some products already have licenses)
- Update German translations for new strings (365 translated)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:06:13 +01:00
8c33eaff29 Clean up known bugs section after v0.3.8 fix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:38:19 +01:00
98002ae3d7 Update CLAUDE.md with v0.3.8 session history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:37:14 +01:00
11 changed files with 572 additions and 102 deletions

View File

@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.4.0] - 2026-01-24
### Added
- Self-licensing prevention: Plugin automatically bypasses license validation when the configured license server URL points to the same WordPress installation
- New `isSelfLicensing()` method in `PluginLicenseChecker` to detect circular licensing scenarios
- New `normalizeDomain()` helper method for domain comparison (strips www prefix, lowercases)
### Changed
- `isLicenseValid()` and `validateLicense()` now check for self-licensing before attempting validation
- Cache clearing now also clears the self-licensing check cache
### Technical Details
- Self-licensing detection compares normalized domains of license server URL and current site URL
- Prevents circular dependency where plugin would try to validate against itself
- Plugins can only be validated against the original store from which they were obtained
## [0.3.9] - 2026-01-24
### Added
- "Generate Licenses" button in order meta box for admin-created orders
- "Generate Missing Licenses" button when some products in an order are missing licenses
- AJAX handler `ajaxGenerateOrderLicenses()` for manual license generation from admin
- Warning message when order domain is not set before generating licenses
### Fixed
- **Critical:** Licenses are now generated for orders created manually in admin area
- Previously, licenses were only generated via checkout hooks, leaving admin-created orders without licenses
### Technical Details
- Added `wclp_generate_order_licenses` AJAX action to `OrderLicenseController`
- Updated `order-licenses.js` with generate button handler and page reload on success
- Added CSS styles for generate status messages
- Updated translations (365 strings)
## [0.3.8] - 2026-01-24
### Fixed

View File

@@ -36,13 +36,9 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
No known bugs at the moment.
### Version 0.3.8
No changes at the moment.
### Version 0.4.0
No changes at the moment.
- Self-licensing prevention: Plugin automatically bypasses license validation when the configured license server URL points to the same WordPress installation (prevents circular dependency)
## Technical Stack
@@ -1091,3 +1087,75 @@ Fixed dashboard widget bugs, improved UI consistency, and added download trackin
- Created release package: `releases/wc-licensed-product-0.3.7.zip` (827 KB)
- SHA256: `e93b2ab06f6d43c2179167090e07eda5db6809df6e391baece4ceba321cf33f6`
- Tagged as `v0.3.7` and pushed to `main` branch
### 2026-01-24 - Version 0.3.8 - Translation Bug Fix
**Overview:**
Fixed a critical translation bug that caused the settings page to crash with an `ArgumentCountError`.
**Bug Fix:**
- Fixed: Duplicate German translation string in `wc-licensed-product-de_CH.po` causing `ArgumentCountError` in settings page
- Root cause: The notification settings description was duplicated in the translation, resulting in two `%s` placeholders when only one argument was passed to `sprintf()`
- Location: [wc-licensed-product-de_CH.po:322-328](languages/wc-licensed-product-de_CH.po#L322-L328)
**Modified files:**
- `languages/wc-licensed-product-de_CH.po` - Removed duplicated translation string
- `languages/wc-licensed-product-de_CH.mo` - Recompiled binary translation
**Technical notes:**
- Error was logged to `tmp/fatal-errors-2026-01-24.log`
- The German `msgstr` contained the same text twice, each with a `%s` placeholder
- `sprintf()` at `SettingsController.php:221` only provided one argument for the single `%s` in the English source
- Translation strings with `%s` placeholders must have exactly matching placeholder counts between source and translation
**Dependency Updates:**
- Updated `magdev/wc-licensed-product-client` from `9f513a8` to `64d215c`
**Release v0.3.8:**
- Created release package: `releases/wc-licensed-product-0.3.8.zip` (829 KB)
- SHA256: `50ad6966c5ab8db2257572084d2d8a820448df62615678e1576696f2c0cb383d`
- Tagged as `v0.3.8` and pushed to `main` branch
### 2026-01-24 - Version 0.3.9 - Admin Order License Generation Fix
**Overview:**
Fixed a critical bug where licenses were not generated for orders created manually in the WordPress admin area.
**Bug Fix:**
- **Critical:** Licenses are now generated for orders created manually in admin area
- Previously, licenses were only generated via checkout hooks (`woocommerce_order_status_completed`, `woocommerce_order_status_processing`, `woocommerce_payment_complete`)
- Admin-created orders bypassed checkout, so the `_licensed_product_domain` meta was never set and licenses were never generated
**Implemented:**
- "Generate Licenses" button in order meta box for admin-created orders
- "Generate Missing Licenses" button when some products in an order already have licenses
- Warning message when order domain is not set before generating licenses
- AJAX handler `ajaxGenerateOrderLicenses()` for manual license generation
**Modified files:**
- `src/Admin/OrderLicenseController.php` - Added Generate button, AJAX handler, CSS styles
- `assets/js/order-licenses.js` - Added `generateLicenses()` function with page reload on success
**Technical notes:**
- Button only appears when order is paid and domain is set
- Uses existing `LicenseManager::generateLicense()` which handles duplicate prevention
- Page reloads after successful generation to show new licenses in table
- Tracks generated vs skipped licenses for accurate feedback messages
- Updated translations (365 strings)
**Release v0.3.9:**
- Created release package: `releases/wc-licensed-product-0.3.9.zip` (851 KB)
- SHA256: `fdb65200c368da380df0cabb3c6ac6419d5b4731cd528f630f9b432a3ba5c586`
- Tagged as `v0.3.9` and pushed to `main` branch

View File

@@ -16,6 +16,9 @@
// Order domain save
$('#wclp-save-order-domain').on('click', this.saveOrderDomain.bind(this));
// Generate licenses button
$(document).on('click', '#wclp-generate-licenses', this.generateLicenses.bind(this));
// License domain edit/save/cancel
$(document).on('click', '.wclp-edit-domain-btn', this.startEditDomain);
$(document).on('click', '.wclp-save-domain-btn', this.saveLicenseDomain.bind(this));
@@ -135,6 +138,54 @@
$editBtn.show();
},
/**
* Generate licenses for order
*/
generateLicenses: function(e) {
e.preventDefault();
var $btn = $(e.currentTarget);
var $spinner = $btn.siblings('.spinner');
var $status = $btn.siblings('.wclp-generate-status');
var orderId = $btn.data('order-id');
$btn.prop('disabled', true);
$spinner.addClass('is-active');
$status.text('').removeClass('success error');
$.ajax({
url: wclpOrderLicenses.ajaxUrl,
type: 'POST',
data: {
action: 'wclp_generate_order_licenses',
nonce: wclpOrderLicenses.nonce,
order_id: orderId
},
success: function(response) {
if (response.success) {
$status.text(response.data.message).addClass('success');
if (response.data.reload) {
// Reload the page after a short delay to show the new licenses
setTimeout(function() {
window.location.reload();
}, 1500);
}
} else {
$status.text(response.data.message || wclpOrderLicenses.strings.error).addClass('error');
$btn.prop('disabled', false);
}
},
error: function() {
$status.text(wclpOrderLicenses.strings.error).addClass('error');
$btn.prop('disabled', false);
},
complete: function() {
$spinner.removeClass('is-active');
}
});
},
/**
* Save license domain
*/

View File

@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: WC Licensed Product 0.3.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-24 10:18+0100\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-24 16:39+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"
@@ -17,8 +17,8 @@ msgstr ""
#: src/Admin/AdminController.php:76 src/Admin/AdminController.php:77
#: src/Admin/AdminController.php:90 src/Admin/AdminController.php:1200
#: src/Admin/OrderLicenseController.php:127
#: src/Admin/OrderLicenseController.php:222
#: src/Admin/OrderLicenseController.php:128
#: src/Admin/OrderLicenseController.php:249
#: src/Frontend/AccountController.php:90
msgid "Licenses"
msgstr "Lizenzen"
@@ -39,7 +39,7 @@ msgstr "Suche..."
msgid "Search failed"
msgstr "Suche fehlgeschlagen"
#: src/Admin/AdminController.php:144 src/Admin/OrderLicenseController.php:285
#: src/Admin/AdminController.php:144 src/Admin/OrderLicenseController.php:338
msgid "Saving..."
msgstr "Speichere..."
@@ -65,22 +65,22 @@ msgstr "Bearbeiten"
#: src/Admin/AdminController.php:149 src/Admin/AdminController.php:1341
#: src/Admin/AdminController.php:1361 src/Admin/AdminController.php:1382
#: src/Admin/AdminController.php:1537 src/Admin/OrderLicenseController.php:185
#: src/Admin/AdminController.php:1537 src/Admin/OrderLicenseController.php:212
#: src/Frontend/AccountController.php:271
msgid "Cancel"
msgstr "Abbrechen"
#: src/Admin/AdminController.php:150 src/Admin/AdminController.php:1340
#: src/Admin/AdminController.php:1360 src/Admin/AdminController.php:1381
#: src/Admin/OrderLicenseController.php:118
#: src/Admin/OrderLicenseController.php:182
#: src/Admin/OrderLicenseController.php:119
#: src/Admin/OrderLicenseController.php:209
msgid "Save"
msgstr "Speichern"
#: src/Admin/AdminController.php:151 src/Admin/AdminController.php:266
#: src/Admin/AdminController.php:1373 src/Admin/AdminController.php:1613
#: src/Admin/DashboardWidgetController.php:136
#: src/Admin/OrderLicenseController.php:201
#: src/Admin/OrderLicenseController.php:228
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:110
#: src/Product/LicensedProductType.php:158
msgid "Lifetime"
@@ -124,8 +124,9 @@ msgstr "Widerrufen"
#: src/Admin/AdminController.php:173 src/Admin/AdminController.php:213
#: src/Admin/AdminController.php:249 src/Admin/AdminController.php:301
#: src/Admin/AdminController.php:339 src/Admin/AdminController.php:369
#: src/Admin/OrderLicenseController.php:301
#: src/Admin/OrderLicenseController.php:340
#: src/Admin/OrderLicenseController.php:355
#: src/Admin/OrderLicenseController.php:394
#: src/Admin/OrderLicenseController.php:458
#: src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:354
@@ -133,7 +134,7 @@ msgid "Permission denied."
msgstr "Zugriff verweigert."
#: src/Admin/AdminController.php:195 src/Admin/AdminController.php:1019
#: src/Admin/OrderLicenseController.php:168
#: src/Admin/OrderLicenseController.php:195
msgid "Unknown"
msgstr "Unbekannt"
@@ -143,7 +144,7 @@ msgstr "Gast"
#: src/Admin/AdminController.php:220 src/Admin/AdminController.php:256
#: src/Admin/AdminController.php:308 src/Admin/AdminController.php:345
#: src/Admin/OrderLicenseController.php:347
#: src/Admin/OrderLicenseController.php:401
msgid "Invalid license ID."
msgstr "Ungültige Lizenz-ID."
@@ -175,7 +176,7 @@ msgstr "Ablaufdatum erfolgreich aktualisiert."
msgid "Invalid date format."
msgstr "Ungültiges Datumsformat."
#: src/Admin/AdminController.php:312 src/Admin/OrderLicenseController.php:351
#: src/Admin/AdminController.php:312 src/Admin/OrderLicenseController.php:405
msgid "Domain cannot be empty."
msgstr "Domain darf nicht leer sein."
@@ -470,14 +471,14 @@ msgid "Apply"
msgstr "Anwenden"
#: src/Admin/AdminController.php:1291 src/Admin/AdminController.php:1442
#: src/Admin/AdminController.php:1491 src/Admin/OrderLicenseController.php:144
#: src/Admin/AdminController.php:1491 src/Admin/OrderLicenseController.php:171
#: src/Admin/SettingsController.php:142
#: src/Email/LicenseEmailController.php:269
msgid "License Key"
msgstr "Lizenzschlüssel"
#: src/Admin/AdminController.php:1292 src/Admin/AdminController.php:1443
#: src/Admin/AdminController.php:1608 src/Admin/OrderLicenseController.php:145
#: src/Admin/AdminController.php:1608 src/Admin/OrderLicenseController.php:172
#: src/Email/LicenseEmailController.php:268
msgid "Product"
msgstr "Produkt"
@@ -489,12 +490,12 @@ msgid "Customer"
msgstr "Kunde"
#: src/Admin/AdminController.php:1294 src/Admin/AdminController.php:1445
#: src/Admin/AdminController.php:1495 src/Admin/OrderLicenseController.php:146
#: src/Admin/AdminController.php:1495 src/Admin/OrderLicenseController.php:173
msgid "Domain"
msgstr "Domain"
#: src/Admin/AdminController.php:1295 src/Admin/AdminController.php:1446
#: src/Admin/OrderLicenseController.php:147
#: src/Admin/OrderLicenseController.php:174
#: src/Admin/VersionAdminController.php:140
msgid "Status"
msgstr "Status"
@@ -505,13 +506,13 @@ msgstr "Erstellt"
#: src/Admin/AdminController.php:1297 src/Admin/AdminController.php:1448
#: src/Admin/AdminController.php:1611 src/Admin/AdminController.php:1613
#: src/Admin/OrderLicenseController.php:148
#: src/Admin/OrderLicenseController.php:175
#: src/Email/LicenseEmailController.php:270
msgid "Expires"
msgstr "Läuft ab"
#: src/Admin/AdminController.php:1298 src/Admin/AdminController.php:1449
#: src/Admin/OrderLicenseController.php:149
#: src/Admin/OrderLicenseController.php:176
#: src/Admin/VersionAdminController.php:142
msgid "Actions"
msgstr "Aktionen"
@@ -758,24 +759,25 @@ msgstr "Noch keine Downloads"
msgid "Top Versions"
msgstr "Top-Versionen"
#: src/Admin/OrderLicenseController.php:56
#: src/Admin/OrderLicenseController.php:57
msgid "Product Licenses"
msgstr "Produktlizenzen"
#: src/Admin/OrderLicenseController.php:77
#: src/Admin/OrderLicenseController.php:313
#: src/Admin/OrderLicenseController.php:78
#: src/Admin/OrderLicenseController.php:367
#: src/Admin/OrderLicenseController.php:469
msgid "Order not found."
msgstr "Bestellung nicht gefunden."
#: src/Admin/OrderLicenseController.php:92
#: src/Admin/OrderLicenseController.php:93
msgid "This order does not contain licensed products."
msgstr "Diese Bestellung enthält keine lizensierten Produkte."
#: src/Admin/OrderLicenseController.php:106
#: src/Admin/OrderLicenseController.php:107
msgid "Order Domain"
msgstr "Bestellungs-Domain"
#: src/Admin/OrderLicenseController.php:108
#: src/Admin/OrderLicenseController.php:109
msgid ""
"The domain specified during checkout. Changing this will not automatically "
"update existing license domains."
@@ -783,17 +785,17 @@ msgstr ""
"Die bei der Bestellung angegebene Domain. Eine Änderung aktualisiert nicht "
"automatisch bestehende Lizenz-Domains."
#: src/Admin/OrderLicenseController.php:116
#: src/Admin/OrderLicenseController.php:117
#: src/Checkout/CheckoutBlocksIntegration.php:102
#: src/Checkout/CheckoutController.php:89
msgid "example.com"
msgstr "beispiel.ch"
#: src/Admin/OrderLicenseController.php:131
#: src/Admin/OrderLicenseController.php:144
msgid "No licenses have been generated for this order yet."
msgstr "Für diese Bestellung wurden noch keine Lizenzen generiert."
#: src/Admin/OrderLicenseController.php:134
#: src/Admin/OrderLicenseController.php:147
msgid ""
"Licenses should be generated automatically when an order is paid. If "
"missing, check that a domain was specified during checkout."
@@ -802,68 +804,115 @@ msgstr ""
"wird. Falls fehlend, prüfen Sie, ob bei der Bestellung eine Domain angegeben "
"wurde."
#: src/Admin/OrderLicenseController.php:137
#: src/Admin/OrderLicenseController.php:150
msgid "Licenses will be generated when the order is marked as paid/completed."
msgstr ""
"Lizenzen werden generiert, sobald die Bestellung als bezahlt/abgeschlossen "
"markiert wird."
#: src/Admin/OrderLicenseController.php:178
#: src/Admin/OrderLicenseController.php:156
msgid "Generate Licenses"
msgstr "Lizenzen generieren"
#: src/Admin/OrderLicenseController.php:164
msgid "Please set the order domain above before generating licenses."
msgstr ""
"Bitte legen Sie zuerst die Bestellungs-Domain oben fest, bevor Sie Lizenzen "
"generieren."
#: src/Admin/OrderLicenseController.php:205
msgid "Edit domain"
msgstr "Domain bearbeiten"
#: src/Admin/OrderLicenseController.php:208
#: src/Admin/OrderLicenseController.php:235
msgid "View in Licenses"
msgstr "In Lizenzen anzeigen"
#: src/Admin/OrderLicenseController.php:221
#: src/Admin/OrderLicenseController.php:248
#, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page."
msgstr ""
"Für weitere Aktionen (widerrufen, verlängern, löschen), gehen Sie zur Seite "
"%s."
#: src/Admin/OrderLicenseController.php:286
#: src/Admin/OrderLicenseController.php:261
#, php-format
msgid "%d licensed product is missing a license."
msgid_plural "%d licensed products are missing licenses."
msgstr[0] "%d lizenziertes Produkt hat keine Lizenz."
msgstr[1] "%d lizenzierte Produkte haben keine Lizenzen."
#: src/Admin/OrderLicenseController.php:270
msgid "Generate Missing Licenses"
msgstr "Fehlende Lizenzen generieren"
#: src/Admin/OrderLicenseController.php:339
msgid "Saved!"
msgstr "Gespeichert!"
#: src/Admin/OrderLicenseController.php:287
msgid "Error saving. Please try again."
msgstr "Fehler beim Speichern. Bitte versuchen Sie es erneut."
#: src/Admin/OrderLicenseController.php:340
msgid "Error. Please try again."
msgstr "Fehler. Bitte versuchen Sie es erneut."
#: src/Admin/OrderLicenseController.php:288
#: src/Admin/OrderLicenseController.php:341
#: src/Frontend/AccountController.php:314
#: src/Frontend/AccountController.php:346
msgid "Please enter a valid domain."
msgstr "Bitte geben Sie eine gültige Domain ein."
#: src/Admin/OrderLicenseController.php:308
#: src/Admin/OrderLicenseController.php:342
msgid "Generating..."
msgstr "Generiere..."
#: src/Admin/OrderLicenseController.php:362
#: src/Admin/OrderLicenseController.php:464
msgid "Invalid order ID."
msgstr "Ungültige Bestellungs-ID."
#: src/Admin/OrderLicenseController.php:319
#: src/Admin/OrderLicenseController.php:357
#: src/Admin/OrderLicenseController.php:373
#: src/Admin/OrderLicenseController.php:411
msgid "Invalid domain format."
msgstr "Ungültiges Domain-Format."
#: src/Admin/OrderLicenseController.php:327
#: src/Admin/OrderLicenseController.php:381
msgid "Order domain updated."
msgstr "Bestellungs-Domain aktualisiert."
#: src/Admin/OrderLicenseController.php:363
#: src/Admin/OrderLicenseController.php:417
#: src/Frontend/AccountController.php:352
#: src/Frontend/DownloadController.php:117
msgid "License not found."
msgstr "Lizenz nicht gefunden."
#: src/Admin/OrderLicenseController.php:371
#: src/Admin/OrderLicenseController.php:425
msgid "License domain updated."
msgstr "Lizenz-Domain aktualisiert."
#: src/Admin/OrderLicenseController.php:375
#: src/Admin/OrderLicenseController.php:429
msgid "Failed to update license domain."
msgstr "Lizenz-Domain konnte nicht aktualisiert werden."
#: src/Admin/OrderLicenseController.php:474
msgid "Order must be paid before licenses can be generated."
msgstr ""
"Die Bestellung muss bezahlt sein, bevor Lizenzen generiert werden können."
#: src/Admin/OrderLicenseController.php:480
msgid "Please set the order domain before generating licenses."
msgstr ""
"Bitte legen Sie die Bestellungs-Domain fest, bevor Sie Lizenzen generieren."
#: src/Admin/OrderLicenseController.php:521
#, php-format
msgid "%d license generated successfully."
msgid_plural "%d licenses generated successfully."
msgstr[0] "%d Lizenz erfolgreich generiert."
msgstr[1] "%d Lizenzen erfolgreich generiert."
#: src/Admin/OrderLicenseController.php:534
msgid "All licenses already exist for this order."
msgstr "Alle Lizenzen für diese Bestellung existieren bereits."
#: src/Admin/SettingsController.php:54
msgid "Licensed Products"
msgstr "Lizensierte Produkte"
@@ -1617,11 +1666,11 @@ msgstr "Diese Lizenz ist inaktiv."
msgid "This license is not valid for this domain."
msgstr "Diese Lizenz ist für diese Domain nicht gültig."
#: src/License/PluginLicenseChecker.php:117
#: src/License/PluginLicenseChecker.php:132
msgid "License settings not configured."
msgstr "Lizenzeinstellungen nicht konfiguriert."
#: src/License/PluginLicenseChecker.php:153
#: src/License/PluginLicenseChecker.php:168
msgid "Could not connect to license server."
msgstr "Verbindung zum Lizenzserver konnte nicht hergestellt werden."

View File

@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: WC Licensed Product 0.3.7\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-24 10:18+0100\n"
"Project-Id-Version: WC Licensed Product 0.4.0\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-24 16:39+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"
@@ -20,8 +20,8 @@ msgstr ""
#: src/Admin/AdminController.php:76 src/Admin/AdminController.php:77
#: src/Admin/AdminController.php:90 src/Admin/AdminController.php:1200
#: src/Admin/OrderLicenseController.php:127
#: src/Admin/OrderLicenseController.php:222
#: src/Admin/OrderLicenseController.php:128
#: src/Admin/OrderLicenseController.php:249
#: src/Frontend/AccountController.php:90
msgid "Licenses"
msgstr ""
@@ -42,7 +42,7 @@ msgstr ""
msgid "Search failed"
msgstr ""
#: src/Admin/AdminController.php:144 src/Admin/OrderLicenseController.php:285
#: src/Admin/AdminController.php:144 src/Admin/OrderLicenseController.php:338
msgid "Saving..."
msgstr ""
@@ -66,22 +66,22 @@ msgstr ""
#: src/Admin/AdminController.php:149 src/Admin/AdminController.php:1341
#: src/Admin/AdminController.php:1361 src/Admin/AdminController.php:1382
#: src/Admin/AdminController.php:1537 src/Admin/OrderLicenseController.php:185
#: src/Admin/AdminController.php:1537 src/Admin/OrderLicenseController.php:212
#: src/Frontend/AccountController.php:271
msgid "Cancel"
msgstr ""
#: src/Admin/AdminController.php:150 src/Admin/AdminController.php:1340
#: src/Admin/AdminController.php:1360 src/Admin/AdminController.php:1381
#: src/Admin/OrderLicenseController.php:118
#: src/Admin/OrderLicenseController.php:182
#: src/Admin/OrderLicenseController.php:119
#: src/Admin/OrderLicenseController.php:209
msgid "Save"
msgstr ""
#: src/Admin/AdminController.php:151 src/Admin/AdminController.php:266
#: src/Admin/AdminController.php:1373 src/Admin/AdminController.php:1613
#: src/Admin/DashboardWidgetController.php:136
#: src/Admin/OrderLicenseController.php:201
#: src/Admin/OrderLicenseController.php:228
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:110
#: src/Product/LicensedProductType.php:158
msgid "Lifetime"
@@ -125,8 +125,9 @@ msgstr ""
#: src/Admin/AdminController.php:173 src/Admin/AdminController.php:213
#: src/Admin/AdminController.php:249 src/Admin/AdminController.php:301
#: src/Admin/AdminController.php:339 src/Admin/AdminController.php:369
#: src/Admin/OrderLicenseController.php:301
#: src/Admin/OrderLicenseController.php:340
#: src/Admin/OrderLicenseController.php:355
#: src/Admin/OrderLicenseController.php:394
#: src/Admin/OrderLicenseController.php:458
#: src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:354
@@ -134,7 +135,7 @@ msgid "Permission denied."
msgstr ""
#: src/Admin/AdminController.php:195 src/Admin/AdminController.php:1019
#: src/Admin/OrderLicenseController.php:168
#: src/Admin/OrderLicenseController.php:195
msgid "Unknown"
msgstr ""
@@ -144,7 +145,7 @@ msgstr ""
#: src/Admin/AdminController.php:220 src/Admin/AdminController.php:256
#: src/Admin/AdminController.php:308 src/Admin/AdminController.php:345
#: src/Admin/OrderLicenseController.php:347
#: src/Admin/OrderLicenseController.php:401
msgid "Invalid license ID."
msgstr ""
@@ -176,7 +177,7 @@ msgstr ""
msgid "Invalid date format."
msgstr ""
#: src/Admin/AdminController.php:312 src/Admin/OrderLicenseController.php:351
#: src/Admin/AdminController.php:312 src/Admin/OrderLicenseController.php:405
msgid "Domain cannot be empty."
msgstr ""
@@ -469,14 +470,14 @@ msgid "Apply"
msgstr ""
#: src/Admin/AdminController.php:1291 src/Admin/AdminController.php:1442
#: src/Admin/AdminController.php:1491 src/Admin/OrderLicenseController.php:144
#: src/Admin/AdminController.php:1491 src/Admin/OrderLicenseController.php:171
#: src/Admin/SettingsController.php:142
#: src/Email/LicenseEmailController.php:269
msgid "License Key"
msgstr ""
#: src/Admin/AdminController.php:1292 src/Admin/AdminController.php:1443
#: src/Admin/AdminController.php:1608 src/Admin/OrderLicenseController.php:145
#: src/Admin/AdminController.php:1608 src/Admin/OrderLicenseController.php:172
#: src/Email/LicenseEmailController.php:268
msgid "Product"
msgstr ""
@@ -488,12 +489,12 @@ msgid "Customer"
msgstr ""
#: src/Admin/AdminController.php:1294 src/Admin/AdminController.php:1445
#: src/Admin/AdminController.php:1495 src/Admin/OrderLicenseController.php:146
#: src/Admin/AdminController.php:1495 src/Admin/OrderLicenseController.php:173
msgid "Domain"
msgstr ""
#: src/Admin/AdminController.php:1295 src/Admin/AdminController.php:1446
#: src/Admin/OrderLicenseController.php:147
#: src/Admin/OrderLicenseController.php:174
#: src/Admin/VersionAdminController.php:140
msgid "Status"
msgstr ""
@@ -504,13 +505,13 @@ msgstr ""
#: src/Admin/AdminController.php:1297 src/Admin/AdminController.php:1448
#: src/Admin/AdminController.php:1611 src/Admin/AdminController.php:1613
#: src/Admin/OrderLicenseController.php:148
#: src/Admin/OrderLicenseController.php:175
#: src/Email/LicenseEmailController.php:270
msgid "Expires"
msgstr ""
#: src/Admin/AdminController.php:1298 src/Admin/AdminController.php:1449
#: src/Admin/OrderLicenseController.php:149
#: src/Admin/OrderLicenseController.php:176
#: src/Admin/VersionAdminController.php:142
msgid "Actions"
msgstr ""
@@ -752,103 +753,147 @@ msgstr ""
msgid "Top Versions"
msgstr ""
#: src/Admin/OrderLicenseController.php:56
#: src/Admin/OrderLicenseController.php:57
msgid "Product Licenses"
msgstr ""
#: src/Admin/OrderLicenseController.php:77
#: src/Admin/OrderLicenseController.php:313
#: src/Admin/OrderLicenseController.php:78
#: src/Admin/OrderLicenseController.php:367
#: src/Admin/OrderLicenseController.php:469
msgid "Order not found."
msgstr ""
#: src/Admin/OrderLicenseController.php:92
#: src/Admin/OrderLicenseController.php:93
msgid "This order does not contain licensed products."
msgstr ""
#: src/Admin/OrderLicenseController.php:106
#: src/Admin/OrderLicenseController.php:107
msgid "Order Domain"
msgstr ""
#: src/Admin/OrderLicenseController.php:108
#: src/Admin/OrderLicenseController.php:109
msgid ""
"The domain specified during checkout. Changing this will not automatically "
"update existing license domains."
msgstr ""
#: src/Admin/OrderLicenseController.php:116
#: src/Admin/OrderLicenseController.php:117
#: src/Checkout/CheckoutBlocksIntegration.php:102
#: src/Checkout/CheckoutController.php:89
msgid "example.com"
msgstr ""
#: src/Admin/OrderLicenseController.php:131
#: src/Admin/OrderLicenseController.php:144
msgid "No licenses have been generated for this order yet."
msgstr ""
#: src/Admin/OrderLicenseController.php:134
#: src/Admin/OrderLicenseController.php:147
msgid ""
"Licenses should be generated automatically when an order is paid. If "
"missing, check that a domain was specified during checkout."
msgstr ""
#: src/Admin/OrderLicenseController.php:137
#: src/Admin/OrderLicenseController.php:150
msgid "Licenses will be generated when the order is marked as paid/completed."
msgstr ""
#: src/Admin/OrderLicenseController.php:178
#: src/Admin/OrderLicenseController.php:156
msgid "Generate Licenses"
msgstr ""
#: src/Admin/OrderLicenseController.php:164
msgid "Please set the order domain above before generating licenses."
msgstr ""
#: src/Admin/OrderLicenseController.php:205
msgid "Edit domain"
msgstr ""
#: src/Admin/OrderLicenseController.php:208
#: src/Admin/OrderLicenseController.php:235
msgid "View in Licenses"
msgstr ""
#: src/Admin/OrderLicenseController.php:221
#: src/Admin/OrderLicenseController.php:248
#, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page."
msgstr ""
#: src/Admin/OrderLicenseController.php:286
#: src/Admin/OrderLicenseController.php:261
#, php-format
msgid "%d licensed product is missing a license."
msgid_plural "%d licensed products are missing licenses."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/OrderLicenseController.php:270
msgid "Generate Missing Licenses"
msgstr ""
#: src/Admin/OrderLicenseController.php:339
msgid "Saved!"
msgstr ""
#: src/Admin/OrderLicenseController.php:287
msgid "Error saving. Please try again."
#: src/Admin/OrderLicenseController.php:340
msgid "Error. Please try again."
msgstr ""
#: src/Admin/OrderLicenseController.php:288
#: src/Admin/OrderLicenseController.php:341
#: src/Frontend/AccountController.php:314
#: src/Frontend/AccountController.php:346
msgid "Please enter a valid domain."
msgstr ""
#: src/Admin/OrderLicenseController.php:308
#: src/Admin/OrderLicenseController.php:342
msgid "Generating..."
msgstr ""
#: src/Admin/OrderLicenseController.php:362
#: src/Admin/OrderLicenseController.php:464
msgid "Invalid order ID."
msgstr ""
#: src/Admin/OrderLicenseController.php:319
#: src/Admin/OrderLicenseController.php:357
#: src/Admin/OrderLicenseController.php:373
#: src/Admin/OrderLicenseController.php:411
msgid "Invalid domain format."
msgstr ""
#: src/Admin/OrderLicenseController.php:327
#: src/Admin/OrderLicenseController.php:381
msgid "Order domain updated."
msgstr ""
#: src/Admin/OrderLicenseController.php:363
#: src/Admin/OrderLicenseController.php:417
#: src/Frontend/AccountController.php:352
#: src/Frontend/DownloadController.php:117
msgid "License not found."
msgstr ""
#: src/Admin/OrderLicenseController.php:371
#: src/Admin/OrderLicenseController.php:425
msgid "License domain updated."
msgstr ""
#: src/Admin/OrderLicenseController.php:375
#: src/Admin/OrderLicenseController.php:429
msgid "Failed to update license domain."
msgstr ""
#: src/Admin/OrderLicenseController.php:474
msgid "Order must be paid before licenses can be generated."
msgstr ""
#: src/Admin/OrderLicenseController.php:480
msgid "Please set the order domain before generating licenses."
msgstr ""
#: src/Admin/OrderLicenseController.php:521
#, php-format
msgid "%d license generated successfully."
msgid_plural "%d licenses generated successfully."
msgstr[0] ""
msgstr[1] ""
#: src/Admin/OrderLicenseController.php:534
msgid "All licenses already exist for this order."
msgstr ""
#: src/Admin/SettingsController.php:54
msgid "Licensed Products"
msgstr ""
@@ -1563,11 +1608,11 @@ msgstr ""
msgid "This license is not valid for this domain."
msgstr ""
#: src/License/PluginLicenseChecker.php:117
#: src/License/PluginLicenseChecker.php:132
msgid "License settings not configured."
msgstr ""
#: src/License/PluginLicenseChecker.php:153
#: src/License/PluginLicenseChecker.php:168
msgid "Could not connect to license server."
msgstr ""

Binary file not shown.

View File

@@ -0,0 +1 @@
fdb65200c368da380df0cabb3c6ac6419d5b4731cd528f630f9b432a3ba5c586 releases/wc-licensed-product-0.3.9.zip

View File

@@ -36,6 +36,7 @@ final class OrderLicenseController
// Handle AJAX actions
add_action('wp_ajax_wclp_update_order_domain', [$this, 'ajaxUpdateOrderDomain']);
add_action('wp_ajax_wclp_update_license_domain', [$this, 'ajaxUpdateLicenseDomain']);
add_action('wp_ajax_wclp_generate_order_licenses', [$this, 'ajaxGenerateOrderLicenses']);
// Enqueue admin scripts
add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']);
@@ -126,6 +127,18 @@ final class OrderLicenseController
<h4><?php esc_html_e('Licenses', 'wc-licensed-product'); ?></h4>
<?php
// Count licensed products to check if all have licenses
$licensedProductCount = 0;
foreach ($order->get_items() as $item) {
$product = $item->get_product();
if ($product && $product->is_type('licensed')) {
$licensedProductCount++;
}
}
$missingLicenses = $licensedProductCount - count($licenses);
?>
<?php if (empty($licenses)): ?>
<p class="description">
<?php esc_html_e('No licenses have been generated for this order yet.', 'wc-licensed-product'); ?>
@@ -137,6 +150,20 @@ final class OrderLicenseController
<em><?php esc_html_e('Licenses will be generated when the order is marked as paid/completed.', 'wc-licensed-product'); ?></em>
<?php endif; ?>
</p>
<?php if ($orderDomain && $order->is_paid()): ?>
<p style="margin-top: 10px;">
<button type="button" class="button button-primary" id="wclp-generate-licenses" data-order-id="<?php echo esc_attr($order->get_id()); ?>">
<?php esc_html_e('Generate Licenses', 'wc-licensed-product'); ?>
</button>
<span class="spinner" style="float: none; margin-top: 4px;"></span>
<span class="wclp-generate-status"></span>
</p>
<?php elseif (!$orderDomain): ?>
<p class="description" style="margin-top: 10px; color: #d63638;">
<span class="dashicons dashicons-warning"></span>
<?php esc_html_e('Please set the order domain above before generating licenses.', 'wc-licensed-product'); ?>
</p>
<?php endif; ?>
<?php else: ?>
<table class="widefat striped wclp-licenses-table">
<thead>
@@ -223,6 +250,29 @@ final class OrderLicenseController
);
?>
</p>
<?php if ($missingLicenses > 0 && $orderDomain && $order->is_paid()): ?>
<p style="margin-top: 10px;">
<span class="dashicons dashicons-warning" style="color: #dba617;"></span>
<?php
printf(
/* translators: %d: Number of missing licenses */
esc_html(_n(
'%d licensed product is missing a license.',
'%d licensed products are missing licenses.',
$missingLicenses,
'wc-licensed-product'
)),
$missingLicenses
);
?>
<button type="button" class="button" id="wclp-generate-licenses" data-order-id="<?php echo esc_attr($order->get_id()); ?>">
<?php esc_html_e('Generate Missing Licenses', 'wc-licensed-product'); ?>
</button>
<span class="spinner" style="float: none; margin-top: 4px;"></span>
<span class="wclp-generate-status"></span>
</p>
<?php endif; ?>
<?php endif; ?>
</div>
@@ -248,6 +298,9 @@ final class OrderLicenseController
.wclp-lifetime { color: #0073aa; font-weight: 500; }
.wclp-edit-domain-btn { color: #0073aa; text-decoration: none; }
.wclp-edit-domain-btn .dashicons { font-size: 16px; width: 16px; height: 16px; }
.wclp-generate-status { font-style: italic; margin-left: 8px; }
.wclp-generate-status.success { color: #46b450; }
.wclp-generate-status.error { color: #dc3232; }
</style>
<?php
}
@@ -284,8 +337,9 @@ final class OrderLicenseController
'strings' => [
'saving' => __('Saving...', 'wc-licensed-product'),
'saved' => __('Saved!', 'wc-licensed-product'),
'error' => __('Error saving. Please try again.', 'wc-licensed-product'),
'error' => __('Error. Please try again.', 'wc-licensed-product'),
'invalidDomain' => __('Please enter a valid domain.', 'wc-licensed-product'),
'generating' => __('Generating...', 'wc-licensed-product'),
],
]);
}
@@ -392,4 +446,96 @@ final class OrderLicenseController
$pattern = '/^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/';
return (bool) preg_match($pattern, $domain);
}
/**
* AJAX handler for generating order licenses
*/
public function ajaxGenerateOrderLicenses(): void
{
check_ajax_referer('wclp_order_license_actions', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')]);
}
$orderId = absint($_POST['order_id'] ?? 0);
if (!$orderId) {
wp_send_json_error(['message' => __('Invalid order ID.', 'wc-licensed-product')]);
}
$order = wc_get_order($orderId);
if (!$order) {
wp_send_json_error(['message' => __('Order not found.', 'wc-licensed-product')]);
}
// Check if order is paid
if (!$order->is_paid()) {
wp_send_json_error(['message' => __('Order must be paid before licenses can be generated.', 'wc-licensed-product')]);
}
// Get domain
$domain = $order->get_meta('_licensed_product_domain');
if (empty($domain)) {
wp_send_json_error(['message' => __('Please set the order domain before generating licenses.', 'wc-licensed-product')]);
}
// Generate licenses for each licensed product
$generated = 0;
$skipped = 0;
foreach ($order->get_items() as $item) {
$product = $item->get_product();
if ($product && $product->is_type('licensed')) {
$license = $this->licenseManager->generateLicense(
$orderId,
$product->get_id(),
$order->get_customer_id(),
$domain
);
if ($license) {
// Check if this is a new license or existing
$existingLicenses = $this->licenseManager->getLicensesByOrder($orderId);
$isNew = true;
foreach ($existingLicenses as $existing) {
if ($existing->getProductId() === $product->get_id() && $existing->getId() !== $license->getId()) {
$isNew = false;
break;
}
}
if ($isNew) {
$generated++;
} else {
$skipped++;
}
}
}
}
if ($generated > 0) {
wp_send_json_success([
'message' => sprintf(
/* translators: %d: Number of licenses generated */
_n(
'%d license generated successfully.',
'%d licenses generated successfully.',
$generated,
'wc-licensed-product'
),
$generated
),
'generated' => $generated,
'skipped' => $skipped,
'reload' => true,
]);
} else {
wp_send_json_success([
'message' => __('All licenses already exist for this order.', 'wc-licensed-product'),
'generated' => 0,
'skipped' => $skipped,
'reload' => false,
]);
}
}
}

View File

@@ -52,6 +52,11 @@ final class PluginLicenseChecker
*/
private ?bool $isLocalhostCached = null;
/**
* Cached self-licensing check result
*/
private ?bool $isSelfLicensingCached = null;
/**
* Get singleton instance
*/
@@ -84,6 +89,11 @@ final class PluginLicenseChecker
return true;
}
// Always valid when self-licensing (server URL points to this installation)
if ($this->isSelfLicensing()) {
return true;
}
// Check cache first
$cached = get_transient(self::CACHE_KEY);
if ($cached !== false) {
@@ -107,6 +117,11 @@ final class PluginLicenseChecker
return true;
}
// Always valid when self-licensing (server URL points to this installation)
if ($this->isSelfLicensing()) {
return true;
}
// Check settings are configured
$serverUrl = $this->getLicenseServerUrl();
$licenseKey = $this->getLicenseKey();
@@ -176,6 +191,7 @@ final class PluginLicenseChecker
delete_transient(self::CACHE_KEY);
delete_transient(self::ERROR_CACHE_KEY);
$this->isLocalhostCached = null;
$this->isSelfLicensingCached = null;
}
/**
@@ -215,6 +231,60 @@ final class PluginLicenseChecker
return false;
}
/**
* Check if self-licensing (license server URL points to this installation)
*
* Prevents circular dependency where plugin tries to validate against itself.
* Plugins can only be validated against the original store from which they were obtained.
*/
public function isSelfLicensing(): bool
{
if ($this->isSelfLicensingCached !== null) {
return $this->isSelfLicensingCached;
}
$serverUrl = $this->getLicenseServerUrl();
// No server URL configured - not self-licensing
if (empty($serverUrl)) {
$this->isSelfLicensingCached = false;
return false;
}
// Parse both URLs to compare domains
$serverParsed = parse_url($serverUrl);
$siteUrl = get_site_url();
$siteParsed = parse_url($siteUrl);
// Get normalized domains (lowercase, no www prefix)
$serverDomain = $this->normalizeDomain($serverParsed['host'] ?? '');
$siteDomain = $this->normalizeDomain($siteParsed['host'] ?? '');
// If domains match, this is self-licensing
if ($serverDomain === $siteDomain) {
$this->isSelfLicensingCached = true;
return true;
}
$this->isSelfLicensingCached = false;
return false;
}
/**
* Normalize a domain for comparison (lowercase, strip www)
*/
private function normalizeDomain(string $domain): string
{
$domain = strtolower(trim($domain));
// Strip www. prefix
if (str_starts_with($domain, 'www.')) {
$domain = substr($domain, 4);
}
return $domain;
}
/**
* Get the current domain from the site URL
*/

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