*/ public function rules(): array { return [ 'name' => ['required', 'string', 'min:2', 'max:120'], 'email' => ['required', 'string', 'email:rfc', 'max:255'], // A minimum length is worth more than it looks: it costs a real // person nothing and rules out the one-word submissions that are // never anything but noise. 'message' => ['required', 'string', 'min:20', 'max:5000'], // Only the general form offers a choice; applying to a call is // always an offer of time. 'kind' => [ $this->isCallApplication() ? 'prohibited' : 'sometimes', Rule::enum(ApplicationKind::class), ], 'organisation' => [ Rule::requiredIf(fn () => $this->input('kind') === ApplicationKind::Request->value), 'nullable', 'string', 'max:160', ], 'hours_per_week' => ['sometimes', 'nullable', 'integer', 'min:1', 'max:40'], 'links' => ['sometimes', 'nullable', 'array', 'max:5'], 'links.*' => ['nullable', 'url:http,https', 'max:255'], // Personal data given by someone who is not a user of this system, // so the agreement to store it is part of the payload rather than // something the frontend is trusted to have asked for. 'consent' => ['accepted'], // The honeypot. Present in the rules so an unexpected type cannot // slip past the check that reads it later. $this->honeypotField() => ['nullable', 'string', 'max:255'], ]; } /** * @return array */ public function attributes(): array { return [ 'name' => __('rules.applications.attributes.name'), 'email' => __('rules.applications.attributes.email'), 'message' => __('rules.applications.attributes.message'), 'organisation' => __('rules.applications.attributes.organisation'), 'hours_per_week' => __('rules.applications.attributes.hours_per_week'), 'consent' => __('rules.applications.attributes.consent'), ]; } /** * Whether this request is answering a specific call. */ public function isCallApplication(): bool { return $this->route('slug') !== null; } /** * What the sender wants, with the call route pinned to "contribute". */ public function kind(): ApplicationKind { if ($this->isCallApplication()) { return ApplicationKind::Contribute; } return ApplicationKind::tryFrom((string) $this->input('kind')) ?? ApplicationKind::Contribute; } /** * A bot fills in every input it finds; a person never sees this one. */ public function looksAutomated(): bool { return filled($this->input($this->honeypotField())); } public function honeypotField(): string { return (string) config('site.submissions.honeypot', 'website'); } }