'datetime', 'end_date' => 'datetime', 'phone_enabled' => 'boolean', 'background_fixed' => 'boolean', 'is_active' => 'boolean', ]; } /** * Route model binding uses 'slug' instead of 'id'. */ public function getRouteKeyName(): string { return 'slug'; } public function user(): BelongsTo { return $this->belongsTo(User::class); } public function subscribers(): HasMany { return $this->hasMany(Subscriber::class); } public function blocks(): HasMany { return $this->hasMany(PageBlock::class)->orderBy('sort_order'); } public function visibleBlocks(): HasMany { return $this->blocks()->where('is_visible', true); } public function getBlockByType(string $type): ?PageBlock { if ($this->relationLoaded('blocks')) { return $this->blocks->firstWhere('type', $type); } return $this->blocks()->where('type', $type)->first(); } public function getFormBlock(): ?PageBlock { return $this->getBlockByType('form'); } public function getHeroBlock(): ?PageBlock { return $this->getBlockByType('hero'); } /** * Phone field is shown and validated when enabled in the form block (falls back to legacy column). */ public function isPhoneFieldEnabledForSubscribers(): bool { $form = $this->getBlockByType('form'); if ($form !== null) { return (bool) data_get($form->content, 'fields.phone.enabled', false); } return (bool) $this->phone_enabled; } /** * When the form block marks the phone field as required (only applies if phone is enabled). */ public function isPhoneFieldRequiredForSubscribers(): bool { $form = $this->getBlockByType('form'); if ($form !== null) { return (bool) data_get($form->content, 'fields.phone.required', false); } return false; } public function headlineForMeta(): string { $hero = $this->getHeroBlock(); if ($hero !== null) { $headline = data_get($hero->content, 'headline'); if (is_string($headline) && $headline !== '') { return $headline; } } return $this->heading; } /** * Keeps legacy DB columns aligned with hero/form blocks until those columns are dropped. */ public function syncLegacyContentColumnsFromBlocks(): void { $this->load('blocks'); $hero = $this->getHeroBlock(); $form = $this->getFormBlock(); $updates = []; if ($hero !== null) { $c = $hero->content ?? []; $updates['heading'] = is_string($c['headline'] ?? null) ? $c['headline'] : $this->heading; $sub = $c['subheadline'] ?? null; $updates['intro_text'] = is_string($sub) ? $sub : null; } if ($form !== null) { $updates['phone_enabled'] = (bool) data_get($form->content, 'fields.phone.enabled', false); } if ($updates !== []) { $this->update($updates); } } public function mailwizzConfig(): HasOne { return $this->hasOne(MailwizzConfig::class); } public function weeztixConfig(): HasOne { return $this->hasOne(WeeztixConfig::class); } public function isBeforeStart(): bool { return Carbon::now()->lt($this->start_date); } public function isActive(): bool { $now = Carbon::now(); return $now->gte($this->start_date) && $now->lte($this->end_date); } public function isExpired(): bool { return Carbon::now()->gt($this->end_date); } public function scopeActive(Builder $query): Builder { return $query->where('is_active', true); } /** * Lifecycle label for admin tables: before registration opens, open window, or after end. */ public function statusKey(): string { if ($this->isBeforeStart()) { return 'before_start'; } if ($this->isExpired()) { return 'expired'; } return 'active'; } }