Deploy Ladill Hosting / deploy (push) Successful in 26s
Add silent OAuth and session keepalive, and show imported RC orders alongside native hosting accounts. Co-authored-by: Cursor <cursoragent@cursor.com>
450 lines
12 KiB
PHP
450 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Support\Arr;
|
|
|
|
class HostingOrder extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
public const STATUS_PENDING = 'pending';
|
|
public const STATUS_ACTIVE = 'active';
|
|
public const STATUS_SUSPENDED = 'suspended';
|
|
public const STATUS_CANCELLED = 'cancelled';
|
|
public const STATUS_EXPIRED = 'expired';
|
|
public const STATUS_FAILED = 'failed';
|
|
|
|
public const TYPE_SINGLE_DOMAIN = 'single_domain';
|
|
public const TYPE_MULTI_DOMAIN = 'multi_domain';
|
|
public const TYPE_RESELLER = 'reseller';
|
|
|
|
protected $fillable = [
|
|
'platform_id',
|
|
'user_id',
|
|
'domain_id',
|
|
'domain_name',
|
|
'rc_order_id',
|
|
'rc_customer_id',
|
|
'plan_name',
|
|
'hosting_type',
|
|
'status',
|
|
'ip_address',
|
|
'cpanel_url',
|
|
'cpanel_username',
|
|
'bandwidth_mb',
|
|
'disk_space_mb',
|
|
'expires_at',
|
|
'provisioned_at',
|
|
'meta',
|
|
'is_legacy_rc',
|
|
];
|
|
|
|
protected $casts = [
|
|
'expires_at' => 'datetime',
|
|
'provisioned_at' => 'datetime',
|
|
'meta' => 'array',
|
|
];
|
|
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function domain(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Domain::class);
|
|
}
|
|
|
|
public function isActive(): bool
|
|
{
|
|
return $this->status === self::STATUS_ACTIVE;
|
|
}
|
|
|
|
public function isExpired(): bool
|
|
{
|
|
return $this->status === self::STATUS_EXPIRED
|
|
|| ($this->expires_at && $this->expires_at->isPast());
|
|
}
|
|
|
|
public function getDomainNameAttribute($value): string
|
|
{
|
|
return $value ?: $this->metaValue([
|
|
'domain_name',
|
|
'domainname',
|
|
'domain-name',
|
|
'domain',
|
|
'hostname',
|
|
'description',
|
|
], '');
|
|
}
|
|
|
|
public function getPlanNameAttribute($value): ?string
|
|
{
|
|
$resolved = $value ?: $this->metaValue([
|
|
'plan_name',
|
|
'planname',
|
|
'productcategory',
|
|
'productkey',
|
|
'description',
|
|
]);
|
|
|
|
return $resolved !== '' ? $resolved : null;
|
|
}
|
|
|
|
public function getPlanIdAttribute(): ?string
|
|
{
|
|
$resolved = $this->metaValue([
|
|
'plan_id',
|
|
'planid',
|
|
'plan-id',
|
|
'productkey',
|
|
'plan.name',
|
|
'entitytype.entitytypekey',
|
|
]);
|
|
|
|
return $resolved !== null && $resolved !== '' ? (string) $resolved : null;
|
|
}
|
|
|
|
public function getRcOrderIdAttribute($value): ?string
|
|
{
|
|
$resolved = $value ?: $this->metaValue([
|
|
'rc_order_id',
|
|
'order_id',
|
|
'orderid',
|
|
'order-id',
|
|
'entityid',
|
|
'entity-id',
|
|
]);
|
|
|
|
return $resolved !== '' ? (string) $resolved : null;
|
|
}
|
|
|
|
public function getRcCustomerIdAttribute($value): ?string
|
|
{
|
|
$resolved = $value ?: $this->metaValue([
|
|
'rc_customer_id',
|
|
'customer_id',
|
|
'customerid',
|
|
'customer-id',
|
|
]);
|
|
|
|
return $resolved !== '' ? (string) $resolved : null;
|
|
}
|
|
|
|
public function getIpAddressAttribute($value): ?string
|
|
{
|
|
$resolved = $value ?: $this->metaValue([
|
|
'ip_address',
|
|
'ipaddress',
|
|
'ip',
|
|
]);
|
|
|
|
return $resolved !== '' ? $resolved : null;
|
|
}
|
|
|
|
public function getCpanelUrlAttribute($value): ?string
|
|
{
|
|
$resolved = $value ?: $this->metaValue([
|
|
'cpanel_url',
|
|
'cpanelurl',
|
|
'controlpanelurl',
|
|
'access_url',
|
|
'url',
|
|
]);
|
|
|
|
if ($this->looksLikeTemporaryHostingUrl($resolved)) {
|
|
$resolved = null;
|
|
}
|
|
|
|
if (! $resolved && $preferredDomain = $this->preferredCpanelDomain()) {
|
|
return 'https://'.$preferredDomain.'/cpanel';
|
|
}
|
|
|
|
if (! $resolved && $this->ip_address) {
|
|
return 'https://'.$this->ip_address.':2083';
|
|
}
|
|
|
|
return $resolved !== '' ? $resolved : null;
|
|
}
|
|
|
|
public function getCpanelUsernameAttribute($value): ?string
|
|
{
|
|
$resolved = $value ?: $this->metaValue([
|
|
'cpanel_username',
|
|
'cpanelusername',
|
|
'access_username',
|
|
'username',
|
|
'siteadminusername',
|
|
]);
|
|
|
|
return $resolved !== '' ? $resolved : null;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
public function getNameserversAttribute($value): array
|
|
{
|
|
$resolved = [];
|
|
|
|
if (is_array($value)) {
|
|
$resolved = $value;
|
|
} else {
|
|
$candidate = $this->metaValue([
|
|
'nameservers',
|
|
'nameserver',
|
|
'name_servers',
|
|
'ns',
|
|
'dns.nameservers',
|
|
'server.nameservers',
|
|
]);
|
|
|
|
if (is_array($candidate)) {
|
|
$resolved = $candidate;
|
|
} else {
|
|
$resolved = array_filter([
|
|
$this->metaValue(['ns1', 'nameserver1', 'name_server_1', 'server.ns1']),
|
|
$this->metaValue(['ns2', 'nameserver2', 'name_server_2', 'server.ns2']),
|
|
$this->metaValue(['ns3', 'nameserver3', 'name_server_3', 'server.ns3']),
|
|
$this->metaValue(['ns4', 'nameserver4', 'name_server_4', 'server.ns4']),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return collect($resolved)
|
|
->map(fn ($item) => strtolower(trim((string) $item, " \t\n\r\0\x0B.")))
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
public function getWebsiteUrlAttribute(): ?string
|
|
{
|
|
$domain = strtolower(trim((string) $this->domain_name));
|
|
|
|
if ($domain === '' || filter_var($domain, FILTER_VALIDATE_IP)) {
|
|
return null;
|
|
}
|
|
|
|
return 'https://'.$domain;
|
|
}
|
|
|
|
public function getDirectUrlAttribute(): ?string
|
|
{
|
|
$resolved = $this->metaValue([
|
|
'tempurl',
|
|
'temp_url',
|
|
'temporary_url',
|
|
'server.tempurl',
|
|
'server.temporary_url',
|
|
]);
|
|
|
|
return $resolved !== '' ? (string) $resolved : null;
|
|
}
|
|
|
|
public function getDiskSpaceMbAttribute($value): ?int
|
|
{
|
|
$resolved = $value ?? $this->metaValue([
|
|
'disk_space_mb',
|
|
'diskspace',
|
|
'disk_space',
|
|
]);
|
|
|
|
return is_numeric($resolved) && (int) $resolved >= 0 ? (int) $resolved : null;
|
|
}
|
|
|
|
public function getBandwidthMbAttribute($value): ?int
|
|
{
|
|
$resolved = $value ?? $this->metaValue([
|
|
'bandwidth_mb',
|
|
'bandwidth',
|
|
]);
|
|
|
|
return is_numeric($resolved) && (int) $resolved >= 0 ? (int) $resolved : null;
|
|
}
|
|
|
|
public function getStatusAttribute($value): string
|
|
{
|
|
$candidate = $value;
|
|
|
|
if ($candidate === null || $candidate === '' || $candidate === self::STATUS_PENDING) {
|
|
$candidate = $this->metaValue([
|
|
'status',
|
|
'currentstatus',
|
|
'orderstatus',
|
|
], $candidate ?: self::STATUS_PENDING);
|
|
}
|
|
|
|
return $this->normalizeStatus((string) $candidate);
|
|
}
|
|
|
|
public function getProvisionedAtAttribute($value): ?Carbon
|
|
{
|
|
if ($value instanceof Carbon) {
|
|
return $value;
|
|
}
|
|
|
|
if ($value) {
|
|
return Carbon::parse($value);
|
|
}
|
|
|
|
$timestamp = $this->metaValue([
|
|
'provisioned_at',
|
|
'creation_time',
|
|
'creationtime',
|
|
'creation-date',
|
|
]);
|
|
|
|
if ($timestamp === null || $timestamp === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return is_numeric($timestamp)
|
|
? Carbon::createFromTimestamp((int) $timestamp)
|
|
: Carbon::parse((string) $timestamp);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function getExpiresAtAttribute($value): ?Carbon
|
|
{
|
|
if ($value instanceof Carbon) {
|
|
return $value;
|
|
}
|
|
|
|
if ($value) {
|
|
return Carbon::parse($value);
|
|
}
|
|
|
|
$timestamp = $this->metaValue([
|
|
'expires_at',
|
|
'end_time',
|
|
'endtime',
|
|
'expirytime',
|
|
'expiry-date',
|
|
]);
|
|
|
|
if ($timestamp === null || $timestamp === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return is_numeric($timestamp)
|
|
? Carbon::createFromTimestamp((int) $timestamp)
|
|
: Carbon::parse((string) $timestamp);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function normalizedMeta(): array
|
|
{
|
|
$meta = $this->getAttributes()['meta'] ?? $this->meta ?? [];
|
|
|
|
while (is_string($meta)) {
|
|
$decoded = json_decode($meta, true);
|
|
$meta = json_last_error() === JSON_ERROR_NONE ? $decoded : [];
|
|
}
|
|
|
|
return is_array($meta) ? $meta : [];
|
|
}
|
|
|
|
private function metaValue(array $keys, mixed $default = null): mixed
|
|
{
|
|
$meta = $this->normalizedMeta();
|
|
$sources = [$meta];
|
|
|
|
if (is_array($meta['raw'] ?? null)) {
|
|
$sources[] = $meta['raw'];
|
|
}
|
|
|
|
foreach ($sources as $source) {
|
|
foreach ($keys as $key) {
|
|
$nested = Arr::get($source, $key);
|
|
if ($nested !== null && $nested !== '') {
|
|
return $nested;
|
|
}
|
|
|
|
if (array_key_exists($key, $source) && $source[$key] !== null && $source[$key] !== '') {
|
|
return $source[$key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
|
|
private function normalizeStatus(string $status): string
|
|
{
|
|
return match (strtolower(trim($status))) {
|
|
'active', 'active (paid)' => self::STATUS_ACTIVE,
|
|
'suspended', 'inactive' => self::STATUS_SUSPENDED,
|
|
'deleted', 'cancelled' => self::STATUS_CANCELLED,
|
|
'expired' => self::STATUS_EXPIRED,
|
|
'failed' => self::STATUS_FAILED,
|
|
default => self::STATUS_PENDING,
|
|
};
|
|
}
|
|
|
|
private function looksLikeTemporaryHostingUrl(?string $value): bool
|
|
{
|
|
$normalized = strtolower(trim((string) $value));
|
|
|
|
if ($normalized === '') {
|
|
return false;
|
|
}
|
|
|
|
return str_contains($normalized, 'tempwebhost.net')
|
|
|| str_contains($normalized, '/~');
|
|
}
|
|
|
|
private function preferredCpanelDomain(): ?string
|
|
{
|
|
$domain = strtolower(trim((string) $this->domain_name));
|
|
|
|
if ($domain === '' || filter_var($domain, FILTER_VALIDATE_IP)) {
|
|
return null;
|
|
}
|
|
|
|
if ($this->relationLoaded('domain') && $this->domain) {
|
|
return $this->domainIsActive($this->domain) ? $domain : null;
|
|
}
|
|
|
|
if ($this->domain_id) {
|
|
$linkedDomain = $this->domain()->first();
|
|
|
|
if ($linkedDomain) {
|
|
return $this->domainIsActive($linkedDomain) ? $domain : null;
|
|
}
|
|
}
|
|
|
|
$matchingDomain = Domain::query()
|
|
->where('host', $domain)
|
|
->latest('id')
|
|
->first();
|
|
|
|
if ($matchingDomain) {
|
|
return $this->domainIsActive($matchingDomain) ? $domain : null;
|
|
}
|
|
|
|
return $domain;
|
|
}
|
|
|
|
private function domainIsActive(Domain $domain): bool
|
|
{
|
|
return (string) $domain->status === 'verified'
|
|
&& (string) $domain->onboarding_state === Domain::STATE_ACTIVE;
|
|
}
|
|
}
|