is_type('licensed')) { return true; } // Variable licensed product if ($product->is_type('licensed-variable')) { return true; } // Variation of a licensed-variable product if ($product->is_type('variation') && $product->get_parent_id()) { $parent = wc_get_product($product->get_parent_id()); if ($parent && $parent->is_type('licensed-variable')) { return true; } } return false; } /** * Generate a unique license key */ public function generateLicenseKey(): string { // Format: XXXX-XXXX-XXXX-XXXX (32 chars hex, 4 groups) $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $key = ''; for ($i = 0; $i < 4; $i++) { if ($i > 0) { $key .= '-'; } for ($j = 0; $j < 4; $j++) { $key .= $chars[random_int(0, strlen($chars) - 1)]; } } return $key; } /** * Generate a license for a completed order * * @param int $orderId Order ID * @param int $productId Product ID (parent product for variations) * @param int $customerId Customer ID * @param string $domain Domain to bind the license to * @param int|null $variationId Optional variation ID for variable licensed products * @return License|null Generated license or null on failure */ public function generateLicense( int $orderId, int $productId, int $customerId, string $domain, ?int $variationId = null ): ?License { global $wpdb; // Normalize domain first for duplicate detection $normalizedDomain = $this->normalizeDomain($domain); // Check if license already exists for this order, product, domain, and variation $existing = $this->getLicenseByOrderProductDomainAndVariation($orderId, $productId, $normalizedDomain, $variationId); if ($existing) { return $existing; } // Load the product that has the license settings // For variations, load the variation; otherwise load the parent product if ($variationId) { $settingsProduct = wc_get_product($variationId); $parentProduct = wc_get_product($productId); // Verify parent is licensed-variable if (!$parentProduct || !$parentProduct->is_type('licensed-variable')) { return null; } // Ensure we have the proper variation class if ($settingsProduct && !$settingsProduct instanceof LicensedProductVariation) { $settingsProduct = new LicensedProductVariation($variationId); } } else { $settingsProduct = wc_get_product($productId); // Check if this is a licensed product (simple) if (!$settingsProduct || !$settingsProduct->is_type('licensed')) { return null; } // Ensure we have the LicensedProduct instance for type hints if (!$settingsProduct instanceof LicensedProduct) { $settingsProduct = new LicensedProduct($productId); } } if (!$settingsProduct) { return null; } // Generate unique license key $licenseKey = $this->generateLicenseKey(); while ($this->getLicenseByKey($licenseKey)) { $licenseKey = $this->generateLicenseKey(); } // Calculate expiration date from the settings product (variation or parent) $expiresAt = null; $validityDays = $settingsProduct->get_validity_days(); if ($validityDays !== null && $validityDays > 0) { $expiresAt = (new \DateTimeImmutable())->modify("+{$validityDays} days")->format('Y-m-d H:i:s'); } // Determine version ID if bound to version (always use parent product ID for versions) $versionId = null; if ($settingsProduct->is_bound_to_version()) { $versionId = $this->getCurrentVersionId($productId); } $tableName = Installer::getLicensesTable(); $result = $wpdb->insert( $tableName, [ 'license_key' => $licenseKey, 'order_id' => $orderId, 'product_id' => $productId, 'customer_id' => $customerId, 'domain' => $this->normalizeDomain($domain), 'version_id' => $versionId, 'status' => License::STATUS_ACTIVE, 'activations_count' => 1, 'max_activations' => $settingsProduct->get_max_activations(), 'expires_at' => $expiresAt, ], ['%s', '%d', '%d', '%d', '%s', '%d', '%s', '%d', '%d', '%s'] ); if ($result === false) { return null; } return $this->getLicenseById((int) $wpdb->insert_id); } /** * Get license by order, product, domain, and optional variation */ public function getLicenseByOrderProductDomainAndVariation(int $orderId, int $productId, string $domain, ?int $variationId = null): ?License { // For now, just use the existing method since we don't store variation_id in licenses table yet // In the future, we could add a variation_id column to the licenses table return $this->getLicenseByOrderProductAndDomain($orderId, $productId, $domain); } /** * Get license by ID */ public function getLicenseById(int $id): ?License { global $wpdb; $tableName = Installer::getLicensesTable(); $row = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$tableName} WHERE id = %d", $id), ARRAY_A ); return $row ? License::fromArray($row) : null; } /** * Get license by license key */ public function getLicenseByKey(string $licenseKey): ?License { global $wpdb; $tableName = Installer::getLicensesTable(); $row = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$tableName} WHERE license_key = %s", $licenseKey), ARRAY_A ); return $row ? License::fromArray($row) : null; } /** * Get license by order and product */ public function getLicenseByOrderAndProduct(int $orderId, int $productId): ?License { global $wpdb; $tableName = Installer::getLicensesTable(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$tableName} WHERE order_id = %d AND product_id = %d", $orderId, $productId ), ARRAY_A ); return $row ? License::fromArray($row) : null; } /** * Get all licenses for an order and product * * @return License[] */ public function getLicensesByOrderAndProduct(int $orderId, int $productId): array { global $wpdb; $tableName = Installer::getLicensesTable(); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tableName} WHERE order_id = %d AND product_id = %d ORDER BY created_at ASC", $orderId, $productId ), ARRAY_A ); return array_map(fn(array $row) => License::fromArray($row), $rows ?: []); } /** * Get license by order, product, and domain */ public function getLicenseByOrderProductAndDomain(int $orderId, int $productId, string $domain): ?License { global $wpdb; $tableName = Installer::getLicensesTable(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$tableName} WHERE order_id = %d AND product_id = %d AND domain = %s", $orderId, $productId, $domain ), ARRAY_A ); return $row ? License::fromArray($row) : null; } /** * Get all licenses for an order */ public function getLicensesByOrder(int $orderId): array { global $wpdb; $tableName = Installer::getLicensesTable(); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tableName} WHERE order_id = %d ORDER BY created_at DESC", $orderId ), ARRAY_A ); return array_map(fn(array $row) => License::fromArray($row), $rows ?: []); } /** * Get all licenses for a customer */ public function getLicensesByCustomer(int $customerId): array { global $wpdb; $tableName = Installer::getLicensesTable(); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tableName} WHERE customer_id = %d ORDER BY created_at DESC", $customerId ), ARRAY_A ); return array_map(fn(array $row) => License::fromArray($row), $rows ?: []); } /** * Get all licenses (for admin) with optional filtering * * @param int $page Page number * @param int $perPage Items per page * @param array $filters Optional filters: search, status, product_id, customer_id * @return array Array of License objects */ public function getAllLicenses(int $page = 1, int $perPage = 20, array $filters = []): array { global $wpdb; $tableName = Installer::getLicensesTable(); $offset = ($page - 1) * $perPage; $where = []; $params = []; // Search filter (searches license key, domain, customer email) if (!empty($filters['search'])) { $search = '%' . $wpdb->esc_like($filters['search']) . '%'; $where[] = "(license_key LIKE %s OR domain LIKE %s)"; $params[] = $search; $params[] = $search; } // Status filter if (!empty($filters['status']) && in_array($filters['status'], [ License::STATUS_ACTIVE, License::STATUS_INACTIVE, License::STATUS_EXPIRED, License::STATUS_REVOKED, ], true)) { $where[] = "status = %s"; $params[] = $filters['status']; } // Product filter if (!empty($filters['product_id'])) { $where[] = "product_id = %d"; $params[] = absint($filters['product_id']); } // Customer filter if (!empty($filters['customer_id'])) { $where[] = "customer_id = %d"; $params[] = absint($filters['customer_id']); } $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; $params[] = $perPage; $params[] = $offset; $sql = "SELECT * FROM {$tableName} {$whereClause} ORDER BY created_at DESC LIMIT %d OFFSET %d"; $rows = $wpdb->get_results( $wpdb->prepare($sql, $params), ARRAY_A ); return array_map(fn(array $row) => License::fromArray($row), $rows ?: []); } /** * Get total license count with optional filtering * * @param array $filters Optional filters: search, status, product_id, customer_id * @return int Total count */ public function getLicenseCount(array $filters = []): int { global $wpdb; $tableName = Installer::getLicensesTable(); $where = []; $params = []; // Search filter if (!empty($filters['search'])) { $search = '%' . $wpdb->esc_like($filters['search']) . '%'; $where[] = "(license_key LIKE %s OR domain LIKE %s)"; $params[] = $search; $params[] = $search; } // Status filter if (!empty($filters['status']) && in_array($filters['status'], [ License::STATUS_ACTIVE, License::STATUS_INACTIVE, License::STATUS_EXPIRED, License::STATUS_REVOKED, ], true)) { $where[] = "status = %s"; $params[] = $filters['status']; } // Product filter if (!empty($filters['product_id'])) { $where[] = "product_id = %d"; $params[] = absint($filters['product_id']); } // Customer filter if (!empty($filters['customer_id'])) { $where[] = "customer_id = %d"; $params[] = absint($filters['customer_id']); } $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; if (empty($params)) { return (int) $wpdb->get_var("SELECT COUNT(*) FROM {$tableName}"); } return (int) $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM {$tableName} {$whereClause}", $params) ); } /** * Get all licensed products for filter dropdown * * @return array Array of [id => name] pairs */ public function getLicensedProducts(): array { global $wpdb; $tableName = Installer::getLicensesTable(); $productIds = $wpdb->get_col("SELECT DISTINCT product_id FROM {$tableName}"); $products = []; foreach ($productIds as $productId) { $product = wc_get_product((int) $productId); if ($product) { $products[(int) $productId] = $product->get_name(); } } return $products; } /** * Validate a license key for a domain */ public function validateLicense(string $licenseKey, string $domain): array { $license = $this->getLicenseByKey($licenseKey); if (!$license) { return [ 'valid' => false, 'error' => 'license_not_found', 'message' => __('License key not found.', 'wc-licensed-product'), ]; } // Check license status if ($license->getStatus() === License::STATUS_REVOKED) { return [ 'valid' => false, 'error' => 'license_revoked', 'message' => __('This license has been revoked.', 'wc-licensed-product'), ]; } // Check expiration if ($license->isExpired()) { $this->updateLicenseStatus($license->getId(), License::STATUS_EXPIRED); return [ 'valid' => false, 'error' => 'license_expired', 'message' => __('This license has expired.', 'wc-licensed-product'), ]; } if ($license->getStatus() === License::STATUS_INACTIVE) { return [ 'valid' => false, 'error' => 'license_inactive', 'message' => __('This license is inactive.', 'wc-licensed-product'), ]; } // Check domain $normalizedDomain = $this->normalizeDomain($domain); if ($license->getDomain() !== $normalizedDomain) { return [ 'valid' => false, 'error' => 'domain_mismatch', 'message' => __('This license is not valid for this domain.', 'wc-licensed-product'), ]; } return [ 'valid' => true, 'license' => [ 'product_id' => $license->getProductId(), 'expires_at' => $license->getExpiresAt()?->format('Y-m-d'), 'version_id' => $license->getVersionId(), ], ]; } /** * Update license status */ public function updateLicenseStatus(int $licenseId, string $status): bool { global $wpdb; $tableName = Installer::getLicensesTable(); $result = $wpdb->update( $tableName, ['status' => $status], ['id' => $licenseId], ['%s'], ['%d'] ); return $result !== false; } /** * Update license expiry date * * @param int $licenseId License ID * @param \DateTimeImmutable $expiresAt New expiry date * @return bool Success */ public function updateLicenseExpiry(int $licenseId, \DateTimeImmutable $expiresAt): bool { global $wpdb; $license = $this->getLicenseById($licenseId); if (!$license) { return false; } $tableName = Installer::getLicensesTable(); $result = $wpdb->update( $tableName, ['expires_at' => $expiresAt->format('Y-m-d H:i:s')], ['id' => $licenseId], ['%s'], ['%d'] ); // If license was expired and new date is in the future, reactivate it if ($result !== false && $license->getStatus() === License::STATUS_EXPIRED && $expiresAt > new \DateTimeImmutable()) { $this->updateLicenseStatus($licenseId, License::STATUS_ACTIVE); } return $result !== false; } /** * Update license domain */ public function updateLicenseDomain(int $licenseId, string $domain): bool { global $wpdb; $tableName = Installer::getLicensesTable(); $result = $wpdb->update( $tableName, ['domain' => $this->normalizeDomain($domain)], ['id' => $licenseId], ['%s'], ['%d'] ); return $result !== false; } /** * Delete a license */ public function deleteLicense(int $licenseId): bool { global $wpdb; $tableName = Installer::getLicensesTable(); $result = $wpdb->delete( $tableName, ['id' => $licenseId], ['%d'] ); return $result !== false; } /** * Normalize domain name */ public function normalizeDomain(string $domain): string { // Remove protocol $domain = preg_replace('#^https?://#', '', $domain); // Remove trailing slash and path $domain = preg_replace('#/.*$#', '', $domain); // Remove www prefix $domain = preg_replace('#^www\.#', '', $domain); // Lowercase return strtolower(trim($domain)); } /** * Get current version ID for a product */ private function getCurrentVersionId(int $productId): ?int { global $wpdb; $tableName = Installer::getVersionsTable(); $versionId = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$tableName} WHERE product_id = %d AND is_active = 1 ORDER BY released_at DESC LIMIT 1", $productId ) ); return $versionId ? (int) $versionId : null; } /** * Extend license expiration * * @param int $licenseId License ID * @param int $days Number of days to extend * @return bool Success */ public function extendLicense(int $licenseId, int $days): bool { global $wpdb; $license = $this->getLicenseById($licenseId); if (!$license) { return false; } // Calculate new expiration date $currentExpiry = $license->getExpiresAt(); if ($currentExpiry === null) { // License is lifetime, set expiration from now $newExpiry = (new \DateTimeImmutable())->modify("+{$days} days"); } elseif ($currentExpiry < new \DateTimeImmutable()) { // License is expired, extend from now $newExpiry = (new \DateTimeImmutable())->modify("+{$days} days"); } else { // License still valid, extend from current expiry $newExpiry = \DateTimeImmutable::createFromInterface($currentExpiry)->modify("+{$days} days"); } $tableName = Installer::getLicensesTable(); $result = $wpdb->update( $tableName, ['expires_at' => $newExpiry->format('Y-m-d H:i:s')], ['id' => $licenseId], ['%s'], ['%d'] ); // If license was expired, reactivate it if ($result !== false && $license->getStatus() === License::STATUS_EXPIRED) { $this->updateLicenseStatus($licenseId, License::STATUS_ACTIVE); } return $result !== false; } /** * Set license to lifetime (no expiration) * * @param int $licenseId License ID * @return bool Success */ public function setLicenseLifetime(int $licenseId): bool { global $wpdb; $license = $this->getLicenseById($licenseId); $tableName = Installer::getLicensesTable(); // Use raw query to set NULL $result = $wpdb->query( $wpdb->prepare( "UPDATE {$tableName} SET expires_at = NULL WHERE id = %d", $licenseId ) ); // If license was expired, reactivate it if ($result !== false && $license && $license->getStatus() === License::STATUS_EXPIRED) { $this->updateLicenseStatus($licenseId, License::STATUS_ACTIVE); } return $result !== false; } /** * Bulk update license status * * @param array $licenseIds Array of license IDs * @param string $status New status * @return int Number of licenses updated */ public function bulkUpdateStatus(array $licenseIds, string $status): int { global $wpdb; if (empty($licenseIds)) { return 0; } $tableName = Installer::getLicensesTable(); $ids = array_map('absint', $licenseIds); $placeholders = implode(',', array_fill(0, count($ids), '%d')); $result = $wpdb->query( $wpdb->prepare( "UPDATE {$tableName} SET status = %s WHERE id IN ({$placeholders})", array_merge([$status], $ids) ) ); return $result !== false ? (int) $result : 0; } /** * Bulk delete licenses * * @param array $licenseIds Array of license IDs * @return int Number of licenses deleted */ public function bulkDelete(array $licenseIds): int { global $wpdb; if (empty($licenseIds)) { return 0; } $tableName = Installer::getLicensesTable(); $ids = array_map('absint', $licenseIds); $placeholders = implode(',', array_fill(0, count($ids), '%d')); $result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$tableName} WHERE id IN ({$placeholders})", $ids ) ); return $result !== false ? (int) $result : 0; } /** * Bulk extend licenses * * @param array $licenseIds Array of license IDs * @param int $days Number of days to extend * @return int Number of licenses extended */ public function bulkExtend(array $licenseIds, int $days): int { $count = 0; foreach ($licenseIds as $licenseId) { if ($this->extendLicense((int) $licenseId, $days)) { $count++; } } return $count; } /** * Transfer license to a new domain * * @param int $licenseId License ID * @param string $newDomain New domain to transfer to * @return bool Success */ public function transferLicense(int $licenseId, string $newDomain): bool { $license = $this->getLicenseById($licenseId); if (!$license) { return false; } // Cannot transfer revoked licenses if ($license->getStatus() === License::STATUS_REVOKED) { return false; } return $this->updateLicenseDomain($licenseId, $newDomain); } /** * Get license statistics * * @return array Statistics data */ public function getStatistics(): array { global $wpdb; $tableName = Installer::getLicensesTable(); // Get counts by status $statusCounts = $wpdb->get_results( "SELECT status, COUNT(*) as count FROM {$tableName} GROUP BY status", ARRAY_A ); $byStatus = [ License::STATUS_ACTIVE => 0, License::STATUS_INACTIVE => 0, License::STATUS_EXPIRED => 0, License::STATUS_REVOKED => 0, ]; foreach ($statusCounts ?: [] as $row) { $byStatus[$row['status']] = (int) $row['count']; } // Get total count $total = array_sum($byStatus); // Get lifetime vs expiring licenses $lifetimeCount = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$tableName} WHERE expires_at IS NULL" ); $expiringCount = $total - $lifetimeCount; // Get licenses expiring soon (next 30 days) $expiringSoon = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$tableName} WHERE expires_at IS NOT NULL AND expires_at <= %s AND expires_at > NOW() AND status = %s", (new \DateTimeImmutable())->modify('+30 days')->format('Y-m-d H:i:s'), License::STATUS_ACTIVE ) ); // Get licenses by product $byProduct = $wpdb->get_results( "SELECT product_id, COUNT(*) as count FROM {$tableName} GROUP BY product_id ORDER BY count DESC LIMIT 10", ARRAY_A ); $productStats = []; foreach ($byProduct ?: [] as $row) { $product = wc_get_product((int) $row['product_id']); $productStats[] = [ 'product_id' => (int) $row['product_id'], 'product_name' => $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product'), 'count' => (int) $row['count'], ]; } // Get licenses created per month (last 12 months) $monthlyData = $wpdb->get_results( "SELECT DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count FROM {$tableName} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH) GROUP BY DATE_FORMAT(created_at, '%Y-%m') ORDER BY month ASC", ARRAY_A ); $monthlyStats = []; foreach ($monthlyData ?: [] as $row) { $monthlyStats[$row['month']] = (int) $row['count']; } // Get top domains $topDomains = $wpdb->get_results( "SELECT domain, COUNT(*) as count FROM {$tableName} GROUP BY domain ORDER BY count DESC LIMIT 10", ARRAY_A ); return [ 'total' => $total, 'by_status' => $byStatus, 'lifetime' => $lifetimeCount, 'expiring' => $expiringCount, 'expiring_soon' => $expiringSoon, 'by_product' => $productStats, 'monthly' => $monthlyStats, 'top_domains' => $topDomains ?: [], ]; } /** * Get licenses expiring within specified days * * @param int $days Number of days to look ahead * @param bool $excludeNotified Whether to exclude already notified licenses * @return array Array of License objects with customer data */ public function getLicensesExpiringSoon(int $days = 7, bool $excludeNotified = true): array { global $wpdb; $tableName = Installer::getLicensesTable(); $now = new \DateTimeImmutable(); $future = $now->modify("+{$days} days"); $sql = "SELECT * FROM {$tableName} WHERE expires_at IS NOT NULL AND expires_at > %s AND expires_at <= %s AND status = %s"; $params = [ $now->format('Y-m-d H:i:s'), $future->format('Y-m-d H:i:s'), License::STATUS_ACTIVE, ]; $rows = $wpdb->get_results( $wpdb->prepare($sql, $params), ARRAY_A ); return array_map(fn(array $row) => License::fromArray($row), $rows ?: []); } /** * Mark license as notified for expiration warning * * @param int $licenseId License ID * @param string $notificationType Type of notification (e.g., 'expiring_7_days', 'expiring_1_day') * @return bool Success */ public function markExpirationNotified(int $licenseId, string $notificationType): bool { $metaKey = '_wclp_expiration_notified_' . sanitize_key($notificationType); update_user_meta($this->getLicenseById($licenseId)?->getCustomerId() ?? 0, $metaKey . '_' . $licenseId, current_time('mysql')); return true; } /** * Check if license was already notified for expiration * * @param int $licenseId License ID * @param string $notificationType Type of notification * @return bool Whether already notified */ public function wasExpirationNotified(int $licenseId, string $notificationType): bool { $license = $this->getLicenseById($licenseId); if (!$license) { return true; // Consider notified if license doesn't exist } $metaKey = '_wclp_expiration_notified_' . sanitize_key($notificationType) . '_' . $licenseId; return (bool) get_user_meta($license->getCustomerId(), $metaKey, true); } /** * Get licenses that have passed their expiration date but are still marked as active * * @return array Array of License objects that need to be auto-expired */ public function getExpiredActiveLicenses(): array { global $wpdb; $tableName = Installer::getLicensesTable(); $now = new \DateTimeImmutable(); $sql = "SELECT * FROM {$tableName} WHERE expires_at IS NOT NULL AND expires_at < %s AND status = %s"; $rows = $wpdb->get_results( $wpdb->prepare($sql, $now->format('Y-m-d H:i:s'), License::STATUS_ACTIVE), ARRAY_A ); return array_map(fn(array $row) => License::fromArray($row), $rows ?: []); } /** * Auto-expire a license and return true if status was changed * * @param int $licenseId License ID * @return bool True if license was expired, false if already expired or error */ public function autoExpireLicense(int $licenseId): bool { $license = $this->getLicenseById($licenseId); if (!$license) { return false; } // Only expire if currently active and past expiration date if ($license->getStatus() !== License::STATUS_ACTIVE) { return false; } if (!$license->isExpired()) { return false; } return $this->updateLicenseStatus($licenseId, License::STATUS_EXPIRED); } /** * Import a license from CSV data * * @param string $licenseKey License key * @param int $productId Product ID * @param int $customerId Customer ID * @param string $domain Domain name * @param int $orderId Order ID (optional) * @param string $status License status * @param int $maxActivations Maximum activations * @param int $activationsCount Current activation count * @param \DateTimeImmutable|null $expiresAt Expiration date or null for lifetime * @return bool Success */ public function importLicense( string $licenseKey, int $productId, int $customerId, string $domain, int $orderId = 0, string $status = License::STATUS_ACTIVE, int $maxActivations = 1, int $activationsCount = 1, ?\DateTimeImmutable $expiresAt = null ): bool { global $wpdb; $tableName = Installer::getLicensesTable(); $result = $wpdb->insert( $tableName, [ 'license_key' => $licenseKey, 'order_id' => $orderId, 'product_id' => $productId, 'customer_id' => $customerId, 'domain' => $this->normalizeDomain($domain), 'version_id' => null, 'status' => $status, 'activations_count' => $activationsCount, 'max_activations' => $maxActivations, 'expires_at' => $expiresAt ? $expiresAt->format('Y-m-d H:i:s') : null, ], ['%s', '%d', '%d', '%d', '%s', '%d', '%s', '%d', '%d', '%s'] ); return $result !== false; } /** * Export all licenses to array format suitable for CSV * * @return array Array of license data for CSV export */ public function exportLicensesForCsv(): array { global $wpdb; $tableName = Installer::getLicensesTable(); $rows = $wpdb->get_results( "SELECT * FROM {$tableName} ORDER BY created_at DESC", ARRAY_A ); $exportData = []; foreach ($rows ?: [] as $row) { $product = wc_get_product((int) $row['product_id']); $customer = get_userdata((int) $row['customer_id']); $order = wc_get_order((int) $row['order_id']); $exportData[] = [ 'ID' => $row['id'], 'License Key' => $row['license_key'], 'Product' => $product ? $product->get_name() : 'Unknown', 'Product ID' => $row['product_id'], 'Order ID' => $row['order_id'], 'Order Number' => $order ? $order->get_order_number() : '', 'Customer' => $customer ? $customer->display_name : 'Guest', 'Customer Email' => $customer ? $customer->user_email : '', 'Customer ID' => $row['customer_id'], 'Domain' => $row['domain'], 'Status' => ucfirst($row['status']), 'Activations' => $row['activations_count'], 'Max Activations' => $row['max_activations'], 'Expires At' => $row['expires_at'] ?: 'Lifetime', 'Created At' => $row['created_at'], 'Updated At' => $row['updated_at'], ]; } return $exportData; } }