getQuestionValidationRules()   A
last analyzed

Complexity

Conditions 5
Paths 16

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 16
nop 1
dl 0
loc 22
rs 9.6111
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
4
namespace App\Http\Requests;
5
6
use App;
7
use App\Models\Compilation;
8
use App\Models\Question;
9
use App\Services\AcademicYearService;
10
use Carbon\Carbon;
11
use Illuminate\Foundation\Http\FormRequest;
12
use Illuminate\Support\Facades\Auth;
13
use Illuminate\Validation\Rule;
14
15
class StoreCompilationRequest extends FormRequest
16
{
17
18
    /**
19
     * @var array Queue reporting question mandatority depending on the answer of a previous question
20
     */
21
    private $mandatorityQueue = [];
22
23
    /**
24
     * Determine if the user is authorized to make this request.
25
     *
26
     * @return bool
27
     */
28
    public function authorize()
29
    {
30
        $user = Auth::user();
31
        return $user->can("create", Compilation::class);
32
    }
33
34
    /**
35
     * Get the validation rules that apply to the request.
36
     *
37
     * @param AcademicYearService $academicYearService
38
     * @return array
39
     */
40
    public function rules(AcademicYearService $academicYearService)
41
    {
42
43
        // Fixed question rules.
44
        $rules = [
45
            "student_id" => "required|exists:students,id|in:" . Auth::user()->student->id,
46
            "stage_location_id" => "required|exists:locations,id",
47
            "stage_ward_id" => "required|exists:wards,id",
48
            "stage_start_date" => [
49
                "bail",
50
                "required",
51
                "date_format:Y-m-d",
52
                "before:today",
53
                "not_overlapping_time_range:stage_end_date,compilations,stage_start_date,stage_end_date,student_id",
54
            ],
55
            "stage_end_date" => [
56
                "bail",
57
                "required",
58
                "date_format:Y-m-d",
59
                "after:stage_start_date",
60
                "before:" . $this->getMaxStageEndDate($this->stage_start_date ?? ""),
61
                "not_overlapping_time_range:stage_start_date,compilations,stage_start_date,stage_end_date,student_id",
62
            ],
63
            "stage_academic_year" => "required|in:" . implode(",", [
64
                    $academicYearService->getPrevious(),
65
                    $academicYearService->getCurrent()
66
                ])
67
        ];
68
69
        // Variable question rules.
70
        $questions = Question::all();
71
72
        foreach ($questions as $question) {
73
74
            $rules["q" . $question->id] = [];
75
76
            foreach ($this->getQuestionValidationRules($question) as $rule) {
77
                $rules["q" . $question->id][] = $rule;
78
            }
79
80
            $this->updateMandatorityQueue($question);
81
82
        }
83
84
        return $rules;
85
    }
86
87
    /**
88
     * Stage end date must be:
89
     *  - before one week from today,
90
     *  - before N weeks after stage start date.
91
     *
92
     * @param string $stageStartDate
93
     * @return string
94
     */
95
    private function getMaxStageEndDate(string $stageStartDate) : string
96
    {
97
        $oneWeekFromToday = Carbon::today()->addWeeks(1);
98
        
99
        // If stage start date is empty or invalid,
100
        // one week from today date is returned.
101
        if ($stageStartDate === "" ||
102
            \DateTime::createFromFormat("Y-m-d", $stageStartDate) === false) {
103
            return $oneWeekFromToday->format("Y-m-d");
104
        }
105
106
        $maxStageEndDate = Carbon::parse($stageStartDate)->addWeeks(config("clqei.stages.max_weeks"));
107
        if ($oneWeekFromToday < $maxStageEndDate) {
108
            $maxStageEndDate = $oneWeekFromToday;
109
        }
110
111
        return $maxStageEndDate->format("Y-m-d");
112
    }
113
114
    /**
115
     * Get validation rules of a single question (dynamic questions, with key "qN").
116
     *
117
     * @param Question $question
118
     * @return \Generator
119
     */
120
    private function getQuestionValidationRules(Question $question)
121
    {
122
123
        // https://laravel.com/docs/5.5/validation#a-note-on-optional-fields
124
        yield ($question->required === true ? "required" : "nullable");
125
126
        if (in_array($question->type, ["single_choice", "multiple_choice"]) === true) {
127
            yield Rule::exists("answers", "id")
128
                ->where(function ($query) use ($question) {
129
                    $query->where("question_id", $question->id);
130
                });
131
        }
132
133
        if ($question->type === "date") {
134
            yield "date";
135
        }
136
137
        // Reading of the mandatority queue, in case the current question is
138
        // required according to the value of a previous question.
139
        $requirement = array_pop($this->mandatorityQueue);
140
        if ($requirement !== null) {
141
            yield "required_if:q" . $requirement["question"] . "," . $requirement["answer"];
142
        }
143
    }
144
145
    /**
146
     * If a specific answer of the input question makes next question(s) mandatory,
147
     * one or more items are added to mandatority queue.
148
     *
149
     * @param Question $question
150
     */
151
    private function updateMandatorityQueue(Question $question)
152
    {
153
        if (isset($question->options) === false) {
154
            return;
155
        }
156
157
        $options = $question->options;
158
        if (isset($options->makes_next_required) === false) {
159
            return;
160
        }
161
        for ($i = 0; $i < $options->makes_next_required->next; $i++) {
162
            array_push(
163
                $this->mandatorityQueue,
164
                [
165
                    "question" => $question->id,
166
                    "answer" => $question->answers[$options->makes_next_required->answer - 1]->id
167
                ]
168
            );
169
        }
170
171
    }
172
173
    /**
174
     * (Integrate and) get data to be validated from the request.
175
     *
176
     * @return array
177
     */
178
    protected function validationData()
179
    {
180
        // When a question could have several answers but none is given,
181
        // one compilation item is anyway created, with NULL answer.
182
        // This logic is required because HTML array fields (e.g. set of checkboxes),
183
        // are not sent when no value is selected (even "nullable"
184
        // validation flag is useless in this case).
185
        foreach (Question::all() as $question) {
186
            if ($this->has("q" . $question->id) === false) {
187
                $this->merge(["q" . $question->id => null]);
188
            }
189
        }
190
191
        return $this->all();
192
    }
193
194
}
195