296 lines
No EOL
9 KiB
PHP
296 lines
No EOL
9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\UserShop;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Config;
|
|
|
|
/**
|
|
* Domain Service - Centralized domain and subdomain management
|
|
*
|
|
* This service provides a centralized way to handle domain resolution,
|
|
* subdomain validation, and domain-specific configuration management.
|
|
*/
|
|
class DomainService
|
|
{
|
|
private const CACHE_TTL = 3600; // 1 hour
|
|
private const CACHE_TAG_USER_SHOPS = 'user_shops';
|
|
private const CACHE_TAG_DOMAIN_PARSING = 'domain_parsing';
|
|
private const FIXED_SUBDOMAINS = ['my', 'in', 'checkout'];
|
|
|
|
private array $domainConfig;
|
|
|
|
public function __construct(?array $domainConfig = null)
|
|
{
|
|
$this->domainConfig = $domainConfig ?? config('domains');
|
|
}
|
|
|
|
/**
|
|
* Determine the type of subdomain
|
|
*/
|
|
public function getSubdomainType(string $subdomain): string
|
|
{
|
|
// Frühe Validierung: Prüfe reservierte Subdomains aus Konfiguration
|
|
$reservedSubdomains = $this->domainConfig['reserved_subdomains'] ?? self::FIXED_SUBDOMAINS;
|
|
|
|
if (in_array($subdomain, $reservedSubdomains)) {
|
|
return match($subdomain) {
|
|
'my' => 'crm',
|
|
'in' => 'portal',
|
|
'checkout' => 'checkout',
|
|
default => 'unknown' // Andere reservierte Subdomains sind ungültig
|
|
};
|
|
}
|
|
|
|
// Frühe Validierung: Prüfe auf ungültige Zeichen für UserShop-Slugs
|
|
if (!preg_match('/^[a-z0-9-]+$/', $subdomain) || strlen($subdomain) < 3) {
|
|
return 'unknown';
|
|
}
|
|
|
|
// Check if it's a valid user shop
|
|
if ($this->isValidUserShop($subdomain)) {
|
|
return 'user-shop';
|
|
}
|
|
|
|
return 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Check if a subdomain represents a valid user shop
|
|
*/
|
|
public function isValidUserShop(string $slug): bool
|
|
{
|
|
$cacheKey = "user_shop_valid_{$slug}";
|
|
|
|
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($slug) {
|
|
// Optimierte Query mit allen Validierungen in einem DB-Call
|
|
$userShop = UserShop::where('slug', $slug)
|
|
->where('active', true)
|
|
->whereHas('user', function ($query) {
|
|
$query->whereNotNull('payment_shop')
|
|
->where('payment_shop', '>', now());
|
|
})
|
|
->exists();
|
|
|
|
return $userShop;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get user shop by slug with caching
|
|
*/
|
|
public function getUserShop(string $slug): ?UserShop
|
|
{
|
|
$cacheKey = "user_shop_{$slug}";
|
|
|
|
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($slug) {
|
|
// Optimierte Query mit allen Validierungen in einem DB-Call
|
|
return UserShop::where('slug', $slug)
|
|
->where('active', true)
|
|
->whereHas('user', function ($query) {
|
|
$query->whereNotNull('payment_shop')
|
|
->where('payment_shop', '>', now());
|
|
})
|
|
->with('user')
|
|
->first();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parse domain from request and determine context
|
|
*/
|
|
public function parseDomain(string $host): array
|
|
{
|
|
// Normalisiere den Host (lowercase)
|
|
$host = strtolower(trim($host));
|
|
$parts = explode('.', $host);
|
|
|
|
// Handle different TLD scenarios
|
|
if (count($parts) < 2) {
|
|
\Log::warning('Invalid host format', ['host' => $host]);
|
|
return [
|
|
'type' => 'invalid',
|
|
'domain' => $host,
|
|
'subdomain' => null,
|
|
'tld' => null,
|
|
'host' => $host
|
|
];
|
|
}
|
|
|
|
// Extract TLD and domain
|
|
$tld = '.' . end($parts);
|
|
$domain = $parts[count($parts) - 2];
|
|
|
|
// Check for subdomain
|
|
$subdomain = null;
|
|
if (count($parts) > 2) {
|
|
$subdomain = $parts[0];
|
|
\Log::debug('DomainService: Using extracted subdomain', ['subdomain' => $subdomain, 'host' => $host]);
|
|
}
|
|
|
|
// Determine domain type based on subdomain and host
|
|
$type = $this->determineDomainType($host, $subdomain);
|
|
|
|
return [
|
|
'type' => $type,
|
|
'domain' => $domain,
|
|
'subdomain' => $subdomain,
|
|
'tld' => $tld,
|
|
'host' => $host,
|
|
'default_user_shop' => $this->domainConfig['domains']['shop']['default_user_shop'] ?? null
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Determine domain type based on full host and subdomain
|
|
*/
|
|
private function determineDomainType(string $host, ?string $subdomain): string
|
|
{
|
|
|
|
|
|
// Check against configured domains
|
|
foreach ($this->domainConfig['domains'] as $type => $config) {
|
|
if (isset($config['host'])) {
|
|
// Handle wildcard user-shop pattern
|
|
if ($type === 'user-shop') {
|
|
$pattern = str_replace('{subdomain}', '([a-z0-9-]+)', $config['host']);
|
|
if (preg_match("/^{$pattern}$/", $host)) {
|
|
return 'user-shop';
|
|
}
|
|
} else {
|
|
// Exact match for other domains
|
|
if ($host === $config['host']) {
|
|
return $type;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Additional check for subdomain-based detection
|
|
if ($subdomain) {
|
|
$subdomainType = $this->getSubdomainType($subdomain);
|
|
if ($subdomainType !== 'unknown') {
|
|
return $subdomainType;
|
|
}
|
|
}
|
|
|
|
return 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Build URL for specific domain type
|
|
*/
|
|
public function buildUrl(string $type, ?string $path = null, ?string $slug = null): string
|
|
{
|
|
$protocol = $this->domainConfig['protocol'] ?? 'https://';
|
|
|
|
$domainConfig = $this->domainConfig['domains'][$type] ?? null;
|
|
|
|
if (!$domainConfig) {
|
|
throw new \InvalidArgumentException("Unknown domain type: {$type}");
|
|
}
|
|
|
|
$host = $domainConfig['host'];
|
|
|
|
// Handle user-shop wildcard
|
|
if ($type === 'user-shop') {
|
|
if (!$slug) {
|
|
throw new \InvalidArgumentException('Slug required for user-shop URLs');
|
|
}
|
|
$host = str_replace('{subdomain}', $slug, $host);
|
|
}
|
|
|
|
$url = $protocol . $host;
|
|
|
|
if ($path) {
|
|
$url .= '/' . ltrim($path, '/');
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Get domain configuration
|
|
*/
|
|
public function getDomainConfiguration(): array
|
|
{
|
|
return $this->domainConfig;
|
|
}
|
|
|
|
/**
|
|
* Clear user shop cache
|
|
*/
|
|
public function clearUserShopCache(string $slug): void
|
|
{
|
|
Cache::forget("user_shop_valid_{$slug}");
|
|
Cache::forget("user_shop_{$slug}");
|
|
}
|
|
|
|
/**
|
|
* Clear all user shop caches
|
|
*/
|
|
public function clearAllUserShopCaches(): void
|
|
{
|
|
// In Laravel mit Cache-Tags würde das eleganter funktionieren
|
|
// Für jetzt eine einfache Lösung für häufig verwendete Shops
|
|
$commonSlugs = ['aloevera']; // Füge häufig verwendete Slugs hinzu
|
|
|
|
foreach ($commonSlugs as $slug) {
|
|
$this->clearUserShopCache($slug);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get default user shop for main domain (fallback)
|
|
*/
|
|
public function getDefaultUserShop(): ?UserShop
|
|
{
|
|
$defaultSlug = $this->domainConfig['domains']['shop']['default_user_shop'] ?? 'aloevera';
|
|
return $this->getUserShop($defaultSlug);
|
|
}
|
|
|
|
/**
|
|
* Validate domain configuration
|
|
*/
|
|
public function validateConfiguration(): array
|
|
{
|
|
$errors = [];
|
|
|
|
// Validate main domains
|
|
$requiredDomains = ['main', 'shop', 'crm', 'portal', 'checkout', 'user-shop'];
|
|
foreach ($requiredDomains as $domain) {
|
|
if (empty($this->domainConfig['domains'][$domain]['host'])) {
|
|
$errors[] = "Domain '{$domain}' not configured";
|
|
}
|
|
}
|
|
|
|
// Validate protocol
|
|
if (empty($this->domainConfig['protocol'])) {
|
|
$errors[] = 'Protocol not configured';
|
|
}
|
|
|
|
// Validate reserved subdomains
|
|
if (empty($this->domainConfig['reserved_subdomains'])) {
|
|
$errors[] = 'Reserved subdomains not configured';
|
|
}
|
|
|
|
// Validate shop default
|
|
$defaultShop = $this->domainConfig['domains']['shop']['default_user_shop'] ?? null;
|
|
if (!$defaultShop) {
|
|
$errors[] = 'Default user shop not configured for shop domain';
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Check if domain configuration is valid
|
|
*/
|
|
public function isConfigurationValid(): bool
|
|
{
|
|
return empty($this->validateConfiguration());
|
|
}
|
|
|
|
|
|
} |