licenseManager = $licenseManager; $this->registerHooks(); } /** * Register WordPress hooks */ private function registerHooks(): void { // Add licenses meta box to order edit page add_action('add_meta_boxes', [$this, 'addLicensesMetaBox']); // Handle AJAX actions add_action('wp_ajax_wclp_update_order_domain', [$this, 'ajaxUpdateOrderDomain']); add_action('wp_ajax_wclp_update_license_domain', [$this, 'ajaxUpdateLicenseDomain']); add_action('wp_ajax_wclp_generate_order_licenses', [$this, 'ajaxGenerateOrderLicenses']); // Enqueue admin scripts add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']); } /** * Add licenses meta box to order edit page */ public function addLicensesMetaBox(): void { // Support both classic post type and HPOS $screen = wc_get_container()->get(\Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::class)->custom_orders_table_usage_is_enabled() ? wc_get_page_screen_id('shop-order') : 'shop_order'; add_meta_box( 'wclp_order_licenses', __('Product Licenses', 'wc-licensed-product'), [$this, 'renderLicensesMetaBox'], $screen, 'normal', 'default' ); } /** * Render licenses meta box */ public function renderLicensesMetaBox($post_or_order): void { // Get order object - support both classic and HPOS if ($post_or_order instanceof \WC_Order) { $order = $post_or_order; } else { $order = wc_get_order($post_or_order->ID); } if (!$order) { echo '

' . esc_html__('Order not found.', 'wc-licensed-product') . '

'; return; } // Check if order has licensed products $hasLicensedProduct = false; foreach ($order->get_items() as $item) { $product = $item->get_product(); if ($product && $product->is_type('licensed')) { $hasLicensedProduct = true; break; } } if (!$hasLicensedProduct) { echo '

' . esc_html__('This order does not contain licensed products.', 'wc-licensed-product') . '

'; return; } // Check for multi-domain format first, then fall back to legacy single domain $multiDomainData = $order->get_meta('_licensed_product_domains'); $legacyDomain = $order->get_meta('_licensed_product_domain'); $hasMultiDomain = !empty($multiDomainData) && is_array($multiDomainData); // Get licenses for this order $licenses = $this->licenseManager->getLicensesByOrder($order->get_id()); wp_nonce_field('wclp_order_license_actions', 'wclp_order_license_nonce'); ?>

get_name() : __('Unknown Product', 'wc-licensed-product'); ?>
:


get_items() as $item) { $product = $item->get_product(); if ($product && $product->is_type('licensed')) { $expectedLicenses++; } } } $missingLicenses = $expectedLicenses - count($licenses); $hasDomainData = $hasMultiDomain || !empty($legacyDomain); ?>

is_paid()): ?>

is_paid()): ?>

getProductId()); $statusClass = 'wclp-status-' . $license->getStatus(); ?>
getLicenseKey()); ?> get_name()); ?>
getDomain()); ?>
getStatus())); ?> getExpiresAt(); if ($expiresAt) { echo esc_html($expiresAt->format(get_option('date_format'))); } else { echo '' . esc_html__('Lifetime', 'wc-licensed-product') . ''; } ?>

' . esc_html__('Licenses', 'wc-licensed-product') . '' ); ?>

0 && $hasDomainData && $order->is_paid()): ?>

id, ['shop_order', 'woocommerce_page_wc-orders'], true) || (isset($_GET['page']) && $_GET['page'] === 'wc-orders' && isset($_GET['action']) && $_GET['action'] === 'edit'); if (!$isOrderEdit) { return; } wp_enqueue_script( 'wclp-order-licenses', WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/js/order-licenses.js', ['jquery'], WC_LICENSED_PRODUCT_VERSION, true ); wp_localize_script('wclp-order-licenses', 'wclpOrderLicenses', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('wclp_order_license_actions'), 'strings' => [ 'saving' => __('Saving...', 'wc-licensed-product'), 'saved' => __('Saved!', 'wc-licensed-product'), 'error' => __('Error. Please try again.', 'wc-licensed-product'), 'invalidDomain' => __('Please enter a valid domain.', 'wc-licensed-product'), 'generating' => __('Generating...', 'wc-licensed-product'), ], ]); } /** * AJAX handler for updating order domain */ public function ajaxUpdateOrderDomain(): 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); $domain = sanitize_text_field($_POST['domain'] ?? ''); 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')]); } // Normalize and validate domain $normalizedDomain = $this->licenseManager->normalizeDomain($domain); if (!empty($domain) && !$this->isValidDomain($normalizedDomain)) { wp_send_json_error(['message' => __('Invalid domain format.', 'wc-licensed-product')]); } // Update order meta $order->update_meta_data('_licensed_product_domain', $normalizedDomain); $order->save(); wp_send_json_success([ 'message' => __('Order domain updated.', 'wc-licensed-product'), 'domain' => $normalizedDomain, ]); } /** * AJAX handler for updating license domain */ public function ajaxUpdateLicenseDomain(): 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')]); } $licenseId = absint($_POST['license_id'] ?? 0); $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')]); } // Normalize and validate domain $normalizedDomain = $this->licenseManager->normalizeDomain($domain); if (!$this->isValidDomain($normalizedDomain)) { wp_send_json_error(['message' => __('Invalid domain format.', 'wc-licensed-product')]); } // Get license to verify it exists $license = $this->licenseManager->getLicenseById($licenseId); if (!$license) { wp_send_json_error(['message' => __('License not found.', 'wc-licensed-product')]); } // Update license domain $success = $this->licenseManager->updateLicenseDomain($licenseId, $normalizedDomain); if ($success) { wp_send_json_success([ 'message' => __('License domain updated.', 'wc-licensed-product'), 'domain' => $normalizedDomain, ]); } else { wp_send_json_error(['message' => __('Failed to update license domain.', 'wc-licensed-product')]); } } /** * Validate domain format */ private function isValidDomain(string $domain): bool { if (empty($domain)) { return true; // Empty is allowed for order domain } if (strlen($domain) > 255) { return false; } $pattern = '/^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/'; return (bool) preg_match($pattern, $domain); } /** * AJAX handler for generating order licenses */ public function ajaxGenerateOrderLicenses(): void { check_ajax_referer('wclp_order_license_actions', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')]); } $orderId = absint($_POST['order_id'] ?? 0); if (!$orderId) { wp_send_json_error(['message' => __('Invalid order ID.', 'wc-licensed-product')]); } $order = wc_get_order($orderId); if (!$order) { wp_send_json_error(['message' => __('Order not found.', 'wc-licensed-product')]); } // Check if order is paid if (!$order->is_paid()) { wp_send_json_error(['message' => __('Order must be paid before licenses can be generated.', 'wc-licensed-product')]); } // Check for multi-domain format first $multiDomainData = $order->get_meta('_licensed_product_domains'); $legacyDomain = $order->get_meta('_licensed_product_domain'); if (!empty($multiDomainData) && is_array($multiDomainData)) { // Multi-domain format $result = $this->generateMultiDomainLicenses($order, $multiDomainData); } elseif (!empty($legacyDomain)) { // Legacy single domain format $result = $this->generateLegacyLicenses($order, $legacyDomain); } else { wp_send_json_error(['message' => __('Please set the order domain before generating licenses.', 'wc-licensed-product')]); return; } if ($result['generated'] > 0) { wp_send_json_success([ 'message' => sprintf( /* translators: %d: Number of licenses generated */ _n( '%d license generated successfully.', '%d licenses generated successfully.', $result['generated'], 'wc-licensed-product' ), $result['generated'] ), 'generated' => $result['generated'], 'skipped' => $result['skipped'], 'reload' => true, ]); } else { wp_send_json_success([ 'message' => __('All licenses already exist for this order.', 'wc-licensed-product'), 'generated' => 0, 'skipped' => $result['skipped'], 'reload' => false, ]); } } /** * Generate licenses for multi-domain format */ private function generateMultiDomainLicenses(\WC_Order $order, array $domainData): array { $generated = 0; $skipped = 0; $orderId = $order->get_id(); $customerId = $order->get_customer_id(); // Index domains by product ID $domainsByProduct = []; foreach ($domainData as $item) { if (isset($item['product_id']) && isset($item['domains']) && is_array($item['domains'])) { $domainsByProduct[(int) $item['product_id']] = $item['domains']; } } foreach ($order->get_items() as $item) { $product = $item->get_product(); if (!$product || !$product->is_type('licensed')) { continue; } $productId = $product->get_id(); $domains = $domainsByProduct[$productId] ?? []; // Get existing licenses for this product $existingLicenses = $this->licenseManager->getLicensesByOrderAndProduct($orderId, $productId); $existingDomains = array_map(fn($l) => $l->getDomain(), $existingLicenses); foreach ($domains as $domain) { $normalizedDomain = $this->licenseManager->normalizeDomain($domain); // Skip if license already exists for this domain if (in_array($normalizedDomain, $existingDomains, true)) { $skipped++; continue; } $license = $this->licenseManager->generateLicense( $orderId, $productId, $customerId, $normalizedDomain ); if ($license) { $generated++; } } } return ['generated' => $generated, 'skipped' => $skipped]; } /** * Generate licenses for legacy single domain format */ private function generateLegacyLicenses(\WC_Order $order, string $domain): array { $generated = 0; $skipped = 0; $orderId = $order->get_id(); $customerId = $order->get_customer_id(); foreach ($order->get_items() as $item) { $product = $item->get_product(); if (!$product || !$product->is_type('licensed')) { continue; } // Check if license already exists $existing = $this->licenseManager->getLicenseByOrderAndProduct($orderId, $product->get_id()); if ($existing) { $skipped++; continue; } $license = $this->licenseManager->generateLicense( $orderId, $product->get_id(), $customerId, $domain ); if ($license) { $generated++; } } return ['generated' => $generated, 'skipped' => $skipped]; } }