You've already forked wc-licensed-product
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12a3a37658 | |||
| b1fe34adfd | |||
| dcf3a03598 | |||
| 38a9f0d90f | |||
| 8b87c954eb | |||
| 1bc643408e | |||
| 875c8dd1c1 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.4] - 2026-01-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Current version display on single product pages for licensed products
|
||||||
|
- Version number shown directly under the product title
|
||||||
|
- Frontend CSS styling for version badge with monospace font
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- Added `displayCurrentVersion()` method to `LicensedProductType` class
|
||||||
|
- Hooked to `woocommerce_single_product_summary` at priority 6 (after title)
|
||||||
|
- Added `enqueueFrontendStyles()` to load CSS on product pages
|
||||||
|
- Uses `LicensedProduct::get_current_version()` to fetch latest version
|
||||||
|
|
||||||
|
## [0.3.3] - 2026-01-22
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed version deactivation button not working in admin product versions table
|
||||||
|
- Corrected parameter order in `updateVersion()` call - `isActive` was being passed to `attachmentId` parameter
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- Bug in `VersionAdminController::ajaxToggleVersion()` - parameters were in wrong order
|
||||||
|
- Changed from `updateVersion($versionId, null, null, !$currentlyActive)` to `updateVersion($versionId, null, !$currentlyActive, null)`
|
||||||
|
|
||||||
## [0.3.2] - 2026-01-22
|
## [0.3.2] - 2026-01-22
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -425,7 +452,8 @@ define('WC_LICENSE_SERVER_SECRET', 'your-secure-random-string-min-32-chars');
|
|||||||
- WordPress REST API integration
|
- WordPress REST API integration
|
||||||
- Custom WooCommerce product type extending WC_Product
|
- Custom WooCommerce product type extending WC_Product
|
||||||
|
|
||||||
[Unreleased]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.2...HEAD
|
[Unreleased]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.3...HEAD
|
||||||
|
[0.3.3]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.2...v0.3.3
|
||||||
[0.3.2]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.1...v0.3.2
|
[0.3.2]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.1...v0.3.2
|
||||||
[0.3.1]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.0...v0.3.1
|
[0.3.1]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.0...v0.3.1
|
||||||
[0.3.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.2.2...v0.3.0
|
[0.3.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.2.2...v0.3.0
|
||||||
|
|||||||
63
CLAUDE.md
63
CLAUDE.md
@@ -869,3 +869,66 @@ Updated OpenAPI specification to document response signing feature added in v0.2
|
|||||||
- All endpoint 200 responses now reference optional signature headers
|
- All endpoint 200 responses now reference optional signature headers
|
||||||
- Header definitions added to components section
|
- Header definitions added to components section
|
||||||
- API description explains SecureLicenseClient usage for signature verification
|
- API description explains SecureLicenseClient usage for signature verification
|
||||||
|
- Changed `magdev/wc-licensed-product-client` from local path to git repository URL
|
||||||
|
- Composer now fetches from: `https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git`
|
||||||
|
- Release package excludes vendor `.git` directories
|
||||||
|
|
||||||
|
**Release v0.3.2:**
|
||||||
|
|
||||||
|
- Created release package: `releases/wc-licensed-product-0.3.2.zip` (810 KB)
|
||||||
|
- SHA256: `ca33c81516b5dcf4a80b3192d8ae4ad39a7bf67196a1f729b563c5ae01b1d39c`
|
||||||
|
- Tagged as `v0.3.2` and pushed to `main` branch
|
||||||
|
|
||||||
|
### 2026-01-22 - Version 0.3.3 - Bug Fix & License Testing
|
||||||
|
|
||||||
|
**Overview:**
|
||||||
|
|
||||||
|
Fixed version deactivation bug and added license testing functionality.
|
||||||
|
|
||||||
|
**Bug Fix:**
|
||||||
|
|
||||||
|
- Fixed version deactivation button not working in admin product versions table
|
||||||
|
- Root cause: Parameters in wrong order in `VersionAdminController::ajaxToggleVersion()`
|
||||||
|
- Changed from `updateVersion($versionId, null, null, !$currentlyActive)` to `updateVersion($versionId, null, !$currentlyActive, null)`
|
||||||
|
|
||||||
|
**Implemented:**
|
||||||
|
|
||||||
|
- Added "Test" action to license overview to validate licenses against `/validate` API endpoint
|
||||||
|
- Test License modal showing license key, domain, and validation results
|
||||||
|
- AJAX handler `handleAjaxTestLicense()` for license testing
|
||||||
|
|
||||||
|
**Modified files:**
|
||||||
|
|
||||||
|
- `src/Admin/VersionAdminController.php` - Fixed parameter order in toggle method
|
||||||
|
- `src/Admin/AdminController.php` - Added Test action to PHP fallback and AJAX handler
|
||||||
|
- `templates/admin/licenses.html.twig` - Added Test action and modal to Twig template
|
||||||
|
|
||||||
|
**Release v0.3.3:**
|
||||||
|
|
||||||
|
- Created release package: `releases/wc-licensed-product-0.3.3.zip` (795 KB)
|
||||||
|
- SHA256: `a06d29eabc2da08613ae13874ed152b8ea9363b8284a2e9bdda414e32777558c`
|
||||||
|
- Tagged as `v0.3.3` and pushed to `main` branch
|
||||||
|
|
||||||
|
### 2026-01-23 - Version 0.3.4 - Frontend Version Display
|
||||||
|
|
||||||
|
**Overview:**
|
||||||
|
|
||||||
|
Added current version display on single product pages for licensed products.
|
||||||
|
|
||||||
|
**Implemented:**
|
||||||
|
|
||||||
|
- Current version displayed directly under the product title
|
||||||
|
- Styled version badge with monospace font and subtle blue background
|
||||||
|
- Frontend CSS automatically loaded on licensed product pages
|
||||||
|
|
||||||
|
**Modified files:**
|
||||||
|
|
||||||
|
- `src/Product/LicensedProductType.php` - Added `displayCurrentVersion()` and `enqueueFrontendStyles()` methods
|
||||||
|
- `assets/css/frontend.css` - Added `.wclp-product-version` styles
|
||||||
|
|
||||||
|
**Technical notes:**
|
||||||
|
|
||||||
|
- Uses `woocommerce_single_product_summary` hook at priority 6 (after title at priority 5)
|
||||||
|
- Only displays for licensed product type
|
||||||
|
- Only displays if product has at least one version defined
|
||||||
|
- Uses `LicensedProduct::get_current_version()` which queries `VersionManager::getLatestVersion()`
|
||||||
|
|||||||
@@ -528,3 +528,24 @@
|
|||||||
color: #721c24;
|
color: #721c24;
|
||||||
border: 1px solid #f5c6cb;
|
border: 1px solid #f5c6cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Product Version Display (Single Product Page) */
|
||||||
|
.wclp-product-version {
|
||||||
|
margin: 0.5em 0 1em 0;
|
||||||
|
font-size: 0.95em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wclp-product-version .version-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wclp-product-version .version-number {
|
||||||
|
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
||||||
|
background: #e7f3ff;
|
||||||
|
padding: 0.15em 0.5em;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #2271b1;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|||||||
4
composer.lock
generated
4
composer.lock
generated
@@ -12,7 +12,7 @@
|
|||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
|
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
|
||||||
"reference": "83037ea0c2d9e365cf9ec0ad50251d3ebc7e4782"
|
"reference": "a3a957914fd6ef74cb479e213d1d3bc0606f496b"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
|
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
|
||||||
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
|
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
|
||||||
},
|
},
|
||||||
"time": "2026-01-22T15:24:57+00:00"
|
"time": "2026-01-22T20:05:48+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1
releases/wc-licensed-product-0.3.3.sha256
Normal file
1
releases/wc-licensed-product-0.3.3.sha256
Normal file
@@ -0,0 +1 @@
|
|||||||
|
a06d29eabc2da08613ae13874ed152b8ea9363b8284a2e9bdda414e32777558c wc-licensed-product-0.3.3.zip
|
||||||
BIN
releases/wc-licensed-product-0.3.3.zip
Normal file
BIN
releases/wc-licensed-product-0.3.3.zip
Normal file
Binary file not shown.
@@ -61,6 +61,9 @@ final class AdminController
|
|||||||
add_action('wp_ajax_wclp_update_license_expiry', [$this, 'handleAjaxExpiryUpdate']);
|
add_action('wp_ajax_wclp_update_license_expiry', [$this, 'handleAjaxExpiryUpdate']);
|
||||||
add_action('wp_ajax_wclp_update_license_domain', [$this, 'handleAjaxDomainUpdate']);
|
add_action('wp_ajax_wclp_update_license_domain', [$this, 'handleAjaxDomainUpdate']);
|
||||||
add_action('wp_ajax_wclp_revoke_license', [$this, 'handleAjaxRevoke']);
|
add_action('wp_ajax_wclp_revoke_license', [$this, 'handleAjaxRevoke']);
|
||||||
|
|
||||||
|
// AJAX handler for license testing
|
||||||
|
add_action('wp_ajax_wclp_test_license', [$this, 'handleAjaxTestLicense']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,6 +358,30 @@ final class AdminController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle AJAX license test - validates license against the API
|
||||||
|
*/
|
||||||
|
public function handleAjaxTestLicense(): void
|
||||||
|
{
|
||||||
|
check_ajax_referer('wclp_inline_edit', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('manage_woocommerce')) {
|
||||||
|
wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$licenseKey = isset($_POST['license_key']) ? sanitize_text_field(wp_unslash($_POST['license_key'])) : '';
|
||||||
|
$domain = isset($_POST['domain']) ? sanitize_text_field(wp_unslash($_POST['domain'])) : '';
|
||||||
|
|
||||||
|
if (empty($licenseKey) || empty($domain)) {
|
||||||
|
wp_send_json_error(['message' => __('License key and domain are required.', 'wc-licensed-product')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the license using LicenseManager
|
||||||
|
$result = $this->licenseManager->validateLicense($licenseKey, $domain);
|
||||||
|
|
||||||
|
wp_send_json_success($result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle admin actions (update, delete licenses)
|
* Handle admin actions (update, delete licenses)
|
||||||
*/
|
*/
|
||||||
@@ -1347,7 +1374,20 @@ final class AdminController
|
|||||||
</td>
|
</td>
|
||||||
<td class="license-actions">
|
<td class="license-actions">
|
||||||
<div class="row-actions">
|
<div class="row-actions">
|
||||||
|
<span class="test">
|
||||||
|
<a href="#" class="wclp-test-license-link"
|
||||||
|
data-license-id="<?php echo esc_attr($item['license']->getId()); ?>"
|
||||||
|
data-license-key="<?php echo esc_attr($item['license']->getLicenseKey()); ?>"
|
||||||
|
data-domain="<?php echo esc_attr($item['license']->getDomain()); ?>"
|
||||||
|
title="<?php esc_attr_e('Test license against API', 'wc-licensed-product'); ?>"><?php esc_html_e('Test', 'wc-licensed-product'); ?></a> |
|
||||||
|
</span>
|
||||||
<?php if ($item['license']->getStatus() !== License::STATUS_REVOKED): ?>
|
<?php if ($item['license']->getStatus() !== License::STATUS_REVOKED): ?>
|
||||||
|
<span class="transfer">
|
||||||
|
<a href="#" class="wclp-transfer-link"
|
||||||
|
data-license-id="<?php echo esc_attr($item['license']->getId()); ?>"
|
||||||
|
data-current-domain="<?php echo esc_attr($item['license']->getDomain()); ?>"
|
||||||
|
title="<?php esc_attr_e('Transfer to new domain', 'wc-licensed-product'); ?>"><?php esc_html_e('Transfer', 'wc-licensed-product'); ?></a> |
|
||||||
|
</span>
|
||||||
<span class="extend">
|
<span class="extend">
|
||||||
<a href="<?php echo esc_url(wp_nonce_url(
|
<a href="<?php echo esc_url(wp_nonce_url(
|
||||||
admin_url('admin.php?page=wc-licenses&action=extend&license_id=' . $item['license']->getId() . '&days=30'),
|
admin_url('admin.php?page=wc-licenses&action=extend&license_id=' . $item['license']->getId() . '&days=30'),
|
||||||
@@ -1429,8 +1469,69 @@ final class AdminController
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Test License Modal -->
|
||||||
|
<div id="wclp-test-modal" class="wclp-modal" style="display:none;">
|
||||||
|
<div class="wclp-modal-content">
|
||||||
|
<span class="wclp-modal-close">×</span>
|
||||||
|
<h2><?php esc_html_e('License Validation Test', 'wc-licensed-product'); ?></h2>
|
||||||
|
<div class="wclp-test-info">
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php esc_html_e('License Key', 'wc-licensed-product'); ?></th>
|
||||||
|
<td><code id="test-license-key"></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php esc_html_e('Domain', 'wc-licensed-product'); ?></th>
|
||||||
|
<td><code id="test-domain"></code></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="wclp-test-loading" style="display:none; text-align:center; padding:20px;">
|
||||||
|
<span class="spinner is-active" style="float:none;"></span>
|
||||||
|
<p><?php esc_html_e('Testing license...', 'wc-licensed-product'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div id="wclp-test-result" style="display:none;">
|
||||||
|
<div id="wclp-test-result-content"></div>
|
||||||
|
</div>
|
||||||
|
<p class="submit">
|
||||||
|
<button type="button" class="button wclp-modal-cancel"><?php esc_html_e('Close', 'wc-licensed-product'); ?></button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transfer Modal -->
|
||||||
|
<div id="wclp-transfer-modal" class="wclp-modal" style="display:none;">
|
||||||
|
<div class="wclp-modal-content">
|
||||||
|
<span class="wclp-modal-close">×</span>
|
||||||
|
<h2><?php esc_html_e('Transfer License to New Domain', 'wc-licensed-product'); ?></h2>
|
||||||
|
<form method="post" action="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>">
|
||||||
|
<input type="hidden" name="action" value="transfer_license">
|
||||||
|
<?php wp_nonce_field('transfer_license', '_wpnonce'); ?>
|
||||||
|
<input type="hidden" name="license_id" id="transfer-license-id" value="">
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label><?php esc_html_e('Current Domain', 'wc-licensed-product'); ?></label></th>
|
||||||
|
<td><code id="transfer-current-domain"></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label for="new_domain"><?php esc_html_e('New Domain', 'wc-licensed-product'); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="new_domain" id="transfer-new-domain" class="regular-text" placeholder="example.com" required>
|
||||||
|
<p class="description"><?php esc_html_e('Enter the new domain without http:// or www.', 'wc-licensed-product'); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p class="submit">
|
||||||
|
<button type="submit" class="button button-primary"><?php esc_html_e('Transfer License', 'wc-licensed-product'); ?></button>
|
||||||
|
<button type="button" class="button wclp-modal-cancel"><?php esc_html_e('Cancel', 'wc-licensed-product'); ?></button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function($) {
|
(function($) {
|
||||||
|
// Checkbox select all
|
||||||
$('#cb-select-all-1, #cb-select-all-2').on('change', function() {
|
$('#cb-select-all-1, #cb-select-all-2').on('change', function() {
|
||||||
$('input[name="license_ids[]"]').prop('checked', this.checked);
|
$('input[name="license_ids[]"]').prop('checked', this.checked);
|
||||||
$('#cb-select-all-1, #cb-select-all-2').prop('checked', this.checked);
|
$('#cb-select-all-1, #cb-select-all-2').prop('checked', this.checked);
|
||||||
@@ -1445,6 +1546,102 @@ final class AdminController
|
|||||||
$('#bulk-action-selector').val(bottomAction);
|
$('#bulk-action-selector').val(bottomAction);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Transfer modal
|
||||||
|
var $transferModal = $('#wclp-transfer-modal');
|
||||||
|
$('.wclp-transfer-link').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var licenseId = $(this).data('license-id');
|
||||||
|
var currentDomain = $(this).data('current-domain');
|
||||||
|
$('#transfer-license-id').val(licenseId);
|
||||||
|
$('#transfer-current-domain').text(currentDomain);
|
||||||
|
$('#transfer-new-domain').val('');
|
||||||
|
$transferModal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test License modal
|
||||||
|
var $testModal = $('#wclp-test-modal');
|
||||||
|
var $testLoading = $('#wclp-test-loading');
|
||||||
|
var $testResult = $('#wclp-test-result');
|
||||||
|
var $testResultContent = $('#wclp-test-result-content');
|
||||||
|
|
||||||
|
$('.wclp-test-license-link').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var licenseKey = $(this).data('license-key');
|
||||||
|
var domain = $(this).data('domain');
|
||||||
|
|
||||||
|
$('#test-license-key').text(licenseKey);
|
||||||
|
$('#test-domain').text(domain);
|
||||||
|
$testLoading.show();
|
||||||
|
$testResult.hide();
|
||||||
|
$testModal.show();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: wclpAdmin.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'wclp_test_license',
|
||||||
|
nonce: wclpAdmin.editNonce,
|
||||||
|
license_key: licenseKey,
|
||||||
|
domain: domain
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$testLoading.hide();
|
||||||
|
if (response.success) {
|
||||||
|
var result = response.data;
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
if (result.valid) {
|
||||||
|
html = '<div class="notice notice-success inline"><p><strong>✓ <?php echo esc_js(__('License is VALID', 'wc-licensed-product')); ?></strong></p></div>';
|
||||||
|
html += '<table class="widefat striped"><tbody>';
|
||||||
|
html += '<tr><th><?php echo esc_js(__('Product', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.product_name || '-') + '</td></tr>';
|
||||||
|
html += '<tr><th><?php echo esc_js(__('Version', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.version || '-') + '</td></tr>';
|
||||||
|
if (result.expires_at) {
|
||||||
|
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.expires_at) + '</td></tr>';
|
||||||
|
} else {
|
||||||
|
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td><?php echo esc_js(__('Lifetime', 'wc-licensed-product')); ?></td></tr>';
|
||||||
|
}
|
||||||
|
html += '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
html = '<div class="notice notice-error inline"><p><strong>✗ <?php echo esc_js(__('License is INVALID', 'wc-licensed-product')); ?></strong></p></div>';
|
||||||
|
html += '<table class="widefat striped"><tbody>';
|
||||||
|
html += '<tr><th><?php echo esc_js(__('Error Code', 'wc-licensed-product')); ?></th><td><code>' + escapeHtml(result.error || 'unknown') + '</code></td></tr>';
|
||||||
|
html += '<tr><th><?php echo esc_js(__('Message', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.message || '-') + '</td></tr>';
|
||||||
|
html += '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$testResultContent.html(html);
|
||||||
|
$testResult.show();
|
||||||
|
} else {
|
||||||
|
$testResultContent.html('<div class="notice notice-error inline"><p>' + escapeHtml(response.data.message || 'Error') + '</p></div>');
|
||||||
|
$testResult.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$testLoading.hide();
|
||||||
|
$testResultContent.html('<div class="notice notice-error inline"><p><?php echo esc_js(__('Failed to test license. Please try again.', 'wc-licensed-product')); ?></p></div>');
|
||||||
|
$testResult.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modals
|
||||||
|
$('.wclp-modal-close, .wclp-modal-cancel').on('click', function() {
|
||||||
|
$(this).closest('.wclp-modal').hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('click', function(e) {
|
||||||
|
if ($(e.target).hasClass('wclp-modal')) {
|
||||||
|
$(e.target).hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ final class VersionAdminController
|
|||||||
wp_send_json_error(['message' => __('Version ID is required.', 'wc-licensed-product')]);
|
wp_send_json_error(['message' => __('Version ID is required.', 'wc-licensed-product')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->versionManager->updateVersion($versionId, null, null, !$currentlyActive);
|
$result = $this->versionManager->updateVersion($versionId, null, !$currentlyActive, null);
|
||||||
|
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
wp_send_json_error(['message' => __('Failed to update version.', 'wc-licensed-product')]);
|
wp_send_json_error(['message' => __('Failed to update version.', 'wc-licensed-product')]);
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ final class LicensedProductType
|
|||||||
|
|
||||||
// Make product virtual by default
|
// Make product virtual by default
|
||||||
add_filter('woocommerce_product_is_virtual', [$this, 'isVirtual'], 10, 2);
|
add_filter('woocommerce_product_is_virtual', [$this, 'isVirtual'], 10, 2);
|
||||||
|
|
||||||
|
// Display current version under product title on single product page
|
||||||
|
add_action('woocommerce_single_product_summary', [$this, 'displayCurrentVersion'], 6);
|
||||||
|
|
||||||
|
// Enqueue frontend CSS for licensed products on single product pages
|
||||||
|
add_action('wp_enqueue_scripts', [$this, 'enqueueFrontendStyles']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,4 +241,52 @@ final class LicensedProductType
|
|||||||
}
|
}
|
||||||
return $isVirtual;
|
return $isVirtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue frontend styles for licensed products on single product pages
|
||||||
|
*/
|
||||||
|
public function enqueueFrontendStyles(): void
|
||||||
|
{
|
||||||
|
if (!is_product()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $product;
|
||||||
|
|
||||||
|
if (!$product || !$product->is_type('licensed')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'wc-licensed-product-frontend',
|
||||||
|
WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/css/frontend.css',
|
||||||
|
[],
|
||||||
|
WC_LICENSED_PRODUCT_VERSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display current version under product title on single product page
|
||||||
|
*/
|
||||||
|
public function displayCurrentVersion(): void
|
||||||
|
{
|
||||||
|
global $product;
|
||||||
|
|
||||||
|
if (!$product || !$product->is_type('licensed')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var LicensedProduct $product */
|
||||||
|
$version = $product->get_current_version();
|
||||||
|
|
||||||
|
if (empty($version)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<p class="wclp-product-version"><span class="version-label">%s</span> <span class="version-number">%s</span></p>',
|
||||||
|
esc_html__('Version:', 'wc-licensed-product'),
|
||||||
|
esc_html($version)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="license-actions">
|
<td class="license-actions">
|
||||||
<div class="row-actions">
|
<div class="row-actions">
|
||||||
|
<span class="test">
|
||||||
|
<a href="#" class="wclp-test-license-link"
|
||||||
|
data-license-id="{{ item.license.id }}"
|
||||||
|
data-license-key="{{ esc_attr(item.license.licenseKey) }}"
|
||||||
|
data-domain="{{ esc_attr(item.license.domain) }}"
|
||||||
|
title="{{ __('Test license against API') }}">{{ __('Test') }}</a> |
|
||||||
|
</span>
|
||||||
{% if item.license.status != 'revoked' %}
|
{% if item.license.status != 'revoked' %}
|
||||||
<span class="transfer">
|
<span class="transfer">
|
||||||
<a href="#" class="wclp-transfer-link"
|
<a href="#" class="wclp-transfer-link"
|
||||||
@@ -272,6 +279,36 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Test License Modal -->
|
||||||
|
<div id="wclp-test-modal" class="wclp-modal" style="display:none;">
|
||||||
|
<div class="wclp-modal-content">
|
||||||
|
<span class="wclp-modal-close">×</span>
|
||||||
|
<h2>{{ __('License Validation Test') }}</h2>
|
||||||
|
<div class="wclp-test-info">
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ __('License Key') }}</th>
|
||||||
|
<td><code id="test-license-key"></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ __('Domain') }}</th>
|
||||||
|
<td><code id="test-domain"></code></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="wclp-test-loading" style="display:none; text-align:center; padding:20px;">
|
||||||
|
<span class="spinner is-active" style="float:none;"></span>
|
||||||
|
<p>{{ __('Testing license...') }}</p>
|
||||||
|
</div>
|
||||||
|
<div id="wclp-test-result" style="display:none;">
|
||||||
|
<div id="wclp-test-result-content"></div>
|
||||||
|
</div>
|
||||||
|
<p class="submit">
|
||||||
|
<button type="button" class="button wclp-modal-cancel">{{ __('Close') }}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Transfer Modal -->
|
<!-- Transfer Modal -->
|
||||||
<div id="wclp-transfer-modal" class="wclp-modal" style="display:none;">
|
<div id="wclp-transfer-modal" class="wclp-modal" style="display:none;">
|
||||||
<div class="wclp-modal-content">
|
<div class="wclp-modal-content">
|
||||||
@@ -349,5 +386,91 @@
|
|||||||
$modal.hide();
|
$modal.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Test License modal
|
||||||
|
var $testModal = $('#wclp-test-modal');
|
||||||
|
var $testLoading = $('#wclp-test-loading');
|
||||||
|
var $testResult = $('#wclp-test-result');
|
||||||
|
var $testResultContent = $('#wclp-test-result-content');
|
||||||
|
|
||||||
|
$('.wclp-test-license-link').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var licenseKey = $(this).data('license-key');
|
||||||
|
var domain = $(this).data('domain');
|
||||||
|
|
||||||
|
// Show modal with info
|
||||||
|
$('#test-license-key').text(licenseKey);
|
||||||
|
$('#test-domain').text(domain);
|
||||||
|
$testLoading.show();
|
||||||
|
$testResult.hide();
|
||||||
|
$testModal.show();
|
||||||
|
|
||||||
|
// Call the test endpoint
|
||||||
|
$.ajax({
|
||||||
|
url: wclpAdmin.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'wclp_test_license',
|
||||||
|
nonce: wclpAdmin.editNonce,
|
||||||
|
license_key: licenseKey,
|
||||||
|
domain: domain
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$testLoading.hide();
|
||||||
|
if (response.success) {
|
||||||
|
var result = response.data;
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
if (result.valid) {
|
||||||
|
html = '<div class="notice notice-success inline"><p><strong>✓ {{ __('License is VALID') }}</strong></p></div>';
|
||||||
|
html += '<table class="widefat striped"><tbody>';
|
||||||
|
html += '<tr><th>{{ __('Product') }}</th><td>' + escapeHtml(result.product_name || '-') + '</td></tr>';
|
||||||
|
html += '<tr><th>{{ __('Version') }}</th><td>' + escapeHtml(result.version || '-') + '</td></tr>';
|
||||||
|
if (result.expires_at) {
|
||||||
|
html += '<tr><th>{{ __('Expires') }}</th><td>' + escapeHtml(result.expires_at) + '</td></tr>';
|
||||||
|
} else {
|
||||||
|
html += '<tr><th>{{ __('Expires') }}</th><td>{{ __('Lifetime') }}</td></tr>';
|
||||||
|
}
|
||||||
|
html += '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
html = '<div class="notice notice-error inline"><p><strong>✗ {{ __('License is INVALID') }}</strong></p></div>';
|
||||||
|
html += '<table class="widefat striped"><tbody>';
|
||||||
|
html += '<tr><th>{{ __('Error Code') }}</th><td><code>' + escapeHtml(result.error || 'unknown') + '</code></td></tr>';
|
||||||
|
html += '<tr><th>{{ __('Message') }}</th><td>' + escapeHtml(result.message || '-') + '</td></tr>';
|
||||||
|
html += '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$testResultContent.html(html);
|
||||||
|
$testResult.show();
|
||||||
|
} else {
|
||||||
|
$testResultContent.html('<div class="notice notice-error inline"><p>' + escapeHtml(response.data.message || 'Error') + '</p></div>');
|
||||||
|
$testResult.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$testLoading.hide();
|
||||||
|
$testResultContent.html('<div class="notice notice-error inline"><p>{{ __('Failed to test license. Please try again.') }}</p></div>');
|
||||||
|
$testResult.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close test modal
|
||||||
|
$testModal.find('.wclp-modal-close, .wclp-modal-cancel').on('click', function() {
|
||||||
|
$testModal.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('click', function(e) {
|
||||||
|
if ($(e.target).is($testModal)) {
|
||||||
|
$testModal.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WooCommerce Licensed Product
|
* Plugin Name: WooCommerce Licensed Product
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
|
||||||
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
|
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
|
||||||
* Version: 0.3.2
|
* Version: 0.3.4
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
* License: GPL-2.0-or-later
|
* License: GPL-2.0-or-later
|
||||||
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plugin constants
|
// Plugin constants
|
||||||
define('WC_LICENSED_PRODUCT_VERSION', '0.3.2');
|
define('WC_LICENSED_PRODUCT_VERSION', '0.3.4');
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user