user() !== null; } protected function prepareForValidation(): void { $title = (string) $this->input('title', ''); $slug = (string) $this->input('slug', ''); $mode = (string) ($this->input('mode') ?: Collection::MODE_MANUAL); $sortMode = (string) ($this->input('sort_mode') ?: ($mode === Collection::MODE_SMART ? Collection::SORT_NEWEST : Collection::SORT_MANUAL)); if ($slug === '' && $title !== '') { $slug = Str::slug(Str::limit($title, 140, '')); } $this->merge([ 'slug' => $slug, 'mode' => $mode, 'sort_mode' => $sortMode, ]); } public function rules(): array { return [ 'title' => ['required', 'string', 'min:2', 'max:120'], 'slug' => ['required', 'string', 'min:2', 'max:140', 'regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/'], 'type' => ['nullable', 'in:' . implode(',', [ Collection::TYPE_PERSONAL, Collection::TYPE_COMMUNITY, Collection::TYPE_EDITORIAL, ])], 'editorial_owner_mode' => ['nullable', 'in:' . implode(',', [ Collection::EDITORIAL_OWNER_CREATOR, Collection::EDITORIAL_OWNER_STAFF_ACCOUNT, Collection::EDITORIAL_OWNER_SYSTEM, ])], 'editorial_owner_username' => ['nullable', 'string', 'max:60'], 'editorial_owner_label' => ['nullable', 'string', 'max:120'], 'description' => ['nullable', 'string', 'max:1000'], 'subtitle' => ['nullable', 'string', 'max:160'], 'summary' => ['nullable', 'string', 'max:320'], 'lifecycle_state' => ['nullable', 'in:' . implode(',', [ Collection::LIFECYCLE_DRAFT, Collection::LIFECYCLE_SCHEDULED, Collection::LIFECYCLE_PUBLISHED, Collection::LIFECYCLE_FEATURED, Collection::LIFECYCLE_ARCHIVED, Collection::LIFECYCLE_EXPIRED, ])], 'collaboration_mode' => ['nullable', 'in:' . implode(',', [ Collection::COLLABORATION_CLOSED, Collection::COLLABORATION_INVITE_ONLY, Collection::COLLABORATION_OPEN, ])], 'allow_submissions' => ['nullable', 'boolean'], 'allow_comments' => ['nullable', 'boolean'], 'allow_saves' => ['nullable', 'boolean'], 'event_key' => ['nullable', 'string', 'max:80'], 'event_label' => ['nullable', 'string', 'max:120'], 'season_key' => ['nullable', 'string', 'max:80'], 'banner_text' => ['nullable', 'string', 'max:200'], 'badge_label' => ['nullable', 'string', 'max:80'], 'spotlight_style' => ['nullable', 'in:' . implode(',', [ Collection::SPOTLIGHT_STYLE_DEFAULT, Collection::SPOTLIGHT_STYLE_EDITORIAL, Collection::SPOTLIGHT_STYLE_SEASONAL, Collection::SPOTLIGHT_STYLE_CHALLENGE, Collection::SPOTLIGHT_STYLE_COMMUNITY, ])], 'analytics_enabled' => ['nullable', 'boolean'], 'presentation_style' => ['nullable', 'in:' . implode(',', [ Collection::PRESENTATION_STANDARD, Collection::PRESENTATION_EDITORIAL_GRID, Collection::PRESENTATION_HERO_GRID, Collection::PRESENTATION_MASONRY, ])], 'emphasis_mode' => ['nullable', 'in:' . implode(',', [ Collection::EMPHASIS_COVER_HEAVY, Collection::EMPHASIS_BALANCED, Collection::EMPHASIS_ARTWORK_FIRST, ])], 'theme_token' => ['nullable', 'in:default,subtle-blue,violet,amber'], 'series_key' => ['nullable', 'string', 'max:80'], 'series_title' => ['nullable', 'string', 'max:160'], 'series_description' => ['nullable', 'string', 'max:400'], 'series_order' => ['nullable', 'integer', 'min:1', 'max:9999'], 'campaign_key' => ['nullable', 'string', 'max:80'], 'campaign_label' => ['nullable', 'string', 'max:120'], 'commercial_eligibility' => ['nullable', 'boolean'], 'promotion_tier' => ['nullable', 'string', 'max:40'], 'sponsorship_label' => ['nullable', 'string', 'max:120'], 'partner_label' => ['nullable', 'string', 'max:120'], 'monetization_ready_status' => ['nullable', 'string', 'max:40'], 'brand_safe_status' => ['nullable', 'string', 'max:40'], 'published_at' => ['nullable', 'date'], 'unpublished_at' => ['nullable', 'date', 'after:published_at'], 'archived_at' => ['nullable', 'date'], 'expired_at' => ['nullable', 'date'], 'visibility' => ['required', 'in:' . implode(',', [ Collection::VISIBILITY_PUBLIC, Collection::VISIBILITY_UNLISTED, Collection::VISIBILITY_PRIVATE, ])], 'mode' => ['required', 'in:' . implode(',', [ Collection::MODE_MANUAL, Collection::MODE_SMART, ])], 'sort_mode' => ['nullable', 'in:' . implode(',', [ Collection::SORT_MANUAL, Collection::SORT_NEWEST, Collection::SORT_OLDEST, Collection::SORT_POPULAR, ])], 'smart_rules_json' => ['nullable', 'array'], 'layout_modules_json' => ['nullable', 'array'], 'layout_modules_json.*.key' => ['required_with:layout_modules_json', 'string', 'max:60'], 'layout_modules_json.*.enabled' => ['nullable', 'boolean'], 'layout_modules_json.*.slot' => ['nullable', 'string', 'max:20'], ]; } public function withValidator($validator): void { $validator->after(function ($validator): void { $type = (string) ($this->input('type') ?: Collection::TYPE_PERSONAL); if ($type === Collection::TYPE_EDITORIAL && ! $this->user()?->hasRole('admin')) { $validator->errors()->add('type', 'Only staff can create editorial collections.'); } if ($type === Collection::TYPE_EDITORIAL && (string) $this->input('editorial_owner_mode') === Collection::EDITORIAL_OWNER_STAFF_ACCOUNT) { $username = trim((string) $this->input('editorial_owner_username', '')); if ($username === '') { $validator->errors()->add('editorial_owner_username', 'Choose the staff account that should own this editorial collection.'); } else { $target = User::query()->whereRaw('LOWER(username) = ?', [Str::lower($username)])->first(); if (! $target || ! ($target->isAdmin() || $target->isModerator())) { $validator->errors()->add('editorial_owner_username', 'The editorial owner must be an admin or moderator account.'); } } } if ($this->filled('unpublished_at') && ! $this->filled('published_at')) { $validator->errors()->add('published_at', 'Set a publish time before adding an unpublish time.'); } if ((string) $this->input('mode') !== Collection::MODE_SMART) { return; } if (! is_array($this->input('smart_rules_json'))) { $validator->errors()->add('smart_rules_json', 'Smart collections require at least one valid rule.'); } }); } }