Deploy Ladill QR Plus / deploy (push) Successful in 28s
Full control center for ticketed events, contributions, attendees, badges, and programmes — not a QR utility clone. Includes SSO shell, import command, and platform cutover runbook. Co-authored-by: Cursor <cursoragent@cursor.com>
370 lines
11 KiB
PHP
370 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Services\Domain\DomainPricingService;
|
|
|
|
class DomainConfig
|
|
{
|
|
private const PRIORITY_TLDS = [
|
|
'com', 'org', 'net', 'ai', 'io', 'co', 'app', 'dev',
|
|
'store', 'shop', 'online', 'site', 'tech', 'cloud', 'pro', 'me',
|
|
'xyz', 'club', 'live', 'com.ng', 'com.gh', 'co.za', 'co.uk', 'info', 'biz',
|
|
];
|
|
|
|
private const DEFAULT_PAGE_SIZE = 20;
|
|
|
|
/**
|
|
* Get featured TLDs for initial display (lightweight).
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
public static function featuredTlds(): array
|
|
{
|
|
$allTlds = self::allTlds();
|
|
$featured = array_values(array_intersect(self::PRIORITY_TLDS, $allTlds));
|
|
|
|
if ($featured !== []) {
|
|
return $featured;
|
|
}
|
|
|
|
return array_slice($allTlds, 0, self::DEFAULT_PAGE_SIZE);
|
|
}
|
|
|
|
/**
|
|
* Get all available TLDs sorted by priority.
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
public static function allTlds(): array
|
|
{
|
|
$current = self::normalizeTlds(config('domain.search_tlds', []));
|
|
$legacy = self::normalizeTlds(config('mailinfra.domain_search_tlds', []));
|
|
|
|
$tlds = $legacy !== [] ? $legacy : $current;
|
|
$livePricedTlds = self::livePricedTlds();
|
|
|
|
if ($livePricedTlds === []) {
|
|
return [];
|
|
}
|
|
|
|
$livePricedLookup = array_flip($livePricedTlds);
|
|
$tlds = array_values(array_filter(
|
|
$tlds,
|
|
static fn (string $tld): bool => isset($livePricedLookup[$tld])
|
|
));
|
|
|
|
return self::sortWithPriority($tlds);
|
|
}
|
|
|
|
/**
|
|
* Get paginated TLDs (excluding featured ones).
|
|
*
|
|
* @return array{tlds: list<string>, hasMore: bool, total: int}
|
|
*/
|
|
public static function paginatedTlds(int $page = 1, int $perPage = self::DEFAULT_PAGE_SIZE): array
|
|
{
|
|
$allTlds = self::allTlds();
|
|
$featured = self::featuredTlds();
|
|
|
|
$remaining = array_values(array_filter($allTlds, fn ($tld) => !in_array($tld, $featured, true)));
|
|
$total = count($remaining);
|
|
|
|
$offset = ($page - 1) * $perPage;
|
|
$tlds = array_slice($remaining, $offset, $perPage);
|
|
|
|
return [
|
|
'tlds' => $tlds,
|
|
'hasMore' => ($offset + $perPage) < $total,
|
|
'total' => $total,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use featuredTlds() or allTlds() instead
|
|
* @return list<string>
|
|
*/
|
|
public static function searchTlds(): array
|
|
{
|
|
return self::featuredTlds();
|
|
}
|
|
|
|
/**
|
|
* Build the TLD list and query term for a search page request.
|
|
*
|
|
* @return array{query: string, tlds: list<string>, hasMore: bool, exact_domain: string|null}
|
|
*/
|
|
public static function searchPageConfig(string $query, int $page = 1, int $perPage = self::DEFAULT_PAGE_SIZE): array
|
|
{
|
|
$query = strtolower(trim($query));
|
|
$allTlds = self::allTlds();
|
|
|
|
if ($allTlds === []) {
|
|
return [
|
|
'query' => $query,
|
|
'tlds' => [],
|
|
'hasMore' => false,
|
|
'exact_domain' => null,
|
|
];
|
|
}
|
|
|
|
$exactDomain = self::normalizeExactDomain($query);
|
|
|
|
if ($exactDomain === null) {
|
|
if ($page === 1) {
|
|
$featured = self::featuredTlds();
|
|
$paginatedData = self::paginatedTlds(1, $perPage);
|
|
|
|
return [
|
|
'query' => $query,
|
|
'tlds' => $featured,
|
|
'hasMore' => $paginatedData['hasMore'] || count($paginatedData['tlds']) > 0,
|
|
'exact_domain' => null,
|
|
];
|
|
}
|
|
|
|
$paginatedData = self::paginatedTlds($page - 1, $perPage);
|
|
|
|
return [
|
|
'query' => $query,
|
|
'tlds' => $paginatedData['tlds'],
|
|
'hasMore' => $paginatedData['hasMore'],
|
|
'exact_domain' => null,
|
|
];
|
|
}
|
|
|
|
$exactTld = self::extractTld($exactDomain);
|
|
$keyword = explode('.', $exactDomain, 2)[0] ?? $exactDomain;
|
|
$firstPageTlds = array_values(array_unique(array_filter([
|
|
$exactTld,
|
|
...self::featuredTlds(),
|
|
])));
|
|
$remainingTlds = array_values(array_filter(
|
|
$allTlds,
|
|
static fn (string $tld): bool => ! in_array($tld, $firstPageTlds, true)
|
|
));
|
|
|
|
if ($page === 1) {
|
|
return [
|
|
'query' => $keyword,
|
|
'tlds' => $firstPageTlds,
|
|
'hasMore' => $remainingTlds !== [],
|
|
'exact_domain' => $exactDomain,
|
|
];
|
|
}
|
|
|
|
$offset = max(0, ($page - 2) * $perPage);
|
|
|
|
return [
|
|
'query' => $keyword,
|
|
'tlds' => array_slice($remainingTlds, $offset, $perPage),
|
|
'hasMore' => ($offset + $perPage) < count($remainingTlds),
|
|
'exact_domain' => $exactDomain,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Sort domain search results by TLD priority.
|
|
*
|
|
* @param array<int, array{domain: string, ...}> $results
|
|
* @return array<int, array{domain: string, ...}>
|
|
*/
|
|
public static function sortResultsByTldPriority(array $results, ?string $query = null): array
|
|
{
|
|
$priorityMap = array_flip(self::PRIORITY_TLDS);
|
|
$exactDomain = self::normalizeExactDomain((string) $query);
|
|
|
|
usort($results, static function (array $a, array $b) use ($priorityMap, $exactDomain): int {
|
|
$aDomain = strtolower((string) ($a['domain'] ?? ''));
|
|
$bDomain = strtolower((string) ($b['domain'] ?? ''));
|
|
|
|
$aExact = $exactDomain !== null && $aDomain === $exactDomain ? 0 : 1;
|
|
$bExact = $exactDomain !== null && $bDomain === $exactDomain ? 0 : 1;
|
|
|
|
if ($aExact !== $bExact) {
|
|
return $aExact <=> $bExact;
|
|
}
|
|
|
|
$aPremium = self::isPremiumResult($a) ? 0 : 1;
|
|
$bPremium = self::isPremiumResult($b) ? 0 : 1;
|
|
|
|
if ($aPremium !== $bPremium) {
|
|
return $aPremium <=> $bPremium;
|
|
}
|
|
|
|
$aAvailable = ! empty($a['available']) ? 0 : 1;
|
|
$bAvailable = ! empty($b['available']) ? 0 : 1;
|
|
|
|
if ($aAvailable !== $bAvailable) {
|
|
return $aAvailable <=> $bAvailable;
|
|
}
|
|
|
|
$aTld = self::extractTld($a['domain'] ?? '');
|
|
$bTld = self::extractTld($b['domain'] ?? '');
|
|
|
|
$aPriority = $priorityMap[$aTld] ?? PHP_INT_MAX;
|
|
$bPriority = $priorityMap[$bTld] ?? PHP_INT_MAX;
|
|
|
|
if ($aPriority !== $bPriority) {
|
|
return $aPriority <=> $bPriority;
|
|
}
|
|
|
|
return $aDomain <=> $bDomain;
|
|
});
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Extract TLD from a domain name.
|
|
*/
|
|
private static function extractTld(string $domain): string
|
|
{
|
|
$parts = explode('.', strtolower($domain), 2);
|
|
|
|
return $parts[1] ?? '';
|
|
}
|
|
|
|
private static function normalizeExactDomain(string $query): ?string
|
|
{
|
|
$query = strtolower(trim($query));
|
|
|
|
if ($query === '' || str_starts_with($query, '.') || str_ends_with($query, '.') || ! str_contains($query, '.')) {
|
|
return null;
|
|
}
|
|
|
|
return preg_match('/^(?=.{1,253}$)(?!-)(?:[a-z0-9-]{1,63}\.)+[a-z]{2,63}$/', $query) === 1
|
|
? $query
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $result
|
|
*/
|
|
private static function isPremiumResult(array $result): bool
|
|
{
|
|
$premium = $result['premium'] ?? false;
|
|
|
|
if (is_bool($premium)) {
|
|
return $premium;
|
|
}
|
|
|
|
return in_array(strtolower(trim((string) $premium)), ['1', 'yes', 'true', 'premium'], true);
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $tlds
|
|
* @return list<string>
|
|
*/
|
|
private static function sortWithPriority(array $tlds): array
|
|
{
|
|
$priorityMap = array_flip(self::PRIORITY_TLDS);
|
|
$pricingMap = self::pricingMapForTlds($tlds);
|
|
|
|
usort($tlds, static function (string $a, string $b) use ($priorityMap, $pricingMap): int {
|
|
$aPriority = $priorityMap[$a] ?? PHP_INT_MAX;
|
|
$bPriority = $priorityMap[$b] ?? PHP_INT_MAX;
|
|
|
|
if ($aPriority !== $bPriority) {
|
|
return $aPriority <=> $bPriority;
|
|
}
|
|
|
|
$aPrice = (int) ($pricingMap[$a]['register'] ?? 0);
|
|
$bPrice = (int) ($pricingMap[$b]['register'] ?? 0);
|
|
|
|
if ($aPrice !== $bPrice) {
|
|
return $bPrice <=> $aPrice;
|
|
}
|
|
|
|
return $a <=> $b;
|
|
});
|
|
|
|
return $tlds;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function resellerClubTldProductKeys(): array
|
|
{
|
|
return array_replace(
|
|
self::normalizeProductKeyMap(config('domain.resellerclub.tld_product_keys', [])),
|
|
self::normalizeProductKeyMap(config('mailinfra.resellerclub_tld_product_keys', []))
|
|
);
|
|
}
|
|
|
|
public static function resellerClubProductKeyCacheTtlSeconds(): int
|
|
{
|
|
$ttl = (int) config('domain.resellerclub.product_key_cache_ttl_seconds', 86400);
|
|
|
|
return $ttl > 0 ? $ttl : 86400;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return list<string>
|
|
*/
|
|
private static function normalizeTlds(mixed $value): array
|
|
{
|
|
if (! is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_unique(array_filter(array_map(
|
|
static fn ($tld): string => ltrim(strtolower(trim((string) $tld)), '.'),
|
|
$value
|
|
))));
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<string, string>
|
|
*/
|
|
private static function normalizeProductKeyMap(mixed $value): array
|
|
{
|
|
if (! is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
$map = [];
|
|
|
|
foreach ($value as $tld => $productKey) {
|
|
$normalizedTld = ltrim(strtolower(trim((string) $tld)), '.');
|
|
$normalizedProductKey = trim((string) $productKey);
|
|
|
|
if ($normalizedTld === '' || $normalizedProductKey === '') {
|
|
continue;
|
|
}
|
|
|
|
$map[$normalizedTld] = $normalizedProductKey;
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
private static function livePricedTlds(): array
|
|
{
|
|
try {
|
|
return app(DomainPricingService::class)->getLivePricedTlds();
|
|
} catch (\Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $tlds
|
|
* @return array<string, array{register: int, renew: int, transfer: int, currency: string}>
|
|
*/
|
|
private static function pricingMapForTlds(array $tlds): array
|
|
{
|
|
try {
|
|
return app(DomainPricingService::class)->getPricingForTlds($tlds);
|
|
} catch (\Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|