CompilationService::isCompilationCreatable()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 2
b 0
f 0
nc 2
nop 0
dl 0
loc 5
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace App\Services;
5
6
use App\Models\Answer;
7
use App\Models\Location;
8
use App\Models\Question;
9
use App\Models\Section;
10
use App\Models\Ward;
11
use Illuminate\Database\Eloquent\Builder;
12
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
13
use Illuminate\Support\Collection;
14
15
class CompilationService
16
{
17
18
    /**
19
     * @var EloquentCollection all questionnaire sections, also deleted
20
     */
21
    private $sections = null;
22
23
    /**
24
     * @var EloquentCollection all questionnaire questions, also deleted
25
     */
26
    private $questions = null;
27
28
    /**
29
     * @var EloquentCollection all questionnaire dynamic answers (i.e. based on database/seeds/*.json files), also deleted
30
     */
31
    private $answers = null;
32
33
    /**
34
     * @var Collection all questionnaire fixed questions (see __construct() method for details), also deleted
35
     */
36
    private $otherQuestions = null;
37
38
    /**
39
     * @var array all questionnaire "other" answers (see __construct() method for details), also deleted
40
     */
41
    private $otherAnswers = [];
42
43
    public function __construct(CountryService $countryService)
44
    {
45
        $this->sections = Section::withTrashed()->get()->keyBy("id");
46
        $this->questions = Question::withTrashed()->get()->keyBy("id");
47
        $this->answers = Answer::withTrashed()->get()->keyBy("id");
48
49
        // Questions whose answers are located on other tables
50
        // and that are not derived from database/seeds/*.json files
51
        $this->otherQuestions = new Collection([
52
            "stage_location_id" => __("Location"),
53
            "stage_ward_id" => __("Ward"),
54
            "stage_start_date" => __("Start date"),
55
            "stage_end_date" => __("End date"),
56
            "stage_academic_year" => __("Academic year"),
57
            "stage_weeks" => __("Weeks"),
58
            "student_gender" => __("Gender"),
59
            "student_nationality" => __("Nationality"),
60
        ]);
61
62
        // Answers located on other tables
63
        $this->otherAnswers["__stage_locations__"] = Location::withTrashed()->get()->keyBy("id");
64
        $this->otherAnswers["__stage_wards__"] = Ward::withTrashed()->get()->keyBy("id");
65
66
        // Answers not located on tables
67
        // @todo handles case of deleted country
68
        $this->otherAnswers["__student_nationalities__"] = new Collection($countryService->getCountries());
69
    }
70
71
    /**
72
     * Detect whether all environment requirements
73
     * to create a compilation are met
74
     *
75
     * @return bool
76
     */
77
    public function isCompilationCreatable(): bool
78
    {
79
        return
80
            Location::count() > 0 &&
81
            Ward::count() > 0;
82
    }
83
84
    /**
85
     * Get section text from ID.
86
     *
87
     * @param int|string $sectionId section ID
88
     * @return string
89
     */
90
    public function getSectionText($sectionId): string
91
    {
92
        $section = $this->sections->get($sectionId);
93
94
        if ($section) {
95
            return $section->text;
96
        }
97
98
        return (string)$sectionId;
99
    }
100
101
    /**
102
     * Get question text from ID (e.g. "23" or "q23").
103
     *
104
     * @param int|string $questionId question ID
105
     * @return string
106
     */
107
    public function getQuestionText($questionId): string
108
    {
109
110
        $otherQuestion = $this->otherQuestions->get($questionId);
111
112
        if ($otherQuestion) {
113
            return $otherQuestion;
114
        }
115
116
        $questionId = (string)$questionId;
117
118
        $question = $this->questions->get(preg_replace("/^q/", "", $questionId));
119
120
        if ($question) {
121
            return $question->text;
122
        }
123
124
        return $questionId;
125
    }
126
127
    /**
128
     * @param int|string $questionId question ID
129
     * @return bool
130
     */
131
    public function isFreeTextQuestion($questionId): bool
132
    {
133
134
        $otherQuestion = $this->otherQuestions->get($questionId);
135
136
        // All other questions are not free text.
137
        if ($otherQuestion) {
138
            return false;
139
        }
140
141
        $questionId = (string)$questionId;
142
143
        $question = $this->questions->get(preg_replace("/^q/", "", $questionId));
144
145
        if ($question) {
146
            return $question->type === "text";
147
        }
148
149
        return false;
150
    }
151
152
    /**
153
     * Get answer text from ID, if any.
154
     *
155
     * @param string|int $answerId
156
     * @param string $questionId context of the answer to search, in case it belongs to a different table
157
     * @return string
158
     *
159
     * @todo refactor
160
     */
161
    public function getAnswerText($answerId, string $questionId = ""): string
162
    {
163
164
        $text = $answerId;
165
166
        switch ($questionId) {
167
168
            case "stage_location_id":
169
            case "stage_ward_id":
170
                $text = $this->otherAnswers["__" . str_replace("_id", "s", $questionId) . "__"]->get($answerId)->name;
171
                break;
172
173
            case "stage_weeks":
174
                $text = $answerId;
175
                break;
176
177
            case "student_gender":
178
                $text = __($answerId);
179
                break;
180
181
            case "student_nationality":
182
                $text = $this->otherAnswers["__student_nationalities__"]->get($answerId);
183
                break;
184
185
            default:
186
                if ($answer = $this->answers->get($answerId)) {
187
                    $text = __($answer->text);
188
                }
189
190
        }
191
192
        return (string)$text;
193
194
    }
195
196
    /**
197
     * Get question's section, if available.
198
     *
199
     * @param int|string $questionId question ID
200
     * @return null|Section
201
     */
202
    public function getQuestionSection($questionId)
203
    {
204
205
        if ($this->otherQuestions->has($questionId) === true) {
206
            return null;
207
        }
208
209
        $questionId = (string)$questionId;
210
211
        $question = $this->questions->get(preg_replace("/^q/", "", $questionId));
212
213
        return $this->sections->get($question->section_id);
214
215
    }
216
217
    /**
218
     * Apply filters to compilation search query.
219
     *
220
     * @param Builder $query
221
     * @param string $parameter
222
     * @param $value
223
     */
224
    public function applyQueryFilters(Builder $query, string $parameter, $value)
225
    {
226
227
        if (strpos($parameter, "stage_") === 0) {
228
            $this->applyQueryStageFilters($query, $parameter, $value);
229
        }
230
231
        if (strpos($parameter, "student_") === 0) {
232
            $parameter = str_replace("student_", "", $parameter);
233
            $this->applyQueryStudentFilters($query, $parameter, $value);
234
        }
235
236
        // Dynamic questions
237
        if (preg_match("/^q\d+$/", $parameter) === 1) {
238
            $parameter = str_replace("q", "", $parameter);
239
            $query->whereHas("items", function ($query) use ($parameter, $value) {
240
                $query->where("question_id", $parameter)
241
                    ->where("answer", $value);
242
            });
243
        }
244
245
    }
246
247
    /**
248
     * Apply stage-related filters to compilation search query.
249
     *
250
     * @param Builder $query
251
     * @param string $parameter
252
     * @param mixed $value
253
     */
254
    private function applyQueryStageFilters(Builder $query, string $parameter, $value)
255
    {
256
257
        switch ($parameter) {
258
            case "stage_location_id":
259
            case "stage_ward_id":
260
            case "stage_academic_year":
261
                $query->where($parameter, $value);
262
                break;
263
            case "stage_weeks":
264
                // @todo check whether this SQL string is compatible with other database engines
265
                $query->whereRaw(
266
                    "round((strftime('%J', stage_end_date) - strftime('%J', stage_start_date) + 1) / 7) = ?",
267
                    [(int)$value]
268
                );
269
                break;
270
            default:
271
272
        }
273
    }
274
275
    /**
276
     * Apply student-related filters to compilation search query.
277
     *
278
     * @param Builder $query
279
     * @param string $parameter
280
     * @param mixed $value
281
     */
282
    private function applyQueryStudentFilters(Builder $query, string $parameter, $value)
283
    {
284
285
        switch ($parameter) {
286
            case "gender":
287
            case "nationality":
288
                $query->whereHas("student", function ($query) use ($parameter, $value) {
289
                    $query->where($parameter, $value);
290
                });
291
                break;
292
            default:
293
294
        }
295
    }
296
297
}
298