chamilo/chamilo-lms

View on GitHub
src/CoreBundle/State/CategorizedExerciseResultStateProvider.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/* For licensing terms, see /license.txt */

declare(strict_types=1);

namespace Chamilo\CoreBundle\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Chamilo\CoreBundle\ApiResource\CategorizedExerciseResult;
use Chamilo\CoreBundle\Entity\TrackEExercise;
use Chamilo\CoreBundle\Security\Authorization\Voter\TrackEExerciseVoter;
use Doctrine\ORM\EntityManagerInterface;
use Event;
use Exception;
use Exercise;
use ExerciseLib;
use Question;
use QuestionOptionsEvaluationPlugin;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use TestCategory;

use function count;

/**
 * @template-implements ProviderInterface<CategorizedExerciseResult>
 */
class CategorizedExerciseResultStateProvider implements ProviderInterface
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly AuthorizationCheckerInterface $security,
        private readonly RequestStack $requestStack
    ) {}

    /**
     * @throws Exception
     */
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|object|null
    {
        $trackExercise = $this->entityManager->find(TrackEExercise::class, $uriVariables['exeId']);

        if (!$trackExercise) {
            return null;
        }

        if (!$this->security->isGranted(TrackEExerciseVoter::VIEW, $trackExercise)) {
            throw new Exception('Not allowed');
        }

        $sessionHandler = $this->requestStack->getCurrentRequest()->getSession();
        $sessionHandler->set('_course', api_get_course_info_by_id($trackExercise->getCourse()->getId()));

        $objExercise = new Exercise();
        $objExercise->read($trackExercise->getQuiz()->getIid());

        ob_start();

        $categoryList = $this->displayQuestionListByAttempt(
            $objExercise,
            $trackExercise
        );

        ob_end_clean();

        $stats = self::getStatsTableByAttempt($objExercise, $categoryList);

        return new CategorizedExerciseResult($trackExercise, $stats);
    }

    /**
     * @throws Exception
     */
    private function displayQuestionListByAttempt(
        Exercise $objExercise,
        TrackEExercise $exerciseTracking
    ): array {
        $courseId = api_get_course_int_id();
        $sessionId = api_get_session_id();

        $question_list = explode(',', $exerciseTracking->getDataTracking());
        $question_list = array_map('intval', $question_list);

        if ($objExercise->getResultAccess()) {
            $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id(
                $exerciseTracking->getExeId()
            );

            if (false === $objExercise->hasResultsAccess($exercise_stat_info)) {
                throw new Exception(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess());
            }
        }

        $total_score = $total_weight = 0;

        // Hide results
        $show_results = false;
        $show_only_score = false;

        if (\in_array(
            $objExercise->results_disabled,
            [
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
            ],
            true
        )) {
            $show_results = true;
        }

        if (\in_array(
            $objExercise->results_disabled,
            [
                RESULT_DISABLE_SHOW_SCORE_ONLY,
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
                RESULT_DISABLE_RANKING,
            ],
            true
        )) {
            $show_only_score = true;
        }

        // Not display expected answer, but score, and feedback
        if (RESULT_DISABLE_SHOW_SCORE_ONLY === $objExercise->results_disabled
            && EXERCISE_FEEDBACK_TYPE_END === $objExercise->getFeedbackType()
        ) {
            $show_results = true;
            $show_only_score = false;
        }

        $showTotalScoreAndUserChoicesInLastAttempt = true;
        $showTotalScore = true;

        if (\in_array(
            $objExercise->results_disabled,
            [
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
            ],
            true
        )) {
            $show_only_score = true;
            $show_results = true;
            $numberAttempts = 0;

            if ($objExercise->attempts > 0) {
                $attempts = Event::getExerciseResultsByUser(
                    api_get_user_id(),
                    $objExercise->id,
                    $courseId,
                    $sessionId,
                    $exerciseTracking->getOrigLpId(),
                    $exerciseTracking->getOrigLpItemId(),
                    'desc'
                );

                if ($attempts) {
                    $numberAttempts = \count($attempts);
                }

                $showTotalScore = false;

                if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT === $objExercise->results_disabled) {
                    $showTotalScore = true;
                }

                $showTotalScoreAndUserChoicesInLastAttempt = false;

                if ($numberAttempts >= $objExercise->attempts) {
                    $showTotalScore = true;
                    $show_only_score = false;
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
                }

                if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK === $objExercise->results_disabled) {
                    $showTotalScore = true;
                    $show_only_score = false;
                    $showTotalScoreAndUserChoicesInLastAttempt = false;

                    if ($numberAttempts >= $objExercise->attempts) {
                        $showTotalScoreAndUserChoicesInLastAttempt = true;
                    }

                    // Check if the current attempt is the last.
                    if (!empty($attempts)) {
                        $showTotalScoreAndUserChoicesInLastAttempt = false;
                        $position = 1;

                        foreach ($attempts as $attempt) {
                            if ($exerciseTracking->getExeId() === $attempt['exe_id']) {
                                break;
                            }

                            $position++;
                        }

                        if ($position === $objExercise->attempts) {
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
                        }
                    }
                }
            }

            if (RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK ===
                $objExercise->results_disabled
            ) {
                $show_only_score = false;
                $showTotalScore = false;
                if ($numberAttempts >= $objExercise->attempts) {
                    $showTotalScore = true;
                }
            }
        }

        $category_list = [
            'none' => [
                'score' => 0,
                'total' => 0,
            ],
        ];
        $exerciseResultCoordinates = [];

        $result = [];
        // Loop over all question to show results for each of them, one by one
        foreach ($question_list as $questionId) {
            // Creates a temporary Question object
            $objQuestionTmp = Question::read($questionId, $objExercise->course);

            // We're inside *one* question. Go through each possible answer for this question
            $result = $objExercise->manage_answer(
                $exerciseTracking->getExeId(),
                $questionId,
                null,
                'exercise_result',
                $exerciseResultCoordinates,
                false,
                true,
                $show_results,
                $objExercise->selectPropagateNeg(),
                [],
                $showTotalScoreAndUserChoicesInLastAttempt
            );

            if (false === $result) {
                continue;
            }

            $total_score += $result['score'];
            $total_weight += $result['weight'];

            $my_total_score = $result['score'];
            $my_total_weight = $result['weight'];
            $scorePassed = ExerciseLib::scorePassed($my_total_score, $my_total_weight);

            // Category report
            $category_was_added_for_this_test = false;
            if (!empty($objQuestionTmp->category)) {
                if (!isset($category_list[$objQuestionTmp->category])) {
                    $category_list[$objQuestionTmp->category] = [
                        'score' => 0,
                        'total' => 0,
                        'total_questions' => 0,
                        'passed' => 0,
                        'wrong' => 0,
                        'no_answer' => 0,
                    ];
                }

                $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
                $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;

                if ($scorePassed) {
                    // Only count passed if score is not empty
                    if (!empty($my_total_score)) {
                        $category_list[$objQuestionTmp->category]['passed']++;
                    }
                } elseif ($result['user_answered']) {
                    $category_list[$objQuestionTmp->category]['wrong']++;
                } else {
                    $category_list[$objQuestionTmp->category]['no_answer']++;
                }

                $category_list[$objQuestionTmp->category]['total_questions']++;
                $category_was_added_for_this_test = true;
            }

            if (!empty($objQuestionTmp->category_list)) {
                foreach ($objQuestionTmp->category_list as $category_id) {
                    $category_list[$category_id]['score'] += $my_total_score;
                    $category_list[$category_id]['total'] += $my_total_weight;
                    $category_was_added_for_this_test = true;
                }
            }

            // No category for this question!
            if (!$category_was_added_for_this_test) {
                $category_list['none']['score'] += $my_total_score;
                $category_list['none']['total'] += $my_total_weight;
            }
        }

        if (($show_results || $show_only_score) && $showTotalScore) {
            if ($result
                && isset($result['answer_type'])
                && MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY !== $result['answer_type']
            ) {
                $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
                if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
                    $formula = $pluginEvaluation->getFormulaForExercise($objExercise->getId());

                    if (!empty($formula)) {
                        $total_score = $pluginEvaluation->getResultWithFormula(
                            $exerciseTracking->getExeId(),
                            $formula
                        );
                        $total_weight = $pluginEvaluation->getMaxScore();
                    }
                }
            }
        }

        if ($this->isAllowedToSeeResults()) {
            $show_results = true;
        }

        if (!$show_results && !$show_only_score && RESULT_DISABLE_RADAR !== $objExercise->results_disabled) {
            throw new AccessDeniedException();
        }

        // Adding total
        $category_list['total'] = [
            'score' => $total_score,
            'total' => $total_weight,
        ];

        return $category_list;
    }

    private static function getStatsTableByAttempt(Exercise $exercise, array $category_list = []): array
    {
        if (empty($category_list)) {
            return [];
        }

        $hide = (int) $exercise->getPageConfigurationAttribute('hide_category_table');

        if (1 === $hide) {
            return [];
        }

        $categoryNameList = TestCategory::getListOfCategoriesNameForTest($exercise->iId);

        if (empty($categoryNameList)) {
            return [];
        }

        $labelsWithId = array_column($categoryNameList, 'title', 'id');

        asort($labelsWithId);

        $stats = [];

        foreach ($labelsWithId as $category_id => $title) {
            if (!isset($category_list[$category_id])) {
                continue;
            }

            $absolute = ExerciseLib::show_score(
                $category_list[$category_id]['score'],
                $category_list[$category_id]['total'],
                false
            );
            $relative = ExerciseLib::show_score(
                $category_list[$category_id]['score'],
                $category_list[$category_id]['total'],
                true,
                false,
                true
            );

            $stats[] = [
                'title' => $title,
                'absolute' => strip_tags($absolute),
                'relative' => strip_tags($relative),
            ];
        }

        if (isset($category_list['none']) && $category_list['none']['score'] > 0) {
            $absolute = ExerciseLib::show_score(
                $category_list['none']['score'],
                $category_list['none']['total'],
                false
            );
            $relative = ExerciseLib::show_score(
                $category_list['none']['score'],
                $category_list['none']['total'],
                true,
                false,
                true
            );

            $stats[] = [
                'title' => get_lang('None'),
                'absolute' => strip_tags($absolute),
                'relative' => strip_tags($relative),
            ];
        }

        $absolute = ExerciseLib::show_score(
            $category_list['total']['score'],
            $category_list['total']['total'],
            false
        );
        $relative = ExerciseLib::show_score(
            $category_list['total']['score'],
            $category_list['total']['total'],
            true,
            false,
            true
        );

        $stats[] = [
            'title' => get_lang('Total'),
            'absolute' => strip_tags($absolute),
            'relative' => strip_tags($relative),
        ];

        return $stats;
    }

    private function isAllowedToSeeResults(): bool
    {
        $isStudentBoss = $this->security->isGranted('ROLE_STUDENT_BOSS');
        $isHRM = $this->security->isGranted('ROLE_HR');
        $isSessionAdmin = $this->security->isGranted('ROLE_SESSION_MANAGER');
        $isCourseTutor = $this->security->isGranted('ROLE_CURRENT_COURSE_SESSION_TEACHER');
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);

        return $isAllowedToEdit || $isCourseTutor || $isSessionAdmin || $isHRM || $isStudentBoss;
    }
}