CompilationsController   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
eloc 105
c 5
b 1
f 1
dl 0
loc 370
rs 10
wmc 17

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A index() 0 61 5
A store() 0 43 3
A create() 0 10 1
A show() 0 28 2
A statisticsCharts() 0 32 1
A statisticsCounts() 0 32 1
A getStatistics() 0 91 3
1
<?php
2
declare(strict_types=1);
3
4
namespace App\Http\Controllers;
5
6
use App;
7
use App\Http\Requests\StoreCompilationRequest;
8
use App\Models\Compilation;
9
use App\Models\CompilationItem;
10
use App\Models\Location;
11
use App\Models\Question;
12
use App\Models\Section;
13
use App\Models\Ward;
14
use Carbon\Carbon;
15
use Illuminate\Support\Facades\Auth;
16
use Illuminate\Support\Facades\DB;
17
use Yajra\DataTables\DataTables;
18
19
class CompilationsController extends Controller
20
{
21
22
    /**
23
     * @var App\Services\CompilationService
24
     */
25
    private $compilationService;
26
27
    /**
28
     * Create a new controller instance.
29
     *
30
     * @param App\Services\CompilationService $compilationService
31
     */
32
    public function __construct(App\Services\CompilationService $compilationService)
33
    {
34
        // If compilations cannot be currently created,
35
        // users are redirected.
36
        $this->middleware("no_new_compilations")->only("create");
37
38
        $this->compilationService = $compilationService;
39
    }
40
41
    /**
42
     * Display a listing of the compilations.
43
     *
44
     * @return \Illuminate\Http\Response|\Illuminate\View\View
45
     */
46
    public function index()
47
    {
48
49
        $compilationBaseQuery = Compilation
50
            ::with([
51
                // Deleted locations, wards and students are included by default via model relationships
52
                "stageLocation",
53
                "stageWard",
54
                "student",
55
                // Deleted users must be included
56
                // https://stackoverflow.com/questions/33900124/eloquent-withtrashed-for-soft-deletes-on-eager-loading-query-laravel-5-1
57
                "student.user" => function ($query) {
58
                    $query->withTrashed();
59
                }
60
            ]);
61
62
        // Student view of compilation list: no DataTables
63
        if (Auth::user()->cannot("viewAll", Compilation::class)) {
64
            $compilations = $compilationBaseQuery
65
                ->whereHas("student", function ($query) {
66
                    $query->where("id", Auth::user()->student->id);
67
                })
68
                ->get();
69
            return view("compilations.index_student", ["compilations" => $compilations]);
70
        }
71
72
73
        // Non-student view of compilation list: DataTables-based
74
75
        // AJAX data call from DataTables.
76
        if (request()->ajax()) {
77
78
            $compilationQuery = $compilationBaseQuery->select("compilations.*");
79
80
            $toReturn = DataTables::of($compilationQuery)
81
                ->editColumn("created_at", function ($compilation) {
82
                    // Date/times must be formatted to simple dates, in order to allow
83
                    // DataTables plugin (datetimes) correctly format the value.
84
                    return with(new Carbon($compilation->created_at))->format("Y-m-d");
85
                });
86
87
            // @todo refactor by extracting all order parameter usage and sanitization logic
88
            $order = request()->get("order")[0];
89
            $order["column"] = (int)$order["column"];
90
            $order["dir"] = in_array($order["dir"], ["asc", "desc"]) ? $order["dir"] : "asc";
91
            // Since stage weeks are computed on-the-fly,
92
            // sorting by that column requires a different logic.
93
            if (request()->input("columns")[$order["column"]]["data"] === "stage_weeks") {
94
                $toReturn->order(function ($query) use ($order) {
95
                    // @todo check whether this SQL string is compatible with other database engines
96
                    $query->orderByRaw(
97
                        "(strftime('%J', stage_end_date) - strftime('%J', stage_start_date)) " . $order["dir"]
98
                    );
99
                });
100
            }
101
102
            return $toReturn->make(true);
103
104
        }
105
106
        return view("compilations.index");
107
    }
108
109
    /**
110
     * Show the form for creating a new compilation.
111
     *
112
     * @return \Illuminate\View\View
113
     */
114
    public function create()
115
    {
116
117
        // Fetch all active sections,
118
        // with their active questions and active answers.
119
        $sections = Section::with("questions.answers")->get();
120
121
        // @todo sort sections and questions by position
122
123
        return view("compilations.create", ["sections" => $sections]);
124
    }
125
126
    /**
127
     * Store a newly created compilation in storage.
128
     *
129
     * @param StoreCompilationRequest $request
130
     * @return \Illuminate\Http\RedirectResponse
131
     */
132
    public function store(StoreCompilationRequest $request)
133
    {
134
135
        // http://www.easylaravelbook.com/blog/creating-and-validating-a-laravel-5-form-the-definitive-guide/
136
137
        $compilation = new Compilation;
138
139
        DB::transaction(function () use ($request, $compilation) {
140
141
            $compilation->student()->associate(Auth::user()->student);
142
            $compilation->stageLocation()->associate(Location::find($request->input("stage_location_id")));
143
            $compilation->stageWard()->associate(Ward::find($request->input("stage_ward_id")));
144
            $compilation->stage_start_date = $request->input("stage_start_date");
145
            $compilation->stage_end_date = $request->input("stage_end_date");
146
            $compilation->stage_academic_year = $request->input("stage_academic_year");
147
            $compilation->save();
148
149
            collect($request->all())
150
                // Only "qN" parameters are considered, to create compilation items.
151
                ->filter(function ($answers, $questionKey) {
152
                    return preg_match("/^q\d+$/", $questionKey) === 1;
153
                })
154
                // When a question has several answers,
155
                // one compilation item is created for each answer.
156
                // For code shortness, all answers are considered as arrays.
157
                ->map(function ($answers, $questionKey) use ($compilation) {
158
159
                    $answers = (is_array($answers) === true ? $answers : [$answers]);
160
161
                    foreach ($answers as $answer) {
162
                        $item = new CompilationItem;
163
                        $item->answer = $answer;
164
                        $item->question()->associate(Question::find(substr($questionKey, 1)));
165
                        $item->compilation()->associate($compilation);
166
                        $item->save();
167
                    }
168
169
                });
170
171
        });
172
173
        // Redirection does not work from within transaction block.
174
        return \Redirect::route("compilations.show", [$compilation->id]);
175
176
    }
177
178
    /**
179
     * Display the specified compilation.
180
     *
181
     * @param \App\Models\Compilation $compilation
182
     * @return \Illuminate\View\View
183
     */
184
    public function show(Compilation $compilation)
185
    {
186
187
        $compilation->load([
188
            // Deleted locations, wards and students are included by default via model relationships
189
            "stageLocation",
190
            "stageWard",
191
            "student",
192
            // Deleted users must be included
193
            // https://stackoverflow.com/questions/33900124/eloquent-withtrashed-for-soft-deletes-on-eager-loading-query-laravel-5-1
194
            "student.user" => function ($query) {
195
                $query->withTrashed();
196
            },
197
            "items",
198
            "items.aanswer",
199
            // "aanswer" is not a mistake
200
            "items.question",
201
            "items.question.section",
202
        ]);
203
204
        $this->authorize("view", $compilation);
205
206
        // When request contains the "receipt" parameter, the compilation's receipt view is rendered.
207
        if (request()->has("receipt")) {
208
            return view("compilations.receipt", ["compilation" => $compilation]);
209
        }
210
211
        return view("compilations.show", ["compilation" => $compilation]);
212
    }
213
214
    /**
215
     * Display compilation statistics as charts.
216
     *
217
     * @param App\Services\StatisticService $statisticService
218
     *
219
     * @return \Illuminate\View\View
220
     */
221
    public function statisticsCharts(App\Services\StatisticService $statisticService)
222
    {
223
224
        /**
225
         * @var array e.g. Array (
226
         *                   [stage_location_id] => Array (
227
         *                     [14] => 82
228
         *                     [21] => 11
229
         *                     [...]
230
         *                   )
231
         *                   [stage_ward_id] => Array (
232
         *                     [65] => 3
233
         *                     [3] => 7
234
         *                     [...]
235
         *                   )
236
         *                   [...]
237
         *                 )
238
         */
239
        $statistics = $this->getStatistics(request()->all(), $statisticService);
240
241
        $sections = Section::with("questions.answers")->get();
242
        $pseudoSection = new Section();
243
        $pseudoSection->id = 0;
244
        $pseudoSection->title = __("Stage");
245
        $sections->prepend($pseudoSection);
246
247
        return view(
248
            "compilations.statistics_charts",
249
            [
250
                "statistics" => $statistics,
251
                "sections" => $sections,
252
                "filters" => request()->query(),
253
            ]
254
        );
255
    }
256
257
    private function getStatistics(array $requestParameters, App\Services\StatisticService $statisticService): array
258
    {
259
260
        $query = Compilation
261
            ::with([
262
                // Deleted students are included by default via model relationships
263
                "student",
264
                "items",
265
            ]);
266
267
        foreach ($requestParameters as $parameter => $value) {
268
            if (empty($value) === true) {
269
                continue;
270
            }
271
            $this->compilationService->applyQueryFilters($query, $parameter, $value);
272
        }
273
274
        /**
275
         * @var array e.g. Array (
276
         *                   Array (
277
         *                     [stage_location_id] => 14
278
         *                     [stage_ward_id] => 47
279
         *                     [stage_academic_year] => 2017/2018
280
         *                     [stage_weeks] => 10
281
         *                     [student_gender] => female
282
         *                     [student_nationality] => IT
283
         *                     [q1] => 10
284
         *                     [q2] => 86
285
         *                     [q3] => 87
286
         *                     [q4] => 90
287
         *                     [q5] => 95
288
         *                     [q6] => 103
289
         *                     [q7] => 105
290
         *                     [q9] => 109
291
         *                     [q10] => 139
292
         *                     [q11] => 141
293
         *                     [q13] => 147
294
         *                     [q14] => 152
295
         *                     [q15] => 156
296
         *                     [q16] => 160
297
         *                     [q17] => 164
298
         *                     [q18] => 168
299
         *                     [q19] => 172
300
         *                     [q20] => 176
301
         *                     [q21] => 180
302
         *                     [q22] => 184
303
         *                     [q23] => 188
304
         *                     [q24] => 192
305
         *                     [q25] => 196
306
         *                     [q26] => 200
307
         *                     [q27] => 204
308
         *                     [q28] => 208
309
         *                     [q29] => 212
310
         *                     [q30] => 216
311
         *                     [q31] => 220
312
         *                     [q32] => 224
313
         *                     [q33] => 228
314
         *                     [q34] => 232
315
         *                     [q35] => 236
316
         *                   )
317
         *                   [...]
318
         *                 )
319
         */
320
        $formattedCompilations = [];
321
        // $query->get() CANNOT BE USED:
322
        // WITH AROUND 1000 COMPILATIONS, IT CAUSES MEMORY EXHAUSTED ERROR
323
        $query->chunk(100, function ($compilations) use (&$formattedCompilations, $statisticService) {
324
            $formattedCompilations = array_merge(
325
                $formattedCompilations,
326
                $statisticService->formatCompilations($compilations)
327
            );
328
        });
329
330
        /**
331
         * @var array e.g. Array (
332
         *                   [stage_location_id] => Array (
333
         *                     [14] => 82
334
         *                     [21] => 11
335
         *                     [...]
336
         *                   )
337
         *                   [stage_ward_id] => Array (
338
         *                     [65] => 3
339
         *                     [3] => 7
340
         *                     [...]
341
         *                   )
342
         *                   [...]
343
         *                 )
344
         */
345
        $statistics = $statisticService->getStatistics($formattedCompilations);
346
347
        return $statistics;
348
    }
349
350
    /**
351
     * Display compilation statistics as counts.
352
     *
353
     * @param App\Services\StatisticService $statisticService
354
     *
355
     * @return \Illuminate\View\View
356
     */
357
    public function statisticsCounts(App\Services\StatisticService $statisticService)
358
    {
359
360
        /**
361
         * @var array e.g. Array (
362
         *                   [stage_location_id] => Array (
363
         *                     [14] => 82
364
         *                     [21] => 11
365
         *                     [...]
366
         *                   )
367
         *                   [stage_ward_id] => Array (
368
         *                     [65] => 3
369
         *                     [3] => 7
370
         *                     [...]
371
         *                   )
372
         *                   [...]
373
         *                 )
374
         */
375
        $statistics = $this->getStatistics(request()->all(), $statisticService);
376
377
        $sections = Section::with("questions.answers")->get();
378
        $pseudoSection = new Section();
379
        $pseudoSection->id = 0;
380
        $pseudoSection->title = __("Stage");
381
        $sections->prepend($pseudoSection);
382
383
        return view(
384
            "compilations.statistics_counts",
385
            [
386
                "statistics" => $statistics,
387
                "sections" => $sections,
388
                "filters" => request()->query(),
389
            ]
390
        );
391
    }
392
393
}
394