'array', 'schema_version' => 'integer', 'render_version' => 'integer', 'preview_width' => 'integer', 'preview_height' => 'integer', 'featured' => 'boolean', 'allow_download' => 'boolean', 'allow_remix' => 'boolean', 'views_count' => 'integer', 'shares_count' => 'integer', 'downloads_count' => 'integer', 'likes_count' => 'integer', 'favorites_count' => 'integer', 'saves_count' => 'integer', 'remixes_count' => 'integer', 'comments_count' => 'integer', 'challenge_entries_count' => 'integer', 'trending_score' => 'float', 'featured_score' => 'float', 'density_score' => 'integer', 'allow_background_reuse' => 'boolean', 'allow_export' => 'boolean', 'published_at' => 'datetime', 'last_engaged_at' => 'datetime', 'last_ranked_at' => 'datetime', 'last_rendered_at' => 'datetime', ]; protected static function booted(): void { static::creating(function (self $card): void { if (! $card->uuid) { $card->uuid = (string) Str::uuid(); } }); } public function user(): BelongsTo { return $this->belongsTo(User::class); } public function category(): BelongsTo { return $this->belongsTo(NovaCardCategory::class, 'category_id'); } public function template(): BelongsTo { return $this->belongsTo(NovaCardTemplate::class, 'template_id'); } public function backgroundImage(): BelongsTo { return $this->belongsTo(NovaCardBackground::class, 'background_image_id'); } public function originalCard(): BelongsTo { return $this->belongsTo(self::class, 'original_card_id'); } public function rootCard(): BelongsTo { return $this->belongsTo(self::class, 'root_card_id'); } public function originalCreator(): BelongsTo { return $this->belongsTo(User::class, 'original_creator_id'); } public function remixes(): HasMany { return $this->hasMany(self::class, 'original_card_id'); } public function tags(): BelongsToMany { return $this->belongsToMany(NovaCardTag::class, 'nova_card_tag_relation', 'card_id', 'tag_id') ->withTimestamps(); } public function reactions(): HasMany { return $this->hasMany(NovaCardReaction::class, 'card_id'); } public function versions(): HasMany { return $this->hasMany(NovaCardVersion::class, 'card_id'); } public function collectionItems(): HasMany { return $this->hasMany(NovaCardCollectionItem::class, 'card_id'); } public function challengeEntries(): HasMany { return $this->hasMany(NovaCardChallengeEntry::class, 'card_id'); } public function comments(): HasMany { return $this->hasMany(NovaCardComment::class, 'card_id'); } public function scopePublished(Builder $query): Builder { return $query ->where('status', self::STATUS_PUBLISHED) ->where('moderation_status', '!=', self::MOD_REJECTED) ->whereIn('visibility', [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED]) ->whereNotNull('published_at'); } public function scopePubliclyVisible(Builder $query): Builder { return $query ->published() ->where('visibility', self::VISIBILITY_PUBLIC) ->whereNotIn('moderation_status', [self::MOD_FLAGGED, self::MOD_REJECTED]) ->where('status', self::STATUS_PUBLISHED); } public function scopeRising(Builder $query): Builder { return $query ->publiclyVisible() ->where('published_at', '>=', now()->subHours(96)) ->where(function (Builder $q): void { $q->where('saves_count', '>', 0) ->orWhere('remixes_count', '>', 0) ->orWhere('likes_count', '>', 1); }); } public function scopeFeaturedEditorial(Builder $query): Builder { return $query ->publiclyVisible() ->where(function (Builder $q): void { $q->whereNotNull('featured_score') ->orWhere('featured', true); }); } public function previewUrl(): ?string { if (! $this->preview_path) { return null; } return Storage::disk((string) config('nova_cards.storage.public_disk', 'public'))->url($this->preview_path); } public function ogPreviewUrl(): ?string { if (! $this->preview_path) { return null; } $ogPath = preg_replace('/\.webp$/', '-og.jpg', $this->preview_path) ?: null; if (! $ogPath) { return $this->previewUrl(); } return Storage::disk((string) config('nova_cards.storage.public_disk', 'public'))->url($ogPath); } public function publicUrl(): string { return route('cards.show', ['slug' => $this->slug, 'id' => $this->id]); } public function isOwnedBy(?User $user): bool { return $user !== null && (int) $user->id === (int) $this->user_id; } public function canBeViewedBy(?User $user): bool { if ($this->isOwnedBy($user)) { return true; } if ($this->status !== self::STATUS_PUBLISHED) { return false; } if ($this->moderation_status === self::MOD_REJECTED || $this->moderation_status === self::MOD_FLAGGED) { return false; } return in_array($this->visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED], true); } public function isRemix(): bool { return $this->original_card_id !== null; } public function canReceiveCommentsFrom(?User $user): bool { return $user !== null && $this->canBeViewedBy($user); } }