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']); // 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; } // Get order domain $orderDomain = $order->get_meta('_licensed_product_domain'); // Get licenses for this order $licenses = $this->licenseManager->getLicensesByOrder($order->get_id()); wp_nonce_field('wclp_order_license_actions', 'wclp_order_license_nonce'); ?>


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') . '' ); ?>

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 saving. Please try again.', 'wc-licensed-product'), 'invalidDomain' => __('Please enter a valid domain.', '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); } }