API: Exercise: Add endpoint to get results by category - refs BT#19742

pull/4913/head
Angel Fernando Quiroz Campos 2 years ago
parent 7f94b09c56
commit e28a71041c
  1. 2
      config/packages/api_platform.yaml
  2. 43
      src/CoreBundle/ApiResource/CategorizedExerciseResult.php
  3. 2
      src/CoreBundle/Resources/config/services.yml
  4. 419
      src/CoreBundle/State/CategorizedExerciseResultStateProvider.php

@ -2,7 +2,7 @@ api_platform:
title: 'Chamilo Api'
version: '1.0.0'
mapping:
paths: ['%kernel.project_dir%/src/CoreBundle/Entity', '%kernel.project_dir%/src/CourseBundle/Entity']
paths: ['%kernel.project_dir%/src/CoreBundle/Entity', '%kernel.project_dir%/src/CourseBundle/Entity', '%kernel.project_dir%/src/CoreBundle/ApiResource',]
patch_formats:
json: ['application/merge-patch+json']
swagger:

@ -0,0 +1,43 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\ApiResource;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use Chamilo\CoreBundle\Entity\TrackEExercise;
use Chamilo\CoreBundle\State\CategorizedExerciseResultStateProvider;
use Symfony\Component\Serializer\Annotation\Ignore;
#[ApiResource(
description: 'Exercise results by categories',
operations: [
new Get(provider: CategorizedExerciseResultStateProvider::class),
],
)]
class CategorizedExerciseResult
{
#[Ignore]
public TrackEExercise $exe;
/**
* @var array<int, array<string, string>>
*/
public array $catories;
public function __construct(TrackEExercise $exe, array $catories)
{
$this->exe = $exe;
$this->catories = $catories;
}
#[ApiProperty(readable: false, identifier: true)]
public function getExeId(): string
{
return (string) $this->exe->getExeId();
}
}

@ -12,7 +12,7 @@ services:
Chamilo\CoreBundle\:
resource: '../../*'
exclude: '../../{Repository,Component,DependencyInjection,EventListener,Form/DataTransformer/ResourceToIdentifierTransformer.php,Framework,Migrations,Settings,ChamiloCoreBundle.php,Entity}'
exclude: '../../{Repository,Component,DependencyInjection,EventListener,Form/DataTransformer/ResourceToIdentifierTransformer.php,Framework,Migrations,Settings,ChamiloCoreBundle.php,Entity,ApiResource}'
public: true
Doctrine\Migrations\Version\DbalMigrationFactory: ~

@ -0,0 +1,419 @@
<?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\CourseVoter;
use Chamilo\CoreBundle\Security\Authorization\Voter\SessionVoter;
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\Security\Core\Authorization\AuthorizationCheckerInterface;
use TestCategory;
use function count;
use function in_array;
class CategorizedExerciseResultStateProvider implements ProviderInterface
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly AuthorizationCheckerInterface $security
) {
}
/**
* @throws Exception
*/
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|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');
}
$objExercise = new Exercise();
$objExercise->read($trackExercise->getQuiz()->getIid());
ob_start();
$categoryList = self::displayQuestionListByAttempt(
$objExercise,
$trackExercise
);
ob_end_clean();
$stats = self::getStatsTableByAttempt($objExercise, $categoryList);
return new CategorizedExerciseResult($trackExercise, $stats);
}
/**
* @throws Exception
*/
private static 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 (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 (!$show_results && !$show_only_score && RESULT_DISABLE_RADAR !== $objExercise->results_disabled) {
throw new Exception(get_lang('Not allowed'));
}
// Adding total
$category_list['total'] = [
'score' => $total_score,
'total' => $total_weight,
];
return $category_list;
}
private static function getStatsTableByAttempt(Exercise $exercise, array $category_list = []): array
{
$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 ($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;
}
}
Loading…
Cancel
Save