Skip to content

Commit

Permalink
Gradebook: Add min_score validation and highlight unmet scores in gra…
Browse files Browse the repository at this point in the history
…debook - refs #6049
  • Loading branch information
christianbeeznest committed Jan 29, 2025
1 parent f6452e7 commit 4ea03be
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 14 deletions.
62 changes: 48 additions & 14 deletions public/main/gradebook/lib/be/category.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\GradebookCategory;
use Chamilo\CoreBundle\Framework\Container;
use ChamiloSession as Session;
use Chamilo\CoreBundle\Component\Utils\ActionIcon;

Expand Down Expand Up @@ -2002,26 +2003,21 @@ public function lockAllItems($locked)

/**
* Generates a certificate for this user if everything matches.
*
* @param int $user_id
* @param bool $sendNotification
* @param bool $skipGenerationIfExists
*
* @return array
*/
public static function generateUserCertificate(
GradebookCategory $category,
$user_id,
$sendNotification = false,
$skipGenerationIfExists = false
int $user_id,
bool $sendNotification = false,
bool $skipGenerationIfExists = false
) {
$user_id = (int) $user_id;
$categoryId = $category->getId();
$sessionId = $category->getSession() ? $category->getSession()->getId() : 0;
$courseId = $category->getCourse()->getId();
$userFinishedCourse = self::userFinishedCourse($user_id, $category, true);
if (!$userFinishedCourse) {
return false;

// check if all min_score requirements are met
if (!self::userMeetsMinimumScores($user_id, $category)) {
return false; // Do not generate certificate if the user does not meet all min_score criteria
}

$skillToolEnabled = SkillModel::hasAccessToUserSkill(api_get_user_id(), $user_id);
Expand All @@ -2034,7 +2030,7 @@ public static function generateUserCertificate(
$userHasSkills = !empty($userSkills);
}

// Block certification links depending on gradebook configuration (generate certifications)
// If certificate generation is disabled, return only badge link (if available)
if (empty($category->getGenerateCertificates())) {
if ($userHasSkills) {
return [
Expand All @@ -2050,6 +2046,7 @@ public static function generateUserCertificate(
}
$my_certificate = GradebookUtils::get_certificate_by_user_id($categoryId, $user_id);

// If certificate already exists and we should skip regeneration, return false
if ($skipGenerationIfExists && !empty($my_certificate)) {
return false;
}
Expand Down Expand Up @@ -2089,7 +2086,7 @@ public static function generateUserCertificate(

$fileWasGenerated = $certificate_obj->isHtmlFileGenerated();

// Fix when using custom certificate BT#15937
// Fix when using a custom certificate plugin
if ('true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate')) {
$infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
if (!empty($infoCertificate)) {
Expand Down Expand Up @@ -2135,6 +2132,43 @@ public static function generateUserCertificate(

return $html;
}

return false;
}

/**
* Checks whether the user has met the minimum score (`min_score`) in all required evaluations.
*/
public static function userMeetsMinimumScores(int $userId, GradebookCategory $category): bool
{
$evaluations = $category->getEvaluations();

foreach ($evaluations as $evaluation) {
$minScore = $evaluation->getMinScore();
if ($minScore !== null) {
$userScore = self::getUserScoreForEvaluation($userId, $evaluation->getId());
if ($userScore === null || $userScore < $minScore) {
return false; // If at least one evaluation is below `min_score`, return false
}
}
}

return true;
}

/**
* Retrieves the score of a user for a specific evaluation using the GradebookResult repository.
*/
public static function getUserScoreForEvaluation(int $userId, int $evaluationId): ?float
{
$gradebookResultRepo = Container::getGradebookResultRepository();

$gradebookResult = $gradebookResultRepo->findOneBy([
'user' => $userId,
'evaluation' => $evaluationId,
]);

return $gradebookResult ? $gradebookResult->getScore() : null;
}

/**
Expand Down
12 changes: 12 additions & 0 deletions public/main/gradebook/lib/gradebook_data_generator.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,13 @@ public function build_result_column(
$scoreDisplay = ScoreDisplay::instance();
$score = $item->calc_score($userId);
$model = ExerciseLib::getCourseScoreModel();

// Get min_score from entity (only if available)
$minScore = null;
if (isset($item->entity) && method_exists($item->entity, 'getMinScore')) {
$minScore = $item->entity->getMinScore();
}

if (!empty($score)) {
switch ($item->get_item_type()) {
// category
Expand Down Expand Up @@ -799,6 +806,11 @@ public function build_result_column(
);
}

// If minScore exists and user score is lower, mark in red
if (!is_null($minScore) && $score[0] < $minScore) {
$display = "<span class='text-danger font-bold'>$display</span>";
}

return [
'display' => $display,
'score' => $score,
Expand Down
15 changes: 15 additions & 0 deletions src/CoreBundle/Entity/GradebookEvaluation.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class GradebookEvaluation
#[ORM\Column(name: 'user_score_list', type: 'array', nullable: true)]
protected ?array $userScoreList = null;

#[ORM\Column(name: 'min_score', type: 'float', precision: 6, scale: 2, nullable: true)]
protected ?float $minScore = null;

public function __construct()
{
$this->locked = 0;
Expand Down Expand Up @@ -308,4 +311,16 @@ public function setCategory(GradebookCategory $category): self

return $this;
}

public function getMinScore(): ?float
{
return $this->minScore;
}

public function setMinScore(?float $minScore): self
{
$this->minScore = $minScore;

return $this;
}
}
15 changes: 15 additions & 0 deletions src/CoreBundle/Entity/GradebookLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class GradebookLink
#[ORM\Column(name: 'user_score_list', type: 'array', nullable: true)]
protected ?array $userScoreList = null;

#[ORM\Column(name: 'min_score', type: 'float', precision: 6, scale: 2, nullable: true)]
protected ?float $minScore = null;

public function __construct()
{
$this->locked = 0;
Expand Down Expand Up @@ -260,4 +263,16 @@ public function setCategory(GradebookCategory $category): self

return $this;
}

public function getMinScore(): ?float
{
return $this->minScore;
}

public function setMinScore(?float $minScore): self
{
$this->minScore = $minScore;

return $this;
}
}
6 changes: 6 additions & 0 deletions src/CoreBundle/Framework/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
use Chamilo\CoreBundle\Repository\GradeBookCategoryRepository;
use Chamilo\CoreBundle\Repository\GradebookCertificateRepository;
use Chamilo\CoreBundle\Repository\GradebookResultRepository;
use Chamilo\CoreBundle\Repository\LanguageRepository;
use Chamilo\CoreBundle\Repository\LegalRepository;
use Chamilo\CoreBundle\Repository\MessageRepository;
Expand Down Expand Up @@ -363,6 +364,11 @@ public static function getGradeBookCertificateRepository(): GradebookCertificate
return self::$container->get(GradebookCertificateRepository::class);
}

public static function getGradebookResultRepository(): GradebookResultRepository
{
return self::$container->get(GradebookResultRepository::class);
}

public static function getGroupRepository(): CGroupRepository
{
return self::$container->get(CGroupRepository::class);
Expand Down
37 changes: 37 additions & 0 deletions src/CoreBundle/Migrations/Schema/V200/Version20250120103800.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

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

namespace Chamilo\CoreBundle\Migrations\Schema\V200;

use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Doctrine\DBAL\Schema\Schema;

final class Version20250120103800 extends AbstractMigrationChamilo
{
public function getDescription(): string
{
return 'Add min_score column to gradebook_evaluation and gradebook_link tables';
}

public function up(Schema $schema): void
{
$this->addSql('
ALTER TABLE gradebook_evaluation
ADD COLUMN min_score FLOAT DEFAULT NULL
');

$this->addSql('
ALTER TABLE gradebook_link
ADD COLUMN min_score FLOAT DEFAULT NULL
');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE gradebook_evaluation DROP COLUMN min_score');
$this->addSql('ALTER TABLE gradebook_link DROP COLUMN min_score');
}
}
19 changes: 19 additions & 0 deletions src/CoreBundle/Repository/GradebookResultRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

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

namespace Chamilo\CoreBundle\Repository;

use Chamilo\CoreBundle\Entity\GradebookResult;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class GradebookResultRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, GradebookResult::class);
}
}

0 comments on commit 4ea03be

Please sign in to comment.