Files
wc-licensed-product/src/License/LicenseManager.php

1054 lines
31 KiB
PHP
Raw Normal View History

<?php
/**
* License Manager
*
* @package Jeremias\WcLicensedProduct\License
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\License;
use Jeremias\WcLicensedProduct\Installer;
use Jeremias\WcLicensedProduct\Product\LicensedProduct;
/**
* Manages license operations (CRUD, validation, generation)
*/
class LicenseManager
{
/**
* 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
*/
public function generateLicense(
int $orderId,
int $productId,
int $customerId,
string $domain
): ?License {
global $wpdb;
// Normalize domain first for duplicate detection
$normalizedDomain = $this->normalizeDomain($domain);
// Check if license already exists for this order, product, and domain
$existing = $this->getLicenseByOrderProductAndDomain($orderId, $productId, $normalizedDomain);
if ($existing) {
return $existing;
}
$product = wc_get_product($productId);
if (!$product || !$product->is_type('licensed')) {
return null;
}
// Ensure we have the LicensedProduct instance for type hints
if (!$product instanceof LicensedProduct) {
$product = new LicensedProduct($productId);
}
// Generate unique license key
$licenseKey = $this->generateLicenseKey();
while ($this->getLicenseByKey($licenseKey)) {
$licenseKey = $this->generateLicenseKey();
}
// Calculate expiration date
$expiresAt = null;
$validityDays = $product->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
$versionId = null;
if ($product->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' => $product->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 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;
}
}