You've already forked wc-licensed-product
Implement versions 0.0.4-0.0.7 features
v0.0.4: - Add WooCommerce settings tab for default license settings - Per-product settings override global defaults v0.0.5: - Add bulk license operations (activate, deactivate, revoke, extend, delete) - Add license renewal/extension and lifetime functionality - Add quick action buttons per license row v0.0.6: - Add license dashboard with statistics and analytics - Add license transfer functionality (admin) - Add CSV export for licenses - Add OpenAPI 3.1 specification - Remove /deactivate API endpoint v0.0.7: - Move license dashboard to WooCommerce Reports section - Add license search and filtering in admin - Add customer-facing license transfer with AJAX modal - Add email notifications for license expiration warnings - Add bulk import licenses from CSV - Update README with comprehensive documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,9 @@ final class AccountController
|
||||
|
||||
// Enqueue frontend styles and scripts
|
||||
add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
|
||||
|
||||
// AJAX handler for license transfer request
|
||||
add_action('wp_ajax_wclp_customer_transfer_license', [$this, 'handleTransferRequest']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +184,19 @@ final class AccountController
|
||||
</div>
|
||||
|
||||
<div class="license-info-row">
|
||||
<span><strong><?php esc_html_e('Domain:', 'wc-licensed-product'); ?></strong> <?php echo esc_html($item['license']->getDomain()); ?></span>
|
||||
<span class="license-domain-display" data-license-id="<?php echo esc_attr($item['license']->getId()); ?>">
|
||||
<strong><?php esc_html_e('Domain:', 'wc-licensed-product'); ?></strong>
|
||||
<span class="domain-value"><?php echo esc_html($item['license']->getDomain()); ?></span>
|
||||
<?php if (in_array($item['license']->getStatus(), ['active', 'inactive'], true)): ?>
|
||||
<button type="button" class="wclp-transfer-btn"
|
||||
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'); ?>">
|
||||
<span class="dashicons dashicons-randomize"></span>
|
||||
<?php esc_html_e('Transfer', 'wc-licensed-product'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<span><strong><?php esc_html_e('Expires:', 'wc-licensed-product'); ?></strong>
|
||||
<?php
|
||||
$expiresAt = $item['license']->getExpiresAt();
|
||||
@@ -213,6 +228,40 @@ final class AccountController
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Transfer Modal -->
|
||||
<div id="wclp-transfer-modal" class="wclp-modal" style="display:none;">
|
||||
<div class="wclp-modal-overlay"></div>
|
||||
<div class="wclp-modal-content">
|
||||
<button type="button" class="wclp-modal-close" aria-label="<?php esc_attr_e('Close', 'wc-licensed-product'); ?>">×</button>
|
||||
<h3><?php esc_html_e('Transfer License to New Domain', 'wc-licensed-product'); ?></h3>
|
||||
<form id="wclp-transfer-form">
|
||||
<input type="hidden" name="license_id" id="transfer-license-id" value="">
|
||||
|
||||
<div class="wclp-form-row">
|
||||
<label><?php esc_html_e('Current Domain', 'wc-licensed-product'); ?></label>
|
||||
<p class="wclp-current-domain"><code id="transfer-current-domain"></code></p>
|
||||
</div>
|
||||
|
||||
<div class="wclp-form-row">
|
||||
<label for="transfer-new-domain"><?php esc_html_e('New Domain', 'wc-licensed-product'); ?></label>
|
||||
<input type="text" name="new_domain" id="transfer-new-domain"
|
||||
placeholder="example.com" required
|
||||
pattern="[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+">
|
||||
<p class="wclp-field-description"><?php esc_html_e('Enter the new domain without http:// or www.', 'wc-licensed-product'); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="wclp-form-row wclp-form-actions">
|
||||
<button type="submit" class="button wclp-btn-primary" id="wclp-transfer-submit">
|
||||
<?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>
|
||||
</div>
|
||||
|
||||
<div id="wclp-transfer-message" class="wclp-message" style="display:none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
@@ -241,10 +290,111 @@ final class AccountController
|
||||
);
|
||||
|
||||
wp_localize_script('wc-licensed-product-frontend', 'wcLicensedProduct', [
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'transferNonce' => wp_create_nonce('wclp_customer_transfer'),
|
||||
'strings' => [
|
||||
'copied' => __('Copied!', 'wc-licensed-product'),
|
||||
'copyFailed' => __('Copy failed', 'wc-licensed-product'),
|
||||
'transferSuccess' => __('License transferred successfully!', 'wc-licensed-product'),
|
||||
'transferError' => __('Transfer failed. Please try again.', 'wc-licensed-product'),
|
||||
'transferConfirm' => __('Are you sure you want to transfer this license to a new domain? This action cannot be undone.', 'wc-licensed-product'),
|
||||
'invalidDomain' => __('Please enter a valid domain.', 'wc-licensed-product'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle AJAX license transfer request from customer
|
||||
*/
|
||||
public function handleTransferRequest(): void
|
||||
{
|
||||
// Verify nonce
|
||||
if (!check_ajax_referer('wclp_customer_transfer', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => __('Security check failed.', 'wc-licensed-product')], 403);
|
||||
}
|
||||
|
||||
// Verify user is logged in
|
||||
$customerId = get_current_user_id();
|
||||
if (!$customerId) {
|
||||
wp_send_json_error(['message' => __('Please log in to transfer a license.', 'wc-licensed-product')], 401);
|
||||
}
|
||||
|
||||
// Get and validate license ID
|
||||
$licenseId = isset($_POST['license_id']) ? absint($_POST['license_id']) : 0;
|
||||
if (!$licenseId) {
|
||||
wp_send_json_error(['message' => __('Invalid license.', 'wc-licensed-product')], 400);
|
||||
}
|
||||
|
||||
// Get and validate new domain
|
||||
$newDomain = isset($_POST['new_domain']) ? sanitize_text_field($_POST['new_domain']) : '';
|
||||
$newDomain = $this->normalizeDomain($newDomain);
|
||||
|
||||
if (empty($newDomain)) {
|
||||
wp_send_json_error(['message' => __('Please enter a valid domain.', 'wc-licensed-product')], 400);
|
||||
}
|
||||
|
||||
// Verify the license belongs to this customer
|
||||
$license = $this->licenseManager->getLicenseById($licenseId);
|
||||
if (!$license) {
|
||||
wp_send_json_error(['message' => __('License not found.', 'wc-licensed-product')], 404);
|
||||
}
|
||||
|
||||
if ($license->getCustomerId() !== $customerId) {
|
||||
wp_send_json_error(['message' => __('You do not have permission to transfer this license.', 'wc-licensed-product')], 403);
|
||||
}
|
||||
|
||||
// Check if license is in a transferable state
|
||||
if ($license->getStatus() === 'revoked') {
|
||||
wp_send_json_error(['message' => __('Revoked licenses cannot be transferred.', 'wc-licensed-product')], 400);
|
||||
}
|
||||
|
||||
if ($license->getStatus() === 'expired') {
|
||||
wp_send_json_error(['message' => __('Expired licenses cannot be transferred.', 'wc-licensed-product')], 400);
|
||||
}
|
||||
|
||||
// Check if domain is the same
|
||||
if ($license->getDomain() === $newDomain) {
|
||||
wp_send_json_error(['message' => __('The new domain is the same as the current domain.', 'wc-licensed-product')], 400);
|
||||
}
|
||||
|
||||
// Perform the transfer
|
||||
$result = $this->licenseManager->transferLicense($licenseId, $newDomain);
|
||||
|
||||
if ($result) {
|
||||
wp_send_json_success([
|
||||
'message' => __('License transferred successfully!', 'wc-licensed-product'),
|
||||
'new_domain' => $newDomain,
|
||||
]);
|
||||
} else {
|
||||
wp_send_json_error(['message' => __('Failed to transfer license. Please try again.', 'wc-licensed-product')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize domain for comparison and storage
|
||||
*/
|
||||
private function normalizeDomain(string $domain): string
|
||||
{
|
||||
// Remove protocol if present
|
||||
$domain = preg_replace('#^https?://#i', '', $domain);
|
||||
|
||||
// Remove www prefix
|
||||
$domain = preg_replace('#^www\.#i', '', $domain);
|
||||
|
||||
// Remove trailing slash
|
||||
$domain = rtrim($domain, '/');
|
||||
|
||||
// Remove path if present
|
||||
$domain = explode('/', $domain)[0];
|
||||
|
||||
// Convert to lowercase
|
||||
$domain = strtolower($domain);
|
||||
|
||||
// Basic validation - must contain at least one dot and valid characters
|
||||
if (!preg_match('/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/', $domain)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user