2026-01-21 18:55:18 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Admin Controller
|
|
|
|
|
*
|
|
|
|
|
* @package Jeremias\WcLicensedProduct\Admin
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Jeremias\WcLicensedProduct\Admin;
|
|
|
|
|
|
|
|
|
|
use Jeremias\WcLicensedProduct\License\License;
|
|
|
|
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
|
|
|
|
use Twig\Environment;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handles admin pages for license management
|
|
|
|
|
*/
|
|
|
|
|
final class AdminController
|
|
|
|
|
{
|
|
|
|
|
private Environment $twig;
|
|
|
|
|
private LicenseManager $licenseManager;
|
|
|
|
|
|
|
|
|
|
public function __construct(Environment $twig, LicenseManager $licenseManager)
|
|
|
|
|
{
|
|
|
|
|
$this->twig = $twig;
|
|
|
|
|
$this->licenseManager = $licenseManager;
|
|
|
|
|
$this->registerHooks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register WordPress hooks
|
|
|
|
|
*/
|
|
|
|
|
private function registerHooks(): void
|
|
|
|
|
{
|
|
|
|
|
// Add admin menu
|
|
|
|
|
add_action('admin_menu', [$this, 'addAdminMenu']);
|
|
|
|
|
|
|
|
|
|
// Enqueue admin styles
|
|
|
|
|
add_action('admin_enqueue_scripts', [$this, 'enqueueStyles']);
|
|
|
|
|
|
|
|
|
|
// Handle admin actions
|
|
|
|
|
add_action('admin_init', [$this, 'handleAdminActions']);
|
|
|
|
|
|
|
|
|
|
// Add licenses column to orders list
|
|
|
|
|
add_filter('manage_edit-shop_order_columns', [$this, 'addOrdersLicenseColumn']);
|
|
|
|
|
add_action('manage_shop_order_posts_custom_column', [$this, 'displayOrdersLicenseColumn'], 10, 2);
|
|
|
|
|
|
|
|
|
|
// HPOS compatibility
|
|
|
|
|
add_filter('woocommerce_shop_order_list_table_columns', [$this, 'addOrdersLicenseColumn']);
|
|
|
|
|
add_action('woocommerce_shop_order_list_table_custom_column', [$this, 'displayOrdersLicenseColumnHpos'], 10, 2);
|
2026-01-21 20:32:35 +01:00
|
|
|
|
|
|
|
|
// Add to WooCommerce Reports
|
|
|
|
|
add_filter('woocommerce_admin_reports', [$this, 'addLicenseReports']);
|
2026-01-21 22:52:51 +01:00
|
|
|
|
|
|
|
|
// AJAX handler for live search
|
|
|
|
|
add_action('wp_ajax_wclp_live_search', [$this, 'handleLiveSearch']);
|
2026-01-21 23:13:07 +01:00
|
|
|
|
|
|
|
|
// AJAX handlers for inline editing
|
|
|
|
|
add_action('wp_ajax_wclp_update_license_status', [$this, 'handleAjaxStatusUpdate']);
|
|
|
|
|
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_revoke_license', [$this, 'handleAjaxRevoke']);
|
2026-01-23 11:37:06 +01:00
|
|
|
|
|
|
|
|
// AJAX handler for license testing
|
|
|
|
|
add_action('wp_ajax_wclp_test_license', [$this, 'handleAjaxTestLicense']);
|
2026-01-21 18:55:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add admin menu pages
|
|
|
|
|
*/
|
|
|
|
|
public function addAdminMenu(): void
|
|
|
|
|
{
|
|
|
|
|
add_submenu_page(
|
|
|
|
|
'woocommerce',
|
|
|
|
|
__('Licenses', 'wc-licensed-product'),
|
|
|
|
|
__('Licenses', 'wc-licensed-product'),
|
|
|
|
|
'manage_woocommerce',
|
|
|
|
|
'wc-licenses',
|
|
|
|
|
[$this, 'renderLicensesPage']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
/**
|
|
|
|
|
* Add license reports to WooCommerce Reports
|
|
|
|
|
*/
|
|
|
|
|
public function addLicenseReports(array $reports): array
|
|
|
|
|
{
|
|
|
|
|
$reports['licenses'] = [
|
|
|
|
|
'title' => __('Licenses', 'wc-licensed-product'),
|
|
|
|
|
'reports' => [
|
|
|
|
|
'overview' => [
|
|
|
|
|
'title' => __('Overview', 'wc-licensed-product'),
|
|
|
|
|
'description' => '',
|
|
|
|
|
'hide_title' => true,
|
|
|
|
|
'callback' => [$this, 'renderDashboardPage'],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return $reports;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 18:55:18 +01:00
|
|
|
/**
|
|
|
|
|
* Enqueue admin styles and scripts
|
|
|
|
|
*/
|
|
|
|
|
public function enqueueStyles(string $hook): void
|
|
|
|
|
{
|
2026-01-21 20:32:35 +01:00
|
|
|
// Check for our pages and WooCommerce Reports page with licenses tab
|
|
|
|
|
$isLicensePage = in_array($hook, ['woocommerce_page_wc-licenses', 'woocommerce_page_wc-license-dashboard'], true);
|
2026-01-22 11:57:05 +01:00
|
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking current page context
|
|
|
|
|
$currentTab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : '';
|
|
|
|
|
$isReportsPage = $hook === 'woocommerce_page_wc-reports' && $currentTab === 'licenses';
|
2026-01-21 20:32:35 +01:00
|
|
|
|
|
|
|
|
if (!$isLicensePage && !$isReportsPage) {
|
2026-01-21 18:55:18 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_enqueue_style(
|
|
|
|
|
'wc-licensed-product-admin',
|
|
|
|
|
WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/css/admin.css',
|
|
|
|
|
[],
|
|
|
|
|
WC_LICENSED_PRODUCT_VERSION
|
|
|
|
|
);
|
2026-01-21 22:52:51 +01:00
|
|
|
|
|
|
|
|
// Enqueue live search script on licenses page
|
|
|
|
|
if ($isLicensePage) {
|
|
|
|
|
wp_enqueue_script(
|
|
|
|
|
'wc-licensed-product-admin',
|
|
|
|
|
WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/js/admin-licenses.js',
|
|
|
|
|
['jquery'],
|
|
|
|
|
WC_LICENSED_PRODUCT_VERSION,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
wp_localize_script('wc-licensed-product-admin', 'wclpAdmin', [
|
|
|
|
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
|
|
|
|
'nonce' => wp_create_nonce('wclp_live_search'),
|
2026-01-21 23:13:07 +01:00
|
|
|
'editNonce' => wp_create_nonce('wclp_inline_edit'),
|
2026-01-21 22:52:51 +01:00
|
|
|
'strings' => [
|
|
|
|
|
'noResults' => __('No licenses found', 'wc-licensed-product'),
|
|
|
|
|
'searching' => __('Searching...', 'wc-licensed-product'),
|
|
|
|
|
'error' => __('Search failed', 'wc-licensed-product'),
|
2026-01-21 23:13:07 +01:00
|
|
|
'saving' => __('Saving...', 'wc-licensed-product'),
|
|
|
|
|
'saved' => __('Saved', 'wc-licensed-product'),
|
|
|
|
|
'saveFailed' => __('Save failed', 'wc-licensed-product'),
|
|
|
|
|
'confirmRevoke' => __('Are you sure you want to revoke this license? This action cannot be undone.', 'wc-licensed-product'),
|
|
|
|
|
'edit' => __('Edit', 'wc-licensed-product'),
|
|
|
|
|
'cancel' => __('Cancel', 'wc-licensed-product'),
|
|
|
|
|
'save' => __('Save', 'wc-licensed-product'),
|
|
|
|
|
'lifetime' => __('Lifetime', 'wc-licensed-product'),
|
|
|
|
|
'copied' => __('Copied!', 'wc-licensed-product'),
|
|
|
|
|
'copyFailed' => __('Copy failed', 'wc-licensed-product'),
|
|
|
|
|
],
|
|
|
|
|
'statuses' => [
|
|
|
|
|
['value' => 'active', 'label' => __('Active', 'wc-licensed-product')],
|
|
|
|
|
['value' => 'inactive', 'label' => __('Inactive', 'wc-licensed-product')],
|
|
|
|
|
['value' => 'expired', 'label' => __('Expired', 'wc-licensed-product')],
|
|
|
|
|
['value' => 'revoked', 'label' => __('Revoked', 'wc-licensed-product')],
|
2026-01-21 22:52:51 +01:00
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle AJAX live search request
|
|
|
|
|
*/
|
|
|
|
|
public function handleLiveSearch(): void
|
|
|
|
|
{
|
|
|
|
|
check_ajax_referer('wclp_live_search', 'nonce');
|
|
|
|
|
|
|
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
|
|
|
wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')], 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
|
|
|
|
|
|
|
|
|
|
if (strlen($search) < 2) {
|
|
|
|
|
wp_send_json_success(['results' => []]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$filters = ['search' => $search];
|
|
|
|
|
$licenses = $this->licenseManager->getAllLicenses(1, 10, $filters);
|
|
|
|
|
|
|
|
|
|
$results = [];
|
|
|
|
|
foreach ($licenses as $license) {
|
|
|
|
|
$product = wc_get_product($license->getProductId());
|
|
|
|
|
$customer = get_userdata($license->getCustomerId());
|
|
|
|
|
|
|
|
|
|
$results[] = [
|
|
|
|
|
'id' => $license->getId(),
|
|
|
|
|
'license_key' => $license->getLicenseKey(),
|
|
|
|
|
'domain' => $license->getDomain(),
|
|
|
|
|
'status' => $license->getStatus(),
|
|
|
|
|
'product_name' => $product ? $product->get_name() : __('Unknown', 'wc-licensed-product'),
|
|
|
|
|
'customer_name' => $customer ? $customer->display_name : __('Guest', 'wc-licensed-product'),
|
|
|
|
|
'customer_email' => $customer ? $customer->user_email : '',
|
|
|
|
|
'view_url' => admin_url('admin.php?page=wc-licenses&s=' . urlencode($license->getLicenseKey())),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_send_json_success(['results' => $results]);
|
2026-01-21 18:55:18 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 23:13:07 +01:00
|
|
|
/**
|
|
|
|
|
* Handle AJAX status update
|
|
|
|
|
*/
|
|
|
|
|
public function handleAjaxStatusUpdate(): 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = isset($_POST['license_id']) ? absint($_POST['license_id']) : 0;
|
|
|
|
|
$status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : '';
|
|
|
|
|
|
|
|
|
|
if (!$licenseId) {
|
|
|
|
|
wp_send_json_error(['message' => __('Invalid license ID.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$validStatuses = [License::STATUS_ACTIVE, License::STATUS_INACTIVE, License::STATUS_EXPIRED, License::STATUS_REVOKED];
|
|
|
|
|
if (!in_array($status, $validStatuses, true)) {
|
|
|
|
|
wp_send_json_error(['message' => __('Invalid status.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$success = $this->licenseManager->updateLicenseStatus($licenseId, $status);
|
|
|
|
|
|
|
|
|
|
if ($success) {
|
|
|
|
|
wp_send_json_success([
|
|
|
|
|
'message' => __('Status updated successfully.', 'wc-licensed-product'),
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'status_label' => ucfirst($status),
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
wp_send_json_error(['message' => __('Failed to update status.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle AJAX expiry date update
|
|
|
|
|
*/
|
|
|
|
|
public function handleAjaxExpiryUpdate(): 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = isset($_POST['license_id']) ? absint($_POST['license_id']) : 0;
|
|
|
|
|
$expiryDate = isset($_POST['expiry_date']) ? sanitize_text_field($_POST['expiry_date']) : '';
|
|
|
|
|
|
|
|
|
|
if (!$licenseId) {
|
|
|
|
|
wp_send_json_error(['message' => __('Invalid license ID.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle "lifetime" option
|
|
|
|
|
if (empty($expiryDate) || strtolower($expiryDate) === 'lifetime') {
|
|
|
|
|
$success = $this->licenseManager->setLicenseLifetime($licenseId);
|
|
|
|
|
if ($success) {
|
|
|
|
|
wp_send_json_success([
|
|
|
|
|
'message' => __('License set to lifetime.', 'wc-licensed-product'),
|
|
|
|
|
'expiry_date' => '',
|
|
|
|
|
'expiry_display' => __('Lifetime', 'wc-licensed-product'),
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
wp_send_json_error(['message' => __('Failed to update expiry date.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate date format
|
|
|
|
|
try {
|
|
|
|
|
$date = new \DateTimeImmutable($expiryDate);
|
|
|
|
|
$success = $this->licenseManager->updateLicenseExpiry($licenseId, $date);
|
|
|
|
|
|
|
|
|
|
if ($success) {
|
|
|
|
|
wp_send_json_success([
|
|
|
|
|
'message' => __('Expiry date updated successfully.', 'wc-licensed-product'),
|
|
|
|
|
'expiry_date' => $date->format('Y-m-d'),
|
|
|
|
|
'expiry_display' => $date->format(get_option('date_format')),
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
wp_send_json_error(['message' => __('Failed to update expiry date.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
wp_send_json_error(['message' => __('Invalid date format.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle AJAX domain update
|
|
|
|
|
*/
|
|
|
|
|
public function handleAjaxDomainUpdate(): 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = isset($_POST['license_id']) ? absint($_POST['license_id']) : 0;
|
|
|
|
|
$domain = isset($_POST['domain']) ? sanitize_text_field($_POST['domain']) : '';
|
|
|
|
|
|
|
|
|
|
if (!$licenseId) {
|
|
|
|
|
wp_send_json_error(['message' => __('Invalid license ID.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (empty($domain)) {
|
|
|
|
|
wp_send_json_error(['message' => __('Domain cannot be empty.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$success = $this->licenseManager->transferLicense($licenseId, $domain);
|
|
|
|
|
|
|
|
|
|
if ($success) {
|
|
|
|
|
// Get the normalized domain from the license
|
|
|
|
|
$license = $this->licenseManager->getLicenseById($licenseId);
|
|
|
|
|
$normalizedDomain = $license ? $license->getDomain() : $domain;
|
|
|
|
|
|
|
|
|
|
wp_send_json_success([
|
|
|
|
|
'message' => __('Domain updated successfully.', 'wc-licensed-product'),
|
|
|
|
|
'domain' => $normalizedDomain,
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
wp_send_json_error(['message' => __('Failed to update domain.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle AJAX revoke
|
|
|
|
|
*/
|
|
|
|
|
public function handleAjaxRevoke(): 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = isset($_POST['license_id']) ? absint($_POST['license_id']) : 0;
|
|
|
|
|
|
|
|
|
|
if (!$licenseId) {
|
|
|
|
|
wp_send_json_error(['message' => __('Invalid license ID.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$success = $this->licenseManager->updateLicenseStatus($licenseId, License::STATUS_REVOKED);
|
|
|
|
|
|
|
|
|
|
if ($success) {
|
|
|
|
|
wp_send_json_success([
|
|
|
|
|
'message' => __('License revoked successfully.', 'wc-licensed-product'),
|
|
|
|
|
'status' => License::STATUS_REVOKED,
|
|
|
|
|
'status_label' => ucfirst(License::STATUS_REVOKED),
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
wp_send_json_error(['message' => __('Failed to revoke license.', 'wc-licensed-product')]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 11:37:06 +01:00
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
|
2026-01-27 21:22:45 +01:00
|
|
|
// Enrich result with product name for display in the popup
|
|
|
|
|
if (!empty($result['valid']) && isset($result['license'])) {
|
|
|
|
|
// Get product name
|
|
|
|
|
$productId = $result['license']['product_id'] ?? null;
|
|
|
|
|
if ($productId) {
|
|
|
|
|
$product = wc_get_product($productId);
|
|
|
|
|
$result['product_name'] = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flatten expires_at for easier access in JavaScript
|
|
|
|
|
$result['expires_at'] = $result['license']['expires_at'] ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 11:37:06 +01:00
|
|
|
wp_send_json_success($result);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 18:55:18 +01:00
|
|
|
/**
|
|
|
|
|
* Handle admin actions (update, delete licenses)
|
|
|
|
|
*/
|
|
|
|
|
public function handleAdminActions(): void
|
|
|
|
|
{
|
|
|
|
|
if (!isset($_GET['page']) || $_GET['page'] !== 'wc-licenses') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle status update
|
|
|
|
|
if (isset($_POST['action']) && $_POST['action'] === 'update_license_status') {
|
|
|
|
|
$this->handleStatusUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle delete
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['license_id'])) {
|
|
|
|
|
$this->handleDelete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle revoke
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'revoke' && isset($_GET['license_id'])) {
|
|
|
|
|
$this->handleRevoke();
|
|
|
|
|
}
|
2026-01-21 20:32:35 +01:00
|
|
|
|
|
|
|
|
// Handle extend
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'extend' && isset($_GET['license_id'])) {
|
|
|
|
|
$this->handleExtend();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle set lifetime
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'lifetime' && isset($_GET['license_id'])) {
|
|
|
|
|
$this->handleSetLifetime();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle bulk actions
|
|
|
|
|
if (isset($_POST['bulk_action']) && !empty($_POST['license_ids'])) {
|
|
|
|
|
$this->handleBulkAction();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle transfer
|
|
|
|
|
if (isset($_POST['action']) && $_POST['action'] === 'transfer_license') {
|
|
|
|
|
$this->handleTransfer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle CSV export
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'export_csv') {
|
|
|
|
|
$this->handleCsvExport();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle CSV import page
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'import_csv') {
|
|
|
|
|
// Show import form - handled in renderImportPage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle CSV import upload
|
|
|
|
|
if (isset($_POST['action']) && $_POST['action'] === 'process_import_csv') {
|
|
|
|
|
$this->handleCsvImport();
|
|
|
|
|
}
|
2026-01-21 18:55:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle license status update
|
|
|
|
|
*/
|
|
|
|
|
private function handleStatusUpdate(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'update_license_status')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = absint($_POST['license_id'] ?? 0);
|
|
|
|
|
$status = sanitize_text_field($_POST['status'] ?? '');
|
|
|
|
|
|
|
|
|
|
if ($licenseId && in_array($status, [License::STATUS_ACTIVE, License::STATUS_INACTIVE, License::STATUS_REVOKED], true)) {
|
|
|
|
|
$this->licenseManager->updateLicenseStatus($licenseId, $status);
|
|
|
|
|
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&updated=1'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle license deletion
|
|
|
|
|
*/
|
|
|
|
|
private function handleDelete(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'delete_license')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = absint($_GET['license_id'] ?? 0);
|
|
|
|
|
if ($licenseId) {
|
|
|
|
|
$this->licenseManager->deleteLicense($licenseId);
|
|
|
|
|
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&deleted=1'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle license revocation
|
|
|
|
|
*/
|
|
|
|
|
private function handleRevoke(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'revoke_license')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = absint($_GET['license_id'] ?? 0);
|
|
|
|
|
if ($licenseId) {
|
|
|
|
|
$this->licenseManager->updateLicenseStatus($licenseId, License::STATUS_REVOKED);
|
|
|
|
|
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&revoked=1'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
/**
|
|
|
|
|
* Handle license extension
|
|
|
|
|
*/
|
|
|
|
|
private function handleExtend(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'extend_license')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = absint($_GET['license_id'] ?? 0);
|
|
|
|
|
$days = absint($_GET['days'] ?? 30);
|
|
|
|
|
|
|
|
|
|
if ($licenseId && $days > 0) {
|
|
|
|
|
$this->licenseManager->extendLicense($licenseId, $days);
|
|
|
|
|
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&extended=1'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle set license to lifetime
|
|
|
|
|
*/
|
|
|
|
|
private function handleSetLifetime(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'lifetime_license')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = absint($_GET['license_id'] ?? 0);
|
|
|
|
|
if ($licenseId) {
|
|
|
|
|
$this->licenseManager->setLicenseLifetime($licenseId);
|
|
|
|
|
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&lifetime=1'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle license transfer
|
|
|
|
|
*/
|
|
|
|
|
private function handleTransfer(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'transfer_license')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenseId = absint($_POST['license_id'] ?? 0);
|
|
|
|
|
$newDomain = sanitize_text_field($_POST['new_domain'] ?? '');
|
|
|
|
|
|
|
|
|
|
if ($licenseId && !empty($newDomain)) {
|
|
|
|
|
$success = $this->licenseManager->transferLicense($licenseId, $newDomain);
|
|
|
|
|
|
|
|
|
|
if ($success) {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&transferred=1'));
|
|
|
|
|
} else {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&transfer_failed=1'));
|
|
|
|
|
}
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle CSV export
|
|
|
|
|
*/
|
|
|
|
|
private function handleCsvExport(): void
|
|
|
|
|
{
|
2026-01-23 21:18:32 +01:00
|
|
|
// Verify nonce for CSRF protection
|
|
|
|
|
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'export_licenses_csv')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
|
|
|
wp_die(__('You do not have permission to export licenses.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $this->licenseManager->exportLicensesForCsv();
|
|
|
|
|
|
|
|
|
|
if (empty($data)) {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&export_empty=1'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set headers for CSV download
|
|
|
|
|
$filename = 'licenses-export-' . gmdate('Y-m-d-His') . '.csv';
|
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
|
|
|
header('Content-Disposition: attachment; filename=' . $filename);
|
|
|
|
|
header('Pragma: no-cache');
|
|
|
|
|
header('Expires: 0');
|
|
|
|
|
|
|
|
|
|
$output = fopen('php://output', 'w');
|
|
|
|
|
|
|
|
|
|
// Write BOM for UTF-8
|
|
|
|
|
fwrite($output, "\xEF\xBB\xBF");
|
|
|
|
|
|
|
|
|
|
// Write header row
|
|
|
|
|
fputcsv($output, array_keys($data[0]));
|
|
|
|
|
|
|
|
|
|
// Write data rows
|
|
|
|
|
foreach ($data as $row) {
|
|
|
|
|
fputcsv($output, $row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fclose($output);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle CSV import
|
|
|
|
|
*/
|
|
|
|
|
private function handleCsvImport(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'import_licenses_csv')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
|
|
|
wp_die(__('You do not have permission to import licenses.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if file was uploaded
|
|
|
|
|
if (!isset($_FILES['import_file']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&action=import_csv&import_error=upload'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$file = $_FILES['import_file'];
|
|
|
|
|
|
|
|
|
|
// Validate file type
|
|
|
|
|
$fileType = wp_check_filetype($file['name']);
|
|
|
|
|
if ($fileType['ext'] !== 'csv') {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&action=import_csv&import_error=filetype'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read the CSV file
|
|
|
|
|
$handle = fopen($file['tmp_name'], 'r');
|
|
|
|
|
if (!$handle) {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&action=import_csv&import_error=read'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get import options
|
|
|
|
|
$skipFirstRow = isset($_POST['skip_first_row']) && $_POST['skip_first_row'] === '1';
|
|
|
|
|
$updateExisting = isset($_POST['update_existing']) && $_POST['update_existing'] === '1';
|
|
|
|
|
|
|
|
|
|
// Skip BOM if present
|
|
|
|
|
$bom = fread($handle, 3);
|
|
|
|
|
if ($bom !== "\xEF\xBB\xBF") {
|
|
|
|
|
rewind($handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read header row if skipping
|
|
|
|
|
if ($skipFirstRow) {
|
|
|
|
|
fgetcsv($handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$imported = 0;
|
|
|
|
|
$updated = 0;
|
|
|
|
|
$skipped = 0;
|
|
|
|
|
$errors = [];
|
|
|
|
|
|
|
|
|
|
while (($row = fgetcsv($handle)) !== false) {
|
|
|
|
|
// Skip empty rows
|
|
|
|
|
if (empty($row) || (count($row) === 1 && empty($row[0]))) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Map CSV columns (expected format from export):
|
|
|
|
|
// ID, License Key, Product, Product ID, Order ID, Order Number, Customer, Customer Email, Customer ID, Domain, Status, Activations, Max Activations, Expires At, Created At, Updated At
|
|
|
|
|
// For import we need: License Key (or generate), Product ID, Customer ID, Domain, Status, Max Activations, Expires At
|
|
|
|
|
$result = $this->processImportRow($row, $updateExisting);
|
|
|
|
|
|
|
|
|
|
if ($result === 'imported') {
|
|
|
|
|
$imported++;
|
|
|
|
|
} elseif ($result === 'updated') {
|
|
|
|
|
$updated++;
|
|
|
|
|
} elseif ($result === 'skipped') {
|
|
|
|
|
$skipped++;
|
|
|
|
|
} else {
|
|
|
|
|
$errors[] = $result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fclose($handle);
|
|
|
|
|
|
|
|
|
|
// Build redirect URL with results
|
|
|
|
|
$redirectUrl = add_query_arg([
|
|
|
|
|
'page' => 'wc-licenses',
|
|
|
|
|
'imported' => $imported,
|
|
|
|
|
'updated' => $updated,
|
|
|
|
|
'skipped' => $skipped,
|
|
|
|
|
'import_errors' => count($errors),
|
|
|
|
|
], admin_url('admin.php'));
|
|
|
|
|
|
|
|
|
|
wp_redirect($redirectUrl);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process a single import row
|
|
|
|
|
*
|
|
|
|
|
* @param array $row CSV row data
|
|
|
|
|
* @param bool $updateExisting Whether to update existing licenses
|
|
|
|
|
* @return string Result: 'imported', 'updated', 'skipped', or error message
|
|
|
|
|
*/
|
|
|
|
|
private function processImportRow(array $row, bool $updateExisting): string
|
|
|
|
|
{
|
|
|
|
|
// Determine if this is from our export format or simplified format
|
|
|
|
|
// Export format has 16 columns, simplified has fewer
|
|
|
|
|
|
|
|
|
|
if (count($row) >= 10) {
|
|
|
|
|
// Full export format
|
|
|
|
|
$licenseKey = trim($row[1] ?? '');
|
|
|
|
|
$productId = absint($row[3] ?? 0);
|
|
|
|
|
$orderId = absint($row[4] ?? 0);
|
|
|
|
|
$customerId = absint($row[8] ?? 0);
|
|
|
|
|
$domain = trim($row[9] ?? '');
|
|
|
|
|
$status = strtolower(trim($row[10] ?? 'active'));
|
|
|
|
|
$activationsCount = absint($row[11] ?? 1);
|
|
|
|
|
$maxActivations = absint($row[12] ?? 1);
|
|
|
|
|
$expiresAt = trim($row[13] ?? '');
|
|
|
|
|
} else {
|
|
|
|
|
// Simplified format: License Key, Product ID, Customer ID, Domain, Status, Max Activations, Expires At
|
|
|
|
|
$licenseKey = trim($row[0] ?? '');
|
|
|
|
|
$productId = absint($row[1] ?? 0);
|
|
|
|
|
$customerId = absint($row[2] ?? 0);
|
|
|
|
|
$domain = trim($row[3] ?? '');
|
|
|
|
|
$status = strtolower(trim($row[4] ?? 'active'));
|
|
|
|
|
$maxActivations = absint($row[5] ?? 1);
|
|
|
|
|
$expiresAt = trim($row[6] ?? '');
|
|
|
|
|
$orderId = 0;
|
|
|
|
|
$activationsCount = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate required fields
|
|
|
|
|
if (empty($domain)) {
|
|
|
|
|
return sprintf(__('Row missing domain', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($productId <= 0) {
|
|
|
|
|
return sprintf(__('Row missing valid product ID', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if license key already exists
|
|
|
|
|
if (!empty($licenseKey)) {
|
|
|
|
|
$existing = $this->licenseManager->getLicenseByKey($licenseKey);
|
|
|
|
|
if ($existing) {
|
|
|
|
|
if ($updateExisting) {
|
|
|
|
|
// Update existing license
|
|
|
|
|
$this->licenseManager->updateLicenseDomain($existing->getId(), $domain);
|
|
|
|
|
if (in_array($status, [License::STATUS_ACTIVE, License::STATUS_INACTIVE, License::STATUS_REVOKED], true)) {
|
|
|
|
|
$this->licenseManager->updateLicenseStatus($existing->getId(), $status);
|
|
|
|
|
}
|
|
|
|
|
return 'updated';
|
|
|
|
|
}
|
|
|
|
|
return 'skipped';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Generate new license key
|
|
|
|
|
$licenseKey = $this->licenseManager->generateLicenseKey();
|
|
|
|
|
while ($this->licenseManager->getLicenseByKey($licenseKey)) {
|
|
|
|
|
$licenseKey = $this->licenseManager->generateLicenseKey();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normalize status
|
|
|
|
|
if (!in_array($status, [License::STATUS_ACTIVE, License::STATUS_INACTIVE, License::STATUS_EXPIRED, License::STATUS_REVOKED], true)) {
|
|
|
|
|
$status = License::STATUS_ACTIVE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse expiration date
|
|
|
|
|
$expiresAtParsed = null;
|
|
|
|
|
if (!empty($expiresAt) && strtolower($expiresAt) !== 'lifetime') {
|
|
|
|
|
try {
|
|
|
|
|
$expiresAtParsed = new \DateTimeImmutable($expiresAt);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Invalid date, leave as null (lifetime)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the license
|
|
|
|
|
$result = $this->licenseManager->importLicense(
|
|
|
|
|
$licenseKey,
|
|
|
|
|
$productId,
|
|
|
|
|
$customerId,
|
|
|
|
|
$domain,
|
|
|
|
|
$orderId,
|
|
|
|
|
$status,
|
|
|
|
|
$maxActivations,
|
|
|
|
|
$activationsCount,
|
|
|
|
|
$expiresAtParsed
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $result ? 'imported' : sprintf(__('Failed to import license for domain %s', 'wc-licensed-product'), $domain);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle bulk actions
|
|
|
|
|
*/
|
|
|
|
|
private function handleBulkAction(): void
|
|
|
|
|
{
|
|
|
|
|
if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'bulk_license_action')) {
|
|
|
|
|
wp_die(__('Security check failed.', 'wc-licensed-product'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$action = sanitize_text_field($_POST['bulk_action'] ?? '');
|
|
|
|
|
$licenseIds = array_map('absint', (array) ($_POST['license_ids'] ?? []));
|
|
|
|
|
|
|
|
|
|
if (empty($licenseIds)) {
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses'));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
|
|
switch ($action) {
|
|
|
|
|
case 'activate':
|
|
|
|
|
$count = $this->licenseManager->bulkUpdateStatus($licenseIds, License::STATUS_ACTIVE);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_activated=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'deactivate':
|
|
|
|
|
$count = $this->licenseManager->bulkUpdateStatus($licenseIds, License::STATUS_INACTIVE);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_deactivated=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'revoke':
|
|
|
|
|
$count = $this->licenseManager->bulkUpdateStatus($licenseIds, License::STATUS_REVOKED);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_revoked=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'delete':
|
|
|
|
|
$count = $this->licenseManager->bulkDelete($licenseIds);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_deleted=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'extend_30':
|
|
|
|
|
$count = $this->licenseManager->bulkExtend($licenseIds, 30);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_extended=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'extend_90':
|
|
|
|
|
$count = $this->licenseManager->bulkExtend($licenseIds, 90);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_extended=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'extend_365':
|
|
|
|
|
$count = $this->licenseManager->bulkExtend($licenseIds, 365);
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses&bulk_extended=' . $count));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
wp_redirect(admin_url('admin.php?page=wc-licenses'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render license dashboard page
|
|
|
|
|
*/
|
|
|
|
|
public function renderDashboardPage(): void
|
|
|
|
|
{
|
|
|
|
|
$stats = $this->licenseManager->getStatistics();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
echo $this->twig->render('admin/dashboard.html.twig', [
|
|
|
|
|
'stats' => $stats,
|
|
|
|
|
'admin_url' => admin_url('admin.php'),
|
|
|
|
|
]);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Fallback to PHP template
|
|
|
|
|
$this->renderDashboardPageFallback($stats);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fallback render for dashboard page
|
|
|
|
|
*/
|
|
|
|
|
private function renderDashboardPageFallback(array $stats): void
|
|
|
|
|
{
|
|
|
|
|
?>
|
|
|
|
|
<div class="wrap wclp-dashboard">
|
|
|
|
|
<h1><?php esc_html_e('License Dashboard', 'wc-licensed-product'); ?></h1>
|
|
|
|
|
|
|
|
|
|
<div class="wclp-dashboard-stats">
|
|
|
|
|
<div class="wclp-stat-cards">
|
|
|
|
|
<div class="wclp-stat-card wclp-stat-total">
|
|
|
|
|
<div class="wclp-stat-icon"><span class="dashicons dashicons-admin-network"></span></div>
|
|
|
|
|
<div class="wclp-stat-content">
|
|
|
|
|
<span class="wclp-stat-number"><?php echo esc_html($stats['total']); ?></span>
|
|
|
|
|
<span class="wclp-stat-label"><?php esc_html_e('Total Licenses', 'wc-licensed-product'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="wclp-stat-card wclp-stat-active">
|
|
|
|
|
<div class="wclp-stat-icon"><span class="dashicons dashicons-yes-alt"></span></div>
|
|
|
|
|
<div class="wclp-stat-content">
|
|
|
|
|
<span class="wclp-stat-number"><?php echo esc_html($stats['by_status'][License::STATUS_ACTIVE]); ?></span>
|
|
|
|
|
<span class="wclp-stat-label"><?php esc_html_e('Active', 'wc-licensed-product'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="wclp-stat-card wclp-stat-inactive">
|
|
|
|
|
<div class="wclp-stat-icon"><span class="dashicons dashicons-marker"></span></div>
|
|
|
|
|
<div class="wclp-stat-content">
|
|
|
|
|
<span class="wclp-stat-number"><?php echo esc_html($stats['by_status'][License::STATUS_INACTIVE]); ?></span>
|
|
|
|
|
<span class="wclp-stat-label"><?php esc_html_e('Inactive', 'wc-licensed-product'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="wclp-stat-card wclp-stat-expired">
|
|
|
|
|
<div class="wclp-stat-icon"><span class="dashicons dashicons-calendar-alt"></span></div>
|
|
|
|
|
<div class="wclp-stat-content">
|
|
|
|
|
<span class="wclp-stat-number"><?php echo esc_html($stats['by_status'][License::STATUS_EXPIRED]); ?></span>
|
|
|
|
|
<span class="wclp-stat-label"><?php esc_html_e('Expired', 'wc-licensed-product'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="wclp-stat-card wclp-stat-revoked">
|
|
|
|
|
<div class="wclp-stat-icon"><span class="dashicons dashicons-dismiss"></span></div>
|
|
|
|
|
<div class="wclp-stat-content">
|
|
|
|
|
<span class="wclp-stat-number"><?php echo esc_html($stats['by_status'][License::STATUS_REVOKED]); ?></span>
|
|
|
|
|
<span class="wclp-stat-label"><?php esc_html_e('Revoked', 'wc-licensed-product'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<?php if ($stats['expiring_soon'] > 0): ?>
|
|
|
|
|
<div class="notice notice-warning">
|
|
|
|
|
<p>
|
|
|
|
|
<span class="dashicons dashicons-warning"></span>
|
|
|
|
|
<strong><?php esc_html_e('Attention:', 'wc-licensed-product'); ?></strong>
|
|
|
|
|
<?php
|
|
|
|
|
printf(
|
|
|
|
|
/* translators: %d: number of licenses expiring */
|
|
|
|
|
_n(
|
|
|
|
|
'%d license is expiring within the next 30 days.',
|
|
|
|
|
'%d licenses are expiring within the next 30 days.',
|
|
|
|
|
$stats['expiring_soon'],
|
|
|
|
|
'wc-licensed-product'
|
|
|
|
|
),
|
|
|
|
|
$stats['expiring_soon']
|
|
|
|
|
);
|
|
|
|
|
?>
|
|
|
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>"><?php esc_html_e('View Licenses', 'wc-licensed-product'); ?></a>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
|
|
<div class="wclp-dashboard-actions">
|
|
|
|
|
<h2><?php esc_html_e('Quick Actions', 'wc-licensed-product'); ?></h2>
|
|
|
|
|
<div class="wclp-action-buttons">
|
|
|
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>" class="button button-primary">
|
|
|
|
|
<span class="dashicons dashicons-admin-network"></span>
|
|
|
|
|
<?php esc_html_e('Manage Licenses', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
2026-01-23 21:18:32 +01:00
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=wc-licenses&action=export_csv'), 'export_licenses_csv')); ?>" class="button">
|
2026-01-21 20:32:35 +01:00
|
|
|
<span class="dashicons dashicons-download"></span>
|
|
|
|
|
<?php esc_html_e('Export to CSV', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-settings&tab=licensed_product')); ?>" class="button">
|
|
|
|
|
<span class="dashicons dashicons-admin-generic"></span>
|
|
|
|
|
<?php esc_html_e('Settings', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 18:55:18 +01:00
|
|
|
/**
|
|
|
|
|
* Render licenses admin page
|
|
|
|
|
*/
|
|
|
|
|
public function renderLicensesPage(): void
|
|
|
|
|
{
|
2026-01-21 20:32:35 +01:00
|
|
|
// Check if showing import page
|
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] === 'import_csv') {
|
|
|
|
|
$this->renderImportPage();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 18:55:18 +01:00
|
|
|
$page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
|
|
|
|
|
$perPage = 20;
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
// Build filters from query params
|
|
|
|
|
$filters = [];
|
|
|
|
|
if (!empty($_GET['s'])) {
|
|
|
|
|
$filters['search'] = sanitize_text_field($_GET['s']);
|
|
|
|
|
}
|
|
|
|
|
if (!empty($_GET['status']) && $_GET['status'] !== 'all') {
|
|
|
|
|
$filters['status'] = sanitize_text_field($_GET['status']);
|
|
|
|
|
}
|
|
|
|
|
if (!empty($_GET['product_id'])) {
|
|
|
|
|
$filters['product_id'] = absint($_GET['product_id']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$licenses = $this->licenseManager->getAllLicenses($page, $perPage, $filters);
|
|
|
|
|
$totalLicenses = $this->licenseManager->getLicenseCount($filters);
|
|
|
|
|
$totalPages = (int) ceil($totalLicenses / $perPage);
|
|
|
|
|
|
|
|
|
|
// Get products for filter dropdown
|
|
|
|
|
$licensedProducts = $this->licenseManager->getLicensedProducts();
|
2026-01-21 18:55:18 +01:00
|
|
|
|
|
|
|
|
// Enrich licenses with related data
|
|
|
|
|
$enrichedLicenses = [];
|
|
|
|
|
foreach ($licenses as $license) {
|
|
|
|
|
$product = wc_get_product($license->getProductId());
|
|
|
|
|
$order = wc_get_order($license->getOrderId());
|
|
|
|
|
$customer = get_userdata($license->getCustomerId());
|
|
|
|
|
|
|
|
|
|
$enrichedLicenses[] = [
|
|
|
|
|
'license' => $license,
|
|
|
|
|
'product_name' => $product ? $product->get_name() : __('Unknown', 'wc-licensed-product'),
|
|
|
|
|
'product_edit_url' => $product ? get_edit_post_link($product->get_id()) : '',
|
|
|
|
|
'order_number' => $order ? $order->get_order_number() : '',
|
|
|
|
|
'order_edit_url' => $order ? $order->get_edit_order_url() : '',
|
|
|
|
|
'customer_name' => $customer ? $customer->display_name : __('Guest', 'wc-licensed-product'),
|
|
|
|
|
'customer_email' => $customer ? $customer->user_email : '',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
// Add URL helper functions to Twig
|
|
|
|
|
$this->twig->addFunction(new \Twig\TwigFunction('extend_url', function (int $licenseId, int $days = 30): string {
|
|
|
|
|
return wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=extend&license_id=' . $licenseId . '&days=' . $days),
|
|
|
|
|
'extend_license'
|
|
|
|
|
);
|
|
|
|
|
}));
|
|
|
|
|
$this->twig->addFunction(new \Twig\TwigFunction('lifetime_url', function (int $licenseId): string {
|
|
|
|
|
return wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=lifetime&license_id=' . $licenseId),
|
|
|
|
|
'lifetime_license'
|
|
|
|
|
);
|
|
|
|
|
}));
|
|
|
|
|
$this->twig->addFunction(new \Twig\TwigFunction('revoke_url', function (int $licenseId): string {
|
|
|
|
|
return wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=revoke&license_id=' . $licenseId),
|
|
|
|
|
'revoke_license'
|
|
|
|
|
);
|
|
|
|
|
}));
|
|
|
|
|
$this->twig->addFunction(new \Twig\TwigFunction('delete_url', function (int $licenseId): string {
|
|
|
|
|
return wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=delete&license_id=' . $licenseId),
|
|
|
|
|
'delete_license'
|
|
|
|
|
);
|
|
|
|
|
}));
|
|
|
|
|
$this->twig->addFunction(new \Twig\TwigFunction('transfer_nonce', function (): string {
|
|
|
|
|
return wp_create_nonce('transfer_license');
|
|
|
|
|
}));
|
2026-01-23 21:18:32 +01:00
|
|
|
$this->twig->addFunction(new \Twig\TwigFunction('export_csv_url', function (): string {
|
|
|
|
|
return wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=export_csv'),
|
|
|
|
|
'export_licenses_csv'
|
|
|
|
|
);
|
|
|
|
|
}));
|
2026-01-21 20:32:35 +01:00
|
|
|
|
2026-01-21 18:55:18 +01:00
|
|
|
try {
|
|
|
|
|
echo $this->twig->render('admin/licenses.html.twig', [
|
|
|
|
|
'licenses' => $enrichedLicenses,
|
|
|
|
|
'current_page' => $page,
|
|
|
|
|
'total_pages' => $totalPages,
|
|
|
|
|
'total_licenses' => $totalLicenses,
|
|
|
|
|
'admin_url' => admin_url('admin.php?page=wc-licenses'),
|
|
|
|
|
'notices' => $this->getNotices(),
|
2026-01-21 20:32:35 +01:00
|
|
|
'filters' => $filters,
|
|
|
|
|
'products' => $licensedProducts,
|
2026-01-21 18:55:18 +01:00
|
|
|
]);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Fallback to PHP template
|
2026-01-21 22:55:49 +01:00
|
|
|
$this->renderLicensesPageFallback($enrichedLicenses, $page, $totalPages, $totalLicenses, $filters, $licensedProducts);
|
2026-01-21 18:55:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get admin notices
|
|
|
|
|
*/
|
|
|
|
|
private function getNotices(): array
|
|
|
|
|
{
|
|
|
|
|
$notices = [];
|
|
|
|
|
|
|
|
|
|
if (isset($_GET['updated'])) {
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => __('License updated successfully.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['deleted'])) {
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => __('License deleted successfully.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['revoked'])) {
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => __('License revoked successfully.', 'wc-licensed-product')];
|
|
|
|
|
}
|
2026-01-21 20:32:35 +01:00
|
|
|
if (isset($_GET['extended'])) {
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => __('License extended successfully.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['lifetime'])) {
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => __('License set to lifetime successfully.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['bulk_activated'])) {
|
|
|
|
|
$count = absint($_GET['bulk_activated']);
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => sprintf(
|
|
|
|
|
/* translators: %d: number of licenses */
|
|
|
|
|
_n('%d license activated.', '%d licenses activated.', $count, 'wc-licensed-product'),
|
|
|
|
|
$count
|
|
|
|
|
)];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['bulk_deactivated'])) {
|
|
|
|
|
$count = absint($_GET['bulk_deactivated']);
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => sprintf(
|
|
|
|
|
/* translators: %d: number of licenses */
|
|
|
|
|
_n('%d license deactivated.', '%d licenses deactivated.', $count, 'wc-licensed-product'),
|
|
|
|
|
$count
|
|
|
|
|
)];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['bulk_revoked'])) {
|
|
|
|
|
$count = absint($_GET['bulk_revoked']);
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => sprintf(
|
|
|
|
|
/* translators: %d: number of licenses */
|
|
|
|
|
_n('%d license revoked.', '%d licenses revoked.', $count, 'wc-licensed-product'),
|
|
|
|
|
$count
|
|
|
|
|
)];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['bulk_deleted'])) {
|
|
|
|
|
$count = absint($_GET['bulk_deleted']);
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => sprintf(
|
|
|
|
|
/* translators: %d: number of licenses */
|
|
|
|
|
_n('%d license deleted.', '%d licenses deleted.', $count, 'wc-licensed-product'),
|
|
|
|
|
$count
|
|
|
|
|
)];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['bulk_extended'])) {
|
|
|
|
|
$count = absint($_GET['bulk_extended']);
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => sprintf(
|
|
|
|
|
/* translators: %d: number of licenses */
|
|
|
|
|
_n('%d license extended.', '%d licenses extended.', $count, 'wc-licensed-product'),
|
|
|
|
|
$count
|
|
|
|
|
)];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['transferred'])) {
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => __('License transferred to new domain successfully.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['transfer_failed'])) {
|
|
|
|
|
$notices[] = ['type' => 'error', 'message' => __('Failed to transfer license. The license may be revoked or invalid.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['export_empty'])) {
|
|
|
|
|
$notices[] = ['type' => 'warning', 'message' => __('No licenses to export.', 'wc-licensed-product')];
|
|
|
|
|
}
|
|
|
|
|
if (isset($_GET['imported'])) {
|
|
|
|
|
$imported = absint($_GET['imported']);
|
|
|
|
|
$updated = absint($_GET['updated'] ?? 0);
|
|
|
|
|
$skipped = absint($_GET['skipped'] ?? 0);
|
|
|
|
|
$errors = absint($_GET['import_errors'] ?? 0);
|
|
|
|
|
|
|
|
|
|
$message = sprintf(
|
|
|
|
|
/* translators: %d: number of licenses imported */
|
|
|
|
|
_n('%d license imported.', '%d licenses imported.', $imported, 'wc-licensed-product'),
|
|
|
|
|
$imported
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($updated > 0) {
|
|
|
|
|
$message .= ' ' . sprintf(
|
|
|
|
|
/* translators: %d: number of licenses updated */
|
|
|
|
|
_n('%d updated.', '%d updated.', $updated, 'wc-licensed-product'),
|
|
|
|
|
$updated
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($skipped > 0) {
|
|
|
|
|
$message .= ' ' . sprintf(
|
|
|
|
|
/* translators: %d: number of licenses skipped */
|
|
|
|
|
_n('%d skipped.', '%d skipped.', $skipped, 'wc-licensed-product'),
|
|
|
|
|
$skipped
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($errors > 0) {
|
|
|
|
|
$message .= ' ' . sprintf(
|
|
|
|
|
/* translators: %d: number of errors */
|
|
|
|
|
_n('%d error.', '%d errors.', $errors, 'wc-licensed-product'),
|
|
|
|
|
$errors
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notices[] = ['type' => 'success', 'message' => $message];
|
|
|
|
|
}
|
2026-01-21 18:55:18 +01:00
|
|
|
|
|
|
|
|
return $notices;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fallback render for licenses page
|
|
|
|
|
*/
|
2026-01-21 22:55:49 +01:00
|
|
|
private function renderLicensesPageFallback(array $enrichedLicenses, int $page, int $totalPages, int $totalLicenses, array $filters = [], array $products = []): void
|
2026-01-21 18:55:18 +01:00
|
|
|
{
|
|
|
|
|
?>
|
|
|
|
|
<div class="wrap">
|
2026-01-21 22:55:49 +01:00
|
|
|
<h1 class="wp-heading-inline"><?php esc_html_e('Licenses', 'wc-licensed-product'); ?></h1>
|
2026-01-23 21:18:32 +01:00
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=wc-licenses&action=export_csv'), 'export_licenses_csv')); ?>" class="page-title-action">
|
2026-01-21 22:55:49 +01:00
|
|
|
<span class="dashicons dashicons-download" style="vertical-align: middle;"></span>
|
|
|
|
|
<?php esc_html_e('Export CSV', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses&action=import_csv')); ?>" class="page-title-action">
|
|
|
|
|
<span class="dashicons dashicons-upload" style="vertical-align: middle;"></span>
|
|
|
|
|
<?php esc_html_e('Import CSV', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<hr class="wp-header-end">
|
2026-01-21 18:55:18 +01:00
|
|
|
|
|
|
|
|
<?php foreach ($this->getNotices() as $notice): ?>
|
|
|
|
|
<div class="notice notice-<?php echo esc_attr($notice['type']); ?> is-dismissible">
|
|
|
|
|
<p><?php echo esc_html($notice['message']); ?></p>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
2026-01-21 22:55:49 +01:00
|
|
|
<!-- Search and Filter Form -->
|
|
|
|
|
<form method="get" action="" class="wclp-filter-form">
|
|
|
|
|
<input type="hidden" name="page" value="wc-licenses">
|
|
|
|
|
|
|
|
|
|
<p class="search-box">
|
|
|
|
|
<label class="screen-reader-text" for="license-search-input"><?php esc_html_e('Search Licenses', 'wc-licensed-product'); ?></label>
|
|
|
|
|
<input type="search" id="license-search-input" name="s" value="<?php echo esc_attr($filters['search'] ?? ''); ?>"
|
|
|
|
|
placeholder="<?php esc_attr_e('Search license key or domain...', 'wc-licensed-product'); ?>">
|
|
|
|
|
<input type="submit" id="search-submit" class="button" value="<?php esc_attr_e('Search', 'wc-licensed-product'); ?>">
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="tablenav top">
|
|
|
|
|
<div class="alignleft actions">
|
|
|
|
|
<select name="status">
|
|
|
|
|
<option value="all"><?php esc_html_e('All Statuses', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="active" <?php selected($filters['status'] ?? '', 'active'); ?>><?php esc_html_e('Active', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="inactive" <?php selected($filters['status'] ?? '', 'inactive'); ?>><?php esc_html_e('Inactive', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="expired" <?php selected($filters['status'] ?? '', 'expired'); ?>><?php esc_html_e('Expired', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="revoked" <?php selected($filters['status'] ?? '', 'revoked'); ?>><?php esc_html_e('Revoked', 'wc-licensed-product'); ?></option>
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
<select name="product_id">
|
|
|
|
|
<option value=""><?php esc_html_e('All Products', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<?php foreach ($products as $id => $name): ?>
|
|
|
|
|
<option value="<?php echo esc_attr($id); ?>" <?php selected($filters['product_id'] ?? '', $id); ?>><?php echo esc_html($name); ?></option>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
<input type="submit" class="button" value="<?php esc_attr_e('Filter', 'wc-licensed-product'); ?>">
|
|
|
|
|
|
|
|
|
|
<?php if (!empty($filters)): ?>
|
|
|
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>" class="button"><?php esc_html_e('Clear', 'wc-licensed-product'); ?></a>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tablenav-pages">
|
|
|
|
|
<span class="displaying-num"><?php echo esc_html($totalLicenses); ?> <?php echo $totalLicenses === 1 ? esc_html__('item', 'wc-licensed-product') : esc_html__('items', 'wc-licensed-product'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
<p class="description">
|
2026-01-21 22:55:49 +01:00
|
|
|
<?php esc_html_e('Showing', 'wc-licensed-product'); ?> <?php echo esc_html($totalLicenses); ?> <?php echo $totalLicenses === 1 ? esc_html__('license', 'wc-licensed-product') : esc_html__('licenses', 'wc-licensed-product'); ?>
|
|
|
|
|
<?php if (!empty($filters)): ?>
|
|
|
|
|
(<?php esc_html_e('filtered', 'wc-licensed-product'); ?>)
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
| <a href="<?php echo esc_url(admin_url('admin.php?page=wc-reports&tab=licenses')); ?>"><?php esc_html_e('View Dashboard', 'wc-licensed-product'); ?></a>
|
2026-01-21 20:32:35 +01:00
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<form method="post" action="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>">
|
|
|
|
|
<?php wp_nonce_field('bulk_license_action'); ?>
|
|
|
|
|
|
|
|
|
|
<div class="tablenav top">
|
|
|
|
|
<div class="alignleft actions bulkactions">
|
|
|
|
|
<select name="bulk_action" id="bulk-action-selector">
|
|
|
|
|
<option value=""><?php esc_html_e('Bulk Actions', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="activate"><?php esc_html_e('Activate', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="deactivate"><?php esc_html_e('Deactivate', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="revoke"><?php esc_html_e('Revoke', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="extend_30"><?php esc_html_e('Extend 30 days', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="extend_90"><?php esc_html_e('Extend 90 days', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="extend_365"><?php esc_html_e('Extend 1 year', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="delete"><?php esc_html_e('Delete', 'wc-licensed-product'); ?></option>
|
|
|
|
|
</select>
|
|
|
|
|
<input type="submit" class="button action" value="<?php esc_attr_e('Apply', 'wc-licensed-product'); ?>">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<table class="wp-list-table widefat fixed striped licenses-table">
|
|
|
|
|
<thead>
|
2026-01-21 18:55:18 +01:00
|
|
|
<tr>
|
2026-01-21 20:32:35 +01:00
|
|
|
<td class="manage-column column-cb check-column">
|
|
|
|
|
<input type="checkbox" id="cb-select-all-1">
|
|
|
|
|
</td>
|
|
|
|
|
<th><?php esc_html_e('License Key', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Product', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Customer', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Domain', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Status', 'wc-licensed-product'); ?></th>
|
2026-01-21 23:50:57 +01:00
|
|
|
<th><?php esc_html_e('Created', 'wc-licensed-product'); ?></th>
|
2026-01-21 20:32:35 +01:00
|
|
|
<th><?php esc_html_e('Expires', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Actions', 'wc-licensed-product'); ?></th>
|
2026-01-21 18:55:18 +01:00
|
|
|
</tr>
|
2026-01-21 20:32:35 +01:00
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php if (empty($enrichedLicenses)): ?>
|
2026-01-21 18:55:18 +01:00
|
|
|
<tr>
|
2026-01-21 23:50:57 +01:00
|
|
|
<td colspan="9"><?php esc_html_e('No licenses found.', 'wc-licensed-product'); ?></td>
|
2026-01-21 18:55:18 +01:00
|
|
|
</tr>
|
2026-01-21 20:32:35 +01:00
|
|
|
<?php else: ?>
|
|
|
|
|
<?php foreach ($enrichedLicenses as $item): ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<th scope="row" class="check-column">
|
|
|
|
|
<input type="checkbox" name="license_ids[]" value="<?php echo esc_attr($item['license']->getId()); ?>">
|
|
|
|
|
</th>
|
2026-01-21 23:13:07 +01:00
|
|
|
<td>
|
|
|
|
|
<code class="wclp-license-key"><?php echo esc_html($item['license']->getLicenseKey()); ?></code>
|
|
|
|
|
<button type="button" class="wclp-copy-btn button-link" data-license-key="<?php echo esc_attr($item['license']->getLicenseKey()); ?>" title="<?php esc_attr_e('Copy to clipboard', 'wc-licensed-product'); ?>">
|
|
|
|
|
<span class="dashicons dashicons-clipboard"></span>
|
|
|
|
|
</button>
|
|
|
|
|
</td>
|
2026-01-21 20:32:35 +01:00
|
|
|
<td>
|
|
|
|
|
<?php if ($item['product_edit_url']): ?>
|
|
|
|
|
<a href="<?php echo esc_url($item['product_edit_url']); ?>">
|
|
|
|
|
<?php echo esc_html($item['product_name']); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<?php echo esc_html($item['product_name']); ?>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<?php echo esc_html($item['customer_name']); ?>
|
|
|
|
|
<?php if ($item['customer_email']): ?>
|
|
|
|
|
<br><small><?php echo esc_html($item['customer_email']); ?></small>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</td>
|
2026-01-21 23:13:07 +01:00
|
|
|
<td class="wclp-editable-cell" data-field="domain" data-license-id="<?php echo esc_attr($item['license']->getId()); ?>">
|
|
|
|
|
<span class="wclp-display-value"><?php echo esc_html($item['license']->getDomain()); ?></span>
|
|
|
|
|
<button type="button" class="wclp-edit-btn button-link" title="<?php esc_attr_e('Edit', 'wc-licensed-product'); ?>">
|
|
|
|
|
<span class="dashicons dashicons-edit"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<div class="wclp-edit-form" style="display:none;">
|
|
|
|
|
<input type="text" class="wclp-edit-input" value="<?php echo esc_attr($item['license']->getDomain()); ?>">
|
|
|
|
|
<button type="button" class="wclp-save-btn button button-small button-primary"><?php esc_html_e('Save', 'wc-licensed-product'); ?></button>
|
|
|
|
|
<button type="button" class="wclp-cancel-btn button button-small"><?php esc_html_e('Cancel', 'wc-licensed-product'); ?></button>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="wclp-editable-cell" data-field="status" data-license-id="<?php echo esc_attr($item['license']->getId()); ?>">
|
|
|
|
|
<span class="wclp-display-value">
|
|
|
|
|
<span class="license-status license-status-<?php echo esc_attr($item['license']->getStatus()); ?>">
|
|
|
|
|
<?php echo esc_html(ucfirst($item['license']->getStatus())); ?>
|
|
|
|
|
</span>
|
2026-01-21 20:32:35 +01:00
|
|
|
</span>
|
2026-01-21 23:13:07 +01:00
|
|
|
<button type="button" class="wclp-edit-btn button-link" title="<?php esc_attr_e('Edit', 'wc-licensed-product'); ?>">
|
|
|
|
|
<span class="dashicons dashicons-edit"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<div class="wclp-edit-form" style="display:none;">
|
|
|
|
|
<select class="wclp-edit-input">
|
|
|
|
|
<option value="active" <?php selected($item['license']->getStatus(), 'active'); ?>><?php esc_html_e('Active', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="inactive" <?php selected($item['license']->getStatus(), 'inactive'); ?>><?php esc_html_e('Inactive', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="expired" <?php selected($item['license']->getStatus(), 'expired'); ?>><?php esc_html_e('Expired', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="revoked" <?php selected($item['license']->getStatus(), 'revoked'); ?>><?php esc_html_e('Revoked', 'wc-licensed-product'); ?></option>
|
|
|
|
|
</select>
|
|
|
|
|
<button type="button" class="wclp-save-btn button button-small button-primary"><?php esc_html_e('Save', 'wc-licensed-product'); ?></button>
|
|
|
|
|
<button type="button" class="wclp-cancel-btn button button-small"><?php esc_html_e('Cancel', 'wc-licensed-product'); ?></button>
|
|
|
|
|
</div>
|
2026-01-21 20:32:35 +01:00
|
|
|
</td>
|
2026-01-21 23:50:57 +01:00
|
|
|
<td class="wclp-created-cell">
|
|
|
|
|
<?php echo esc_html($item['license']->getCreatedAt()->format(get_option('date_format'))); ?>
|
|
|
|
|
</td>
|
2026-01-21 23:13:07 +01:00
|
|
|
<td class="wclp-editable-cell" data-field="expiry" data-license-id="<?php echo esc_attr($item['license']->getId()); ?>">
|
|
|
|
|
<?php $expiresAt = $item['license']->getExpiresAt(); ?>
|
|
|
|
|
<span class="wclp-display-value">
|
|
|
|
|
<?php if ($expiresAt): ?>
|
|
|
|
|
<?php echo esc_html($expiresAt->format(get_option('date_format'))); ?>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<span class="license-lifetime"><?php esc_html_e('Lifetime', 'wc-licensed-product'); ?></span>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</span>
|
|
|
|
|
<button type="button" class="wclp-edit-btn button-link" title="<?php esc_attr_e('Edit', 'wc-licensed-product'); ?>">
|
|
|
|
|
<span class="dashicons dashicons-edit"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<div class="wclp-edit-form" style="display:none;">
|
|
|
|
|
<input type="date" class="wclp-edit-input" value="<?php echo $expiresAt ? esc_attr($expiresAt->format('Y-m-d')) : ''; ?>" placeholder="<?php esc_attr_e('Leave empty for lifetime', 'wc-licensed-product'); ?>">
|
|
|
|
|
<button type="button" class="wclp-save-btn button button-small button-primary"><?php esc_html_e('Save', 'wc-licensed-product'); ?></button>
|
|
|
|
|
<button type="button" class="wclp-cancel-btn button button-small"><?php esc_html_e('Cancel', 'wc-licensed-product'); ?></button>
|
|
|
|
|
<button type="button" class="wclp-lifetime-btn button button-small" title="<?php esc_attr_e('Set to lifetime', 'wc-licensed-product'); ?>">∞</button>
|
|
|
|
|
</div>
|
2026-01-21 20:32:35 +01:00
|
|
|
</td>
|
|
|
|
|
<td class="license-actions">
|
|
|
|
|
<div class="row-actions">
|
2026-01-23 11:41:05 +01:00
|
|
|
<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>
|
2026-01-21 20:32:35 +01:00
|
|
|
<?php if ($item['license']->getStatus() !== License::STATUS_REVOKED): ?>
|
2026-01-23 11:41:05 +01:00
|
|
|
<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>
|
2026-01-21 20:32:35 +01:00
|
|
|
<span class="extend">
|
|
|
|
|
<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'),
|
|
|
|
|
'extend_license'
|
|
|
|
|
)); ?>" title="<?php esc_attr_e('Extend by 30 days', 'wc-licensed-product'); ?>">+30d</a> |
|
|
|
|
|
</span>
|
|
|
|
|
<span class="lifetime">
|
|
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=lifetime&license_id=' . $item['license']->getId()),
|
|
|
|
|
'lifetime_license'
|
|
|
|
|
)); ?>" title="<?php esc_attr_e('Set to lifetime', 'wc-licensed-product'); ?>">∞</a> |
|
|
|
|
|
</span>
|
|
|
|
|
<span class="revoke">
|
|
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=revoke&license_id=' . $item['license']->getId()),
|
|
|
|
|
'revoke_license'
|
|
|
|
|
)); ?>" onclick="return confirm('<?php esc_attr_e('Are you sure?', 'wc-licensed-product'); ?>')">
|
|
|
|
|
<?php esc_html_e('Revoke', 'wc-licensed-product'); ?>
|
|
|
|
|
</a> |
|
|
|
|
|
</span>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<span class="delete">
|
|
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(
|
|
|
|
|
admin_url('admin.php?page=wc-licenses&action=delete&license_id=' . $item['license']->getId()),
|
|
|
|
|
'delete_license'
|
|
|
|
|
)); ?>" class="submitdelete" onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this license?', 'wc-licensed-product'); ?>')">
|
|
|
|
|
<?php esc_html_e('Delete', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
<tfoot>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="manage-column column-cb check-column">
|
|
|
|
|
<input type="checkbox" id="cb-select-all-2">
|
|
|
|
|
</td>
|
|
|
|
|
<th><?php esc_html_e('License Key', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Product', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Customer', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Domain', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Status', 'wc-licensed-product'); ?></th>
|
2026-01-21 23:50:57 +01:00
|
|
|
<th><?php esc_html_e('Created', 'wc-licensed-product'); ?></th>
|
2026-01-21 20:32:35 +01:00
|
|
|
<th><?php esc_html_e('Expires', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<th><?php esc_html_e('Actions', 'wc-licensed-product'); ?></th>
|
|
|
|
|
</tr>
|
|
|
|
|
</tfoot>
|
|
|
|
|
</table>
|
2026-01-21 18:55:18 +01:00
|
|
|
|
|
|
|
|
<div class="tablenav bottom">
|
2026-01-21 20:32:35 +01:00
|
|
|
<div class="alignleft actions bulkactions">
|
|
|
|
|
<select name="bulk_action_2" id="bulk-action-selector-bottom">
|
|
|
|
|
<option value=""><?php esc_html_e('Bulk Actions', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="activate"><?php esc_html_e('Activate', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="deactivate"><?php esc_html_e('Deactivate', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="revoke"><?php esc_html_e('Revoke', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="extend_30"><?php esc_html_e('Extend 30 days', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="extend_90"><?php esc_html_e('Extend 90 days', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="extend_365"><?php esc_html_e('Extend 1 year', 'wc-licensed-product'); ?></option>
|
|
|
|
|
<option value="delete"><?php esc_html_e('Delete', 'wc-licensed-product'); ?></option>
|
|
|
|
|
</select>
|
|
|
|
|
<input type="submit" class="button action" value="<?php esc_attr_e('Apply', 'wc-licensed-product'); ?>">
|
|
|
|
|
</div>
|
|
|
|
|
<?php if ($totalPages > 1): ?>
|
|
|
|
|
<div class="tablenav-pages">
|
|
|
|
|
<?php
|
|
|
|
|
echo paginate_links([
|
|
|
|
|
'base' => admin_url('admin.php?page=wc-licenses&paged=%#%'),
|
|
|
|
|
'format' => '',
|
|
|
|
|
'current' => $page,
|
|
|
|
|
'total' => $totalPages,
|
|
|
|
|
]);
|
|
|
|
|
?>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
|
2026-01-23 11:41:05 +01:00
|
|
|
<!-- 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>
|
|
|
|
|
|
2026-01-21 20:32:35 +01:00
|
|
|
<script>
|
|
|
|
|
(function($) {
|
2026-01-23 11:41:05 +01:00
|
|
|
// Checkbox select all
|
2026-01-21 20:32:35 +01:00
|
|
|
$('#cb-select-all-1, #cb-select-all-2').on('change', function() {
|
|
|
|
|
$('input[name="license_ids[]"]').prop('checked', this.checked);
|
|
|
|
|
$('#cb-select-all-1, #cb-select-all-2').prop('checked', this.checked);
|
|
|
|
|
});
|
|
|
|
|
$('#bulk-action-selector, #bulk-action-selector-bottom').on('change', function() {
|
|
|
|
|
$('#bulk-action-selector, #bulk-action-selector-bottom').val($(this).val());
|
|
|
|
|
});
|
|
|
|
|
$('form').on('submit', function() {
|
|
|
|
|
var topAction = $('#bulk-action-selector').val();
|
|
|
|
|
var bottomAction = $('#bulk-action-selector-bottom').val();
|
|
|
|
|
if (!topAction && bottomAction) {
|
|
|
|
|
$('#bulk-action-selector').val(bottomAction);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-01-23 11:41:05 +01:00
|
|
|
|
|
|
|
|
// 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>';
|
2026-01-27 21:22:45 +01:00
|
|
|
html += '<tr><th><?php echo esc_js(__('Product', 'wc-licensed-product')); ?></th><td><strong>' + escapeHtml(result.product_name || '-') + '</strong></td></tr>';
|
2026-01-23 11:41:05 +01:00
|
|
|
if (result.expires_at) {
|
|
|
|
|
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.expires_at) + '</td></tr>';
|
|
|
|
|
} else {
|
2026-01-27 21:22:45 +01:00
|
|
|
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td><span class="license-lifetime"><?php echo esc_js(__('Lifetime', 'wc-licensed-product')); ?></span></td></tr>';
|
2026-01-23 11:41:05 +01:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-01-21 20:32:35 +01:00
|
|
|
})(jQuery);
|
|
|
|
|
</script>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render the CSV import page
|
|
|
|
|
*/
|
|
|
|
|
private function renderImportPage(): void
|
|
|
|
|
{
|
|
|
|
|
$importError = $_GET['import_error'] ?? '';
|
|
|
|
|
?>
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
<h1>
|
|
|
|
|
<?php esc_html_e('Import Licenses', 'wc-licensed-product'); ?>
|
|
|
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>" class="page-title-action">
|
|
|
|
|
<?php esc_html_e('Back to Licenses', 'wc-licensed-product'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
</h1>
|
|
|
|
|
|
|
|
|
|
<?php if ($importError): ?>
|
|
|
|
|
<div class="notice notice-error">
|
|
|
|
|
<p>
|
2026-01-21 18:55:18 +01:00
|
|
|
<?php
|
2026-01-21 20:32:35 +01:00
|
|
|
switch ($importError) {
|
|
|
|
|
case 'upload':
|
|
|
|
|
esc_html_e('Error uploading file. Please try again.', 'wc-licensed-product');
|
|
|
|
|
break;
|
|
|
|
|
case 'filetype':
|
|
|
|
|
esc_html_e('Invalid file type. Please upload a CSV file.', 'wc-licensed-product');
|
|
|
|
|
break;
|
|
|
|
|
case 'read':
|
|
|
|
|
esc_html_e('Error reading file. Please check the file format.', 'wc-licensed-product');
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
esc_html_e('An error occurred during import.', 'wc-licensed-product');
|
|
|
|
|
}
|
2026-01-21 18:55:18 +01:00
|
|
|
?>
|
2026-01-21 20:32:35 +01:00
|
|
|
</p>
|
2026-01-21 18:55:18 +01:00
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
2026-01-21 20:32:35 +01:00
|
|
|
|
|
|
|
|
<div class="card" style="max-width: 800px; padding: 20px;">
|
|
|
|
|
<h2><?php esc_html_e('Import Licenses from CSV', 'wc-licensed-product'); ?></h2>
|
|
|
|
|
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e('Upload a CSV file to import licenses. You can use the exported CSV format or a simplified format.', 'wc-licensed-product'); ?>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<h3><?php esc_html_e('CSV Format', 'wc-licensed-product'); ?></h3>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e('The CSV file should contain the following columns:', 'wc-licensed-product'); ?>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div style="background: #f8f9fa; padding: 15px; border-radius: 4px; margin: 15px 0;">
|
|
|
|
|
<p><strong><?php esc_html_e('Full Format (from Export):', 'wc-licensed-product'); ?></strong></p>
|
|
|
|
|
<code style="display: block; margin-bottom: 10px; font-size: 12px;">ID, License Key, Product, Product ID, Order ID, Order Number, Customer, Customer Email, Customer ID, Domain, Status, Activations, Max Activations, Expires At, Created At, Updated At</code>
|
|
|
|
|
|
|
|
|
|
<p><strong><?php esc_html_e('Simplified Format:', 'wc-licensed-product'); ?></strong></p>
|
|
|
|
|
<code style="display: block; font-size: 12px;">License Key, Product ID, Customer ID, Domain, Status, Max Activations, Expires At</code>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p class="description">
|
|
|
|
|
<strong><?php esc_html_e('Notes:', 'wc-licensed-product'); ?></strong><br>
|
|
|
|
|
- <?php esc_html_e('Leave License Key empty to auto-generate.', 'wc-licensed-product'); ?><br>
|
|
|
|
|
- <?php esc_html_e('Status can be: active, inactive, expired, revoked (defaults to active).', 'wc-licensed-product'); ?><br>
|
|
|
|
|
- <?php esc_html_e('Expires At should be in YYYY-MM-DD format or "Lifetime".', 'wc-licensed-product'); ?>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<hr style="margin: 20px 0;">
|
|
|
|
|
|
|
|
|
|
<form method="post" action="<?php echo esc_url(admin_url('admin.php?page=wc-licenses')); ?>" enctype="multipart/form-data">
|
|
|
|
|
<?php wp_nonce_field('import_licenses_csv'); ?>
|
|
|
|
|
<input type="hidden" name="action" value="process_import_csv">
|
|
|
|
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
<tr>
|
|
|
|
|
<th scope="row">
|
|
|
|
|
<label for="import_file"><?php esc_html_e('CSV File', 'wc-licensed-product'); ?></label>
|
|
|
|
|
</th>
|
|
|
|
|
<td>
|
|
|
|
|
<input type="file" name="import_file" id="import_file" accept=".csv" required>
|
|
|
|
|
<p class="description"><?php esc_html_e('Select a CSV file to import.', 'wc-licensed-product'); ?></p>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<th scope="row"><?php esc_html_e('Options', 'wc-licensed-product'); ?></th>
|
|
|
|
|
<td>
|
|
|
|
|
<label>
|
|
|
|
|
<input type="checkbox" name="skip_first_row" value="1" checked>
|
|
|
|
|
<?php esc_html_e('Skip first row (header row)', 'wc-licensed-product'); ?>
|
|
|
|
|
</label>
|
|
|
|
|
<br><br>
|
|
|
|
|
<label>
|
|
|
|
|
<input type="checkbox" name="update_existing" value="1">
|
|
|
|
|
<?php esc_html_e('Update existing licenses (by license key)', 'wc-licensed-product'); ?>
|
|
|
|
|
</label>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e('If enabled, licenses with matching keys will be updated instead of skipped.', 'wc-licensed-product'); ?>
|
|
|
|
|
</p>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
<p class="submit">
|
|
|
|
|
<button type="submit" class="button button-primary">
|
|
|
|
|
<span class="dashicons dashicons-upload" style="vertical-align: middle;"></span>
|
|
|
|
|
<?php esc_html_e('Import Licenses', 'wc-licensed-product'); ?>
|
|
|
|
|
</button>
|
|
|
|
|
</p>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
2026-01-21 18:55:18 +01:00
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add license column to orders list
|
|
|
|
|
*/
|
|
|
|
|
public function addOrdersLicenseColumn(array $columns): array
|
|
|
|
|
{
|
|
|
|
|
$newColumns = [];
|
|
|
|
|
foreach ($columns as $key => $value) {
|
|
|
|
|
$newColumns[$key] = $value;
|
|
|
|
|
if ($key === 'order_status') {
|
|
|
|
|
$newColumns['license'] = __('License', 'wc-licensed-product');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $newColumns;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Display license column content
|
|
|
|
|
*/
|
|
|
|
|
public function displayOrdersLicenseColumn(string $column, int $postId): void
|
|
|
|
|
{
|
|
|
|
|
if ($column !== 'license') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$order = wc_get_order($postId);
|
|
|
|
|
$this->outputLicenseColumnContent($order);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Display license column content (HPOS)
|
|
|
|
|
*/
|
|
|
|
|
public function displayOrdersLicenseColumnHpos(string $column, \WC_Order $order): void
|
|
|
|
|
{
|
|
|
|
|
if ($column !== 'license') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->outputLicenseColumnContent($order);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output license column content
|
|
|
|
|
*/
|
|
|
|
|
private function outputLicenseColumnContent(?\WC_Order $order): void
|
|
|
|
|
{
|
|
|
|
|
if (!$order) {
|
|
|
|
|
echo '—';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$hasLicensedProduct = false;
|
|
|
|
|
foreach ($order->get_items() as $item) {
|
|
|
|
|
$product = $item->get_product();
|
|
|
|
|
if ($product && $product->is_type('licensed')) {
|
|
|
|
|
$hasLicensedProduct = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$hasLicensedProduct) {
|
|
|
|
|
echo '—';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$domain = $order->get_meta('_licensed_product_domain');
|
|
|
|
|
if ($domain) {
|
|
|
|
|
echo '<span class="dashicons dashicons-admin-network"></span> ' . esc_html($domain);
|
|
|
|
|
} else {
|
|
|
|
|
echo '<span class="dashicons dashicons-warning" title="' . esc_attr__('No domain specified', 'wc-licensed-product') . '"></span>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|