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>
This commit is contained in:
2026-01-24 16:06:13 +01:00
parent 8c33eaff29
commit 6b83fce8b2
5 changed files with 1985 additions and 1702 deletions

View File

@@ -16,6 +16,9 @@
// Order domain save // Order domain save
$('#wclp-save-order-domain').on('click', this.saveOrderDomain.bind(this)); $('#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 // License domain edit/save/cancel
$(document).on('click', '.wclp-edit-domain-btn', this.startEditDomain); $(document).on('click', '.wclp-edit-domain-btn', this.startEditDomain);
$(document).on('click', '.wclp-save-domain-btn', this.saveLicenseDomain.bind(this)); $(document).on('click', '.wclp-save-domain-btn', this.saveLicenseDomain.bind(this));
@@ -135,6 +138,54 @@
$editBtn.show(); $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 * Save license domain
*/ */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,7 @@ final class OrderLicenseController
// Handle AJAX actions // Handle AJAX actions
add_action('wp_ajax_wclp_update_order_domain', [$this, 'ajaxUpdateOrderDomain']); 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_update_license_domain', [$this, 'ajaxUpdateLicenseDomain']);
add_action('wp_ajax_wclp_generate_order_licenses', [$this, 'ajaxGenerateOrderLicenses']);
// Enqueue admin scripts // Enqueue admin scripts
add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']); add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']);
@@ -126,6 +127,18 @@ final class OrderLicenseController
<h4><?php esc_html_e('Licenses', 'wc-licensed-product'); ?></h4> <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)): ?> <?php if (empty($licenses)): ?>
<p class="description"> <p class="description">
<?php esc_html_e('No licenses have been generated for this order yet.', 'wc-licensed-product'); ?> <?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> <em><?php esc_html_e('Licenses will be generated when the order is marked as paid/completed.', 'wc-licensed-product'); ?></em>
<?php endif; ?> <?php endif; ?>
</p> </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: ?> <?php else: ?>
<table class="widefat striped wclp-licenses-table"> <table class="widefat striped wclp-licenses-table">
<thead> <thead>
@@ -223,6 +250,29 @@ final class OrderLicenseController
); );
?> ?>
</p> </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; ?> <?php endif; ?>
</div> </div>
@@ -248,6 +298,9 @@ final class OrderLicenseController
.wclp-lifetime { color: #0073aa; font-weight: 500; } .wclp-lifetime { color: #0073aa; font-weight: 500; }
.wclp-edit-domain-btn { color: #0073aa; text-decoration: none; } .wclp-edit-domain-btn { color: #0073aa; text-decoration: none; }
.wclp-edit-domain-btn .dashicons { font-size: 16px; width: 16px; height: 16px; } .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> </style>
<?php <?php
} }
@@ -284,8 +337,9 @@ final class OrderLicenseController
'strings' => [ 'strings' => [
'saving' => __('Saving...', 'wc-licensed-product'), 'saving' => __('Saving...', 'wc-licensed-product'),
'saved' => __('Saved!', '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'), '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,}$/'; $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); 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,
]);
}
}
} }