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); // Add to WooCommerce Reports add_filter('woocommerce_admin_reports', [$this, 'addLicenseReports']); // AJAX handler for live search add_action('wp_ajax_wclp_live_search', [$this, 'handleLiveSearch']); } /** * 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'] ); } /** * 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; } /** * Enqueue admin styles and scripts */ public function enqueueStyles(string $hook): void { // 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); $isReportsPage = $hook === 'woocommerce_page_wc-reports' && isset($_GET['tab']) && $_GET['tab'] === 'licenses'; if (!$isLicensePage && !$isReportsPage) { return; } wp_enqueue_style( 'wc-licensed-product-admin', WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/css/admin.css', [], WC_LICENSED_PRODUCT_VERSION ); // 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'), 'strings' => [ 'noResults' => __('No licenses found', 'wc-licensed-product'), 'searching' => __('Searching...', 'wc-licensed-product'), 'error' => __('Search failed', 'wc-licensed-product'), ], ]); } } /** * 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]); } /** * 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(); } // 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(); } } /** * 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; } } /** * 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 { 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 { ?>
renderImportPage(); return; } $page = isset($_GET['paged']) ? absint($_GET['paged']) : 1; $perPage = 20; // 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(); // 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 : '', ]; } // 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'); })); 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(), 'filters' => $filters, 'products' => $licensedProducts, ]); } catch (\Exception $e) { // Fallback to PHP template $this->renderLicensesPageFallback($enrichedLicenses, $page, $totalPages, $totalLicenses, $filters, $licensedProducts); } } /** * 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')]; } 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]; } return $notices; } /** * Fallback render for licenses page */ private function renderLicensesPageFallback(array $enrichedLicenses, int $page, int $totalPages, int $totalLicenses, array $filters = [], array $products = []): void { ?> $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 ' ' . esc_html($domain); } else { echo ''; } } }