Passed
Push — master ( 719760...59ab2e )
by Angel Fernando Quiroz
18:35
created

isAllowedToSeeResults()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 6
c 1
b 0
f 0
nc 5
nop 0
dl 0
loc 9
rs 9.6111
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\State;
8
9
use ApiPlatform\Metadata\Operation;
10
use ApiPlatform\State\ProviderInterface;
11
use Chamilo\CoreBundle\ApiResource\CategorizedExerciseResult;
12
use Chamilo\CoreBundle\Entity\TrackEExercise;
13
use Chamilo\CoreBundle\Security\Authorization\Voter\TrackEExerciseVoter;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Event;
16
use Exception;
17
use Exercise;
18
use ExerciseLib;
19
use Question;
20
use QuestionOptionsEvaluationPlugin;
21
use Symfony\Component\HttpFoundation\RequestStack;
22
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
23
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
24
use TestCategory;
25
26
use function count;
27
28
/**
29
 * @template-implements ProviderInterface<CategorizedExerciseResult>
30
 */
31
class CategorizedExerciseResultStateProvider implements ProviderInterface
32
{
33
    public function __construct(
34
        private readonly EntityManagerInterface $entityManager,
35
        private readonly AuthorizationCheckerInterface $security,
36
        private readonly RequestStack $requestStack
37
    ) {}
38
39
    /**
40
     * @throws Exception
41
     */
42
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): null|array|object
43
    {
44
        $trackExercise = $this->entityManager->find(TrackEExercise::class, $uriVariables['exeId']);
45
46
        if (!$trackExercise) {
47
            return null;
48
        }
49
50
        if (!$this->security->isGranted(TrackEExerciseVoter::VIEW, $trackExercise)) {
51
            throw new Exception('Not allowed');
52
        }
53
54
        $sessionHandler = $this->requestStack->getCurrentRequest()->getSession();
55
        $sessionHandler->set('_course', api_get_course_info_by_id($trackExercise->getCourse()->getId()));
56
57
        $objExercise = new Exercise();
58
        $objExercise->read($trackExercise->getQuiz()->getIid());
59
60
        ob_start();
61
62
        $categoryList = $this->displayQuestionListByAttempt(
63
            $objExercise,
64
            $trackExercise
65
        );
66
67
        ob_end_clean();
68
69
        $stats = self::getStatsTableByAttempt($objExercise, $categoryList);
70
71
        return new CategorizedExerciseResult($trackExercise, $stats);
72
    }
73
74
    /**
75
     * @throws Exception
76
     */
77
    private function displayQuestionListByAttempt(
78
        Exercise $objExercise,
79
        TrackEExercise $exerciseTracking
80
    ): array {
81
        $courseId = api_get_course_int_id();
82
        $sessionId = api_get_session_id();
83
84
        $question_list = explode(',', $exerciseTracking->getDataTracking());
85
        $question_list = array_map('intval', $question_list);
86
87
        if ($objExercise->getResultAccess()) {
88
            $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id(
89
                $exerciseTracking->getExeId()
90
            );
91
92
            if (false === $objExercise->hasResultsAccess($exercise_stat_info)) {
93
                throw new Exception(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess());
94
            }
95
        }
96
97
        $total_score = $total_weight = 0;
98
99
        // Hide results
100
        $show_results = false;
101
        $show_only_score = false;
102
103
        if (\in_array(
104
            $objExercise->results_disabled,
105
            [
106
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
107
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
108
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
109
            ],
110
            true
111
        )) {
112
            $show_results = true;
113
        }
114
115
        if (\in_array(
116
            $objExercise->results_disabled,
117
            [
118
                RESULT_DISABLE_SHOW_SCORE_ONLY,
119
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
120
                RESULT_DISABLE_RANKING,
121
            ],
122
            true
123
        )) {
124
            $show_only_score = true;
125
        }
126
127
        // Not display expected answer, but score, and feedback
128
        if (RESULT_DISABLE_SHOW_SCORE_ONLY === $objExercise->results_disabled
129
            && EXERCISE_FEEDBACK_TYPE_END === $objExercise->getFeedbackType()
130
        ) {
131
            $show_results = true;
132
            $show_only_score = false;
133
        }
134
135
        $showTotalScoreAndUserChoicesInLastAttempt = true;
136
        $showTotalScore = true;
137
138
        if (\in_array(
139
            $objExercise->results_disabled,
140
            [
141
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
142
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
143
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
144
            ],
145
            true
146
        )) {
147
            $show_only_score = true;
148
            $show_results = true;
149
            $numberAttempts = 0;
150
151
            if ($objExercise->attempts > 0) {
152
                $attempts = Event::getExerciseResultsByUser(
0 ignored issues
show
Bug introduced by
The method getExerciseResultsByUser() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

152
                /** @scrutinizer ignore-call */ 
153
                $attempts = Event::getExerciseResultsByUser(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
153
                    api_get_user_id(),
154
                    $objExercise->id,
155
                    $courseId,
156
                    $sessionId,
157
                    $exerciseTracking->getOrigLpId(),
158
                    $exerciseTracking->getOrigLpItemId(),
159
                    'desc'
160
                );
161
162
                if ($attempts) {
163
                    $numberAttempts = \count($attempts);
164
                }
165
166
                $showTotalScore = false;
167
168
                if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT === $objExercise->results_disabled) {
169
                    $showTotalScore = true;
170
                }
171
172
                $showTotalScoreAndUserChoicesInLastAttempt = false;
173
174
                if ($numberAttempts >= $objExercise->attempts) {
175
                    $showTotalScore = true;
176
                    $show_only_score = false;
177
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
178
                }
179
180
                if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK === $objExercise->results_disabled) {
181
                    $showTotalScore = true;
182
                    $show_only_score = false;
183
                    $showTotalScoreAndUserChoicesInLastAttempt = false;
184
185
                    if ($numberAttempts >= $objExercise->attempts) {
186
                        $showTotalScoreAndUserChoicesInLastAttempt = true;
187
                    }
188
189
                    // Check if the current attempt is the last.
190
                    if (!empty($attempts)) {
191
                        $showTotalScoreAndUserChoicesInLastAttempt = false;
192
                        $position = 1;
193
194
                        foreach ($attempts as $attempt) {
195
                            if ($exerciseTracking->getExeId() === $attempt['exe_id']) {
196
                                break;
197
                            }
198
199
                            $position++;
200
                        }
201
202
                        if ($position === $objExercise->attempts) {
203
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
204
                        }
205
                    }
206
                }
207
            }
208
209
            if (RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK ===
210
                $objExercise->results_disabled
211
            ) {
212
                $show_only_score = false;
213
                $showTotalScore = false;
214
                if ($numberAttempts >= $objExercise->attempts) {
215
                    $showTotalScore = true;
216
                }
217
            }
218
        }
219
220
        $category_list = [
221
            'none' => [
222
                'score' => 0,
223
                'total' => 0,
224
            ],
225
        ];
226
        $exerciseResultCoordinates = [];
227
228
        $result = [];
229
        // Loop over all question to show results for each of them, one by one
230
        foreach ($question_list as $questionId) {
231
            // Creates a temporary Question object
232
            $objQuestionTmp = Question::read($questionId, $objExercise->course);
233
234
            // We're inside *one* question. Go through each possible answer for this question
235
            $result = $objExercise->manage_answer(
236
                $exerciseTracking->getExeId(),
237
                $questionId,
238
                null,
239
                'exercise_result',
240
                $exerciseResultCoordinates,
241
                false,
242
                true,
243
                $show_results,
244
                $objExercise->selectPropagateNeg(),
245
                [],
246
                $showTotalScoreAndUserChoicesInLastAttempt
247
            );
248
249
            if (false === $result) {
250
                continue;
251
            }
252
253
            $total_score += $result['score'];
254
            $total_weight += $result['weight'];
255
256
            $my_total_score = $result['score'];
257
            $my_total_weight = $result['weight'];
258
            $scorePassed = ExerciseLib::scorePassed($my_total_score, $my_total_weight);
259
260
            // Category report
261
            $category_was_added_for_this_test = false;
262
            if (!empty($objQuestionTmp->category)) {
263
                if (!isset($category_list[$objQuestionTmp->category])) {
264
                    $category_list[$objQuestionTmp->category] = [
265
                        'score' => 0,
266
                        'total' => 0,
267
                        'total_questions' => 0,
268
                        'passed' => 0,
269
                        'wrong' => 0,
270
                        'no_answer' => 0,
271
                    ];
272
                }
273
274
                $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
275
                $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
276
277
                if ($scorePassed) {
278
                    // Only count passed if score is not empty
279
                    if (!empty($my_total_score)) {
280
                        $category_list[$objQuestionTmp->category]['passed']++;
281
                    }
282
                } elseif ($result['user_answered']) {
283
                    $category_list[$objQuestionTmp->category]['wrong']++;
284
                } else {
285
                    $category_list[$objQuestionTmp->category]['no_answer']++;
286
                }
287
288
                $category_list[$objQuestionTmp->category]['total_questions']++;
289
                $category_was_added_for_this_test = true;
290
            }
291
292
            if (!empty($objQuestionTmp->category_list)) {
293
                foreach ($objQuestionTmp->category_list as $category_id) {
294
                    $category_list[$category_id]['score'] += $my_total_score;
295
                    $category_list[$category_id]['total'] += $my_total_weight;
296
                    $category_was_added_for_this_test = true;
297
                }
298
            }
299
300
            // No category for this question!
301
            if (!$category_was_added_for_this_test) {
302
                $category_list['none']['score'] += $my_total_score;
303
                $category_list['none']['total'] += $my_total_weight;
304
            }
305
        }
306
307
        if (($show_results || $show_only_score) && $showTotalScore) {
308
            if ($result
309
                && isset($result['answer_type'])
310
                && MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY !== $result['answer_type']
311
            ) {
312
                $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
313
                if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
314
                    $formula = $pluginEvaluation->getFormulaForExercise($objExercise->getId());
315
316
                    if (!empty($formula)) {
317
                        $total_score = $pluginEvaluation->getResultWithFormula(
318
                            $exerciseTracking->getExeId(),
319
                            $formula
320
                        );
321
                        $total_weight = $pluginEvaluation->getMaxScore();
322
                    }
323
                }
324
            }
325
        }
326
327
        if ($this->isAllowedToSeeResults()) {
328
            $show_results = true;
329
        }
330
331
        if (!$show_results && !$show_only_score && RESULT_DISABLE_RADAR !== $objExercise->results_disabled) {
332
            throw new AccessDeniedException();
333
        }
334
335
        // Adding total
336
        $category_list['total'] = [
337
            'score' => $total_score,
338
            'total' => $total_weight,
339
        ];
340
341
        return $category_list;
342
    }
343
344
    private static function getStatsTableByAttempt(Exercise $exercise, array $category_list = []): array
345
    {
346
        if (empty($category_list)) {
347
            return [];
348
        }
349
350
        $hide = (int) $exercise->getPageConfigurationAttribute('hide_category_table');
351
352
        if (1 === $hide) {
353
            return [];
354
        }
355
356
        $categoryNameList = TestCategory::getListOfCategoriesNameForTest($exercise->iId);
357
358
        if (empty($categoryNameList)) {
359
            return [];
360
        }
361
362
        $labelsWithId = array_column($categoryNameList, 'title', 'id');
363
364
        asort($labelsWithId);
365
366
        $stats = [];
367
368
        foreach ($labelsWithId as $category_id => $title) {
369
            if (!isset($category_list[$category_id])) {
370
                continue;
371
            }
372
373
            $absolute = ExerciseLib::show_score(
374
                $category_list[$category_id]['score'],
375
                $category_list[$category_id]['total'],
376
                false
377
            );
378
            $relative = ExerciseLib::show_score(
379
                $category_list[$category_id]['score'],
380
                $category_list[$category_id]['total'],
381
                true,
382
                false,
383
                true
384
            );
385
386
            $stats[] = [
387
                'title' => $title,
388
                'absolute' => strip_tags($absolute),
389
                'relative' => strip_tags($relative),
390
            ];
391
        }
392
393
        if (isset($category_list['none']) && $category_list['none']['score'] > 0) {
394
            $absolute = ExerciseLib::show_score(
395
                $category_list['none']['score'],
396
                $category_list['none']['total'],
397
                false
398
            );
399
            $relative = ExerciseLib::show_score(
400
                $category_list['none']['score'],
401
                $category_list['none']['total'],
402
                true,
403
                false,
404
                true
405
            );
406
407
            $stats[] = [
408
                'title' => get_lang('None'),
409
                'absolute' => strip_tags($absolute),
410
                'relative' => strip_tags($relative),
411
            ];
412
        }
413
414
        $absolute = ExerciseLib::show_score(
415
            $category_list['total']['score'],
416
            $category_list['total']['total'],
417
            false
418
        );
419
        $relative = ExerciseLib::show_score(
420
            $category_list['total']['score'],
421
            $category_list['total']['total'],
422
            true,
423
            false,
424
            true
425
        );
426
427
        $stats[] = [
428
            'title' => get_lang('Total'),
429
            'absolute' => strip_tags($absolute),
430
            'relative' => strip_tags($relative),
431
        ];
432
433
        return $stats;
434
    }
435
436
    private function isAllowedToSeeResults(): bool
437
    {
438
        $isStudentBoss = $this->security->isGranted('ROLE_STUDENT_BOSS');
439
        $isHRM = $this->security->isGranted('ROLE_RRHH');
440
        $isSessionAdmin = $this->security->isGranted('ROLE_SESSION_MANAGER');
441
        $isCourseTutor = $this->security->isGranted('ROLE_CURRENT_COURSE_SESSION_TEACHER');
442
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
443
444
        return $isAllowedToEdit || $isCourseTutor || $isSessionAdmin || $isHRM || $isStudentBoss;
445
    }
446
}
447