You've already forked wc-licensed-product
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ec3f42b1f | |||
| 4817175f99 | |||
| a4561057fa | |||
| d15c59b7c3 | |||
| 4a90e6b18b | |||
| 502a8c7cd7 | |||
| 6b83fce8b2 | |||
| 8c33eaff29 | |||
| 98002ae3d7 | |||
| a93381dce6 | |||
| a522455a0a | |||
| 2de6abe133 | |||
| 8d60758f23 |
51
CHANGELOG.md
51
CHANGELOG.md
@@ -7,6 +7,57 @@ 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
|
||||
|
||||
- Fixed duplicate German translation string causing `ArgumentCountError` in settings page
|
||||
- The notification settings description had duplicated text with two `%s` placeholders
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `magdev/wc-licensed-product-client` to latest version (64d215c)
|
||||
|
||||
## [0.3.7] - 2026-01-24
|
||||
|
||||
### Added
|
||||
|
||||
142
CLAUDE.md
142
CLAUDE.md
@@ -36,18 +36,9 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
||||
|
||||
No known bugs at the moment.
|
||||
|
||||
### Version 0.3.7
|
||||
|
||||
- Fixed: Dashboard widget "View All Licenses" link used wrong page slug
|
||||
- Fixed: Download links in customer account resulted in 404 errors (missing query var registration)
|
||||
- Removed: Redundant "Status Breakdown" section from dashboard widget (info already in stat cards)
|
||||
- Changed: License Types section now uses card style matching the stats row above
|
||||
- Added: Download counter for licensed product versions (tracked per version)
|
||||
- Added: Download Statistics admin dashboard widget showing total downloads, top products, and top versions
|
||||
|
||||
### 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
|
||||
|
||||
@@ -1037,3 +1028,134 @@ define('WC_LICENSE_TRUSTED_PROXIES', '10.0.0.1,192.168.1.0/24');
|
||||
- Created release package: `releases/wc-licensed-product-0.3.6.zip` (818 KB)
|
||||
- SHA256: `b0063f0312759f090e12faba83de730baf4114139d763e46fad2b781d4b38270`
|
||||
- Tagged as `v0.3.6` and pushed to `main` branch
|
||||
|
||||
### 2026-01-24 - Version 0.3.7 - Dashboard Improvements & Download Counter
|
||||
|
||||
**Overview:**
|
||||
|
||||
Fixed dashboard widget bugs, improved UI consistency, and added download tracking functionality with a new statistics widget.
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
- Fixed: Dashboard widget "View All Licenses" link used wrong page slug (`wc-licensed-product-licenses` instead of `wc-licenses`)
|
||||
- Fixed: Download links in customer account resulted in 404 errors due to missing query var registration
|
||||
- Added `license-download` endpoint registration during plugin activation in `Installer::activate()`
|
||||
- Added `addDownloadQueryVar()` method to `DownloadController` for proper WordPress endpoint recognition
|
||||
|
||||
**UI Improvements:**
|
||||
|
||||
- Removed redundant "Status Breakdown" section from license statistics widget (info already shown in stat cards above)
|
||||
- Changed License Types section to use card-style layout matching the stats row above
|
||||
- Cleaned up unused CSS for status badges
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Download counter for licensed product versions (tracked per version in database)
|
||||
- New Download Statistics admin dashboard widget showing:
|
||||
- Total downloads count
|
||||
- Top 5 products by downloads
|
||||
- Top 5 versions by downloads
|
||||
|
||||
**New files:**
|
||||
|
||||
- `src/Admin/DownloadWidgetController.php` - Dashboard widget for download statistics
|
||||
|
||||
**New methods in VersionManager:**
|
||||
|
||||
- `incrementDownloadCount()` - Atomically increment download count for a version
|
||||
- `getTotalDownloadCount()` - Get total downloads across all versions
|
||||
- `getDownloadStatistics()` - Get download stats grouped by product and version
|
||||
|
||||
**Modified files:**
|
||||
|
||||
- `src/Installer.php` - Added `download_count` column to versions table, added `license-download` endpoint registration
|
||||
- `src/Product/ProductVersion.php` - Added `downloadCount` property and `getDownloadCount()` method
|
||||
- `src/Product/VersionManager.php` - Added download counting methods
|
||||
- `src/Frontend/DownloadController.php` - Added query var registration, increment download count on file serve
|
||||
- `src/Admin/DashboardWidgetController.php` - Fixed URL, removed Status Breakdown, changed License Types to cards
|
||||
- `src/Plugin.php` - Added DownloadWidgetController instantiation
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
- Download count is incremented atomically using SQL `download_count = download_count + 1`
|
||||
- Statistics queries use SQL aggregation with product name enrichment via `wc_get_product()`
|
||||
- WordPress endpoints require both `add_rewrite_endpoint()` AND `query_vars` filter registration
|
||||
- Existing installations need to flush rewrite rules (Settings > Permalinks > Save) or reactivate plugin
|
||||
|
||||
**Release v0.3.7:**
|
||||
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
16
composer.lock
generated
16
composer.lock
generated
@@ -12,7 +12,7 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
|
||||
"reference": "9f513a819e8218a0e8e16f0be8f7edbf0f30245e"
|
||||
"reference": "64d215cb265a64ff318cfbb954dd128b0076dc1d"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
@@ -52,7 +52,7 @@
|
||||
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
|
||||
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
|
||||
},
|
||||
"time": "2026-01-23T15:45:59+00:00"
|
||||
"time": "2026-01-24T13:32:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
@@ -894,16 +894,16 @@
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.22.2",
|
||||
"version": "v3.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2"
|
||||
"reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2",
|
||||
"reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9",
|
||||
"reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -957,7 +957,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.22.2"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.23.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -969,7 +969,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-14T11:28:47+00:00"
|
||||
"time": "2026-01-23T21:00:41+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
releases/wc-licensed-product-0.3.7.zip
Normal file
BIN
releases/wc-licensed-product-0.3.7.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.3.7.zip.sha256
Normal file
1
releases/wc-licensed-product-0.3.7.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
e93b2ab06f6d43c2179167090e07eda5db6809df6e391baece4ceba321cf33f6 wc-licensed-product-0.3.7.zip
|
||||
BIN
releases/wc-licensed-product-0.3.9.zip
Normal file
BIN
releases/wc-licensed-product-0.3.9.zip
Normal file
Binary file not shown.
1
releases/wc-licensed-product-0.3.9.zip.sha256
Normal file
1
releases/wc-licensed-product-0.3.9.zip.sha256
Normal file
@@ -0,0 +1 @@
|
||||
fdb65200c368da380df0cabb3c6ac6419d5b4731cd528f630f9b432a3ba5c586 releases/wc-licensed-product-0.3.9.zip
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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.7
|
||||
* 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.7');
|
||||
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__));
|
||||
|
||||
Reference in New Issue
Block a user