Skip to content

Pass/fail conditional session course subscription - refs BT#22403 #6092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions main/exercise/exercise.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Exercise
public $review_answers;
public $randomByCat;
public $text_when_finished;
public $text_when_finished_failure;
public $display_category_name;
public $pass_percentage;
public $edit_exercise_in_lp = false;
Expand Down Expand Up @@ -127,6 +128,7 @@ public function __construct($courseId = 0)
$this->review_answers = false;
$this->randomByCat = 0;
$this->text_when_finished = '';
$this->text_when_finished_failure = '';
$this->display_category_name = 0;
$this->pass_percentage = 0;
$this->modelType = 1;
Expand Down Expand Up @@ -204,6 +206,7 @@ public function read($id, $parseQuestionList = true)
$this->saveCorrectAnswers = $object->save_correct_answers;
$this->randomByCat = $object->random_by_category;
$this->text_when_finished = $object->text_when_finished;
$this->text_when_finished_failure = $object->text_when_finished_failure;
$this->display_category_name = $object->display_category_name;
$this->pass_percentage = $object->pass_percentage;
$this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
Expand Down Expand Up @@ -456,6 +459,28 @@ public function updateTextWhenFinished($text)
$this->text_when_finished = $text;
}

/**
* Get the text to display when the user has failed the test.
*
* @return string html text : the text to display ay the end of the test
*/
public function getTextWhenFinishedFailure(): string
{
if (empty($this->text_when_finished_failure)) {
return '';
}

return $this->text_when_finished_failure;
}

/**
* Set the text to display when the user has succeeded in the test.
*/
public function setTextWhenFinishedFailure(string $text): void
{
$this->text_when_finished_failure = $text;
}

/**
* return 1 or 2 if randomByCat.
*
Expand Down Expand Up @@ -1599,6 +1624,7 @@ public function save($type_e = '')
$review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
$randomByCat = (int) $this->randomByCat;
$text_when_finished = $this->text_when_finished;
$text_when_finished_failure = $this->text_when_finished_failure;
$display_category_name = (int) $this->display_category_name;
$pass_percentage = (int) $this->pass_percentage;
$session_id = $this->sessionId;
Expand Down Expand Up @@ -1654,6 +1680,10 @@ public function save($type_e = '')
'hide_question_title' => $this->getHideQuestionTitle(),
];

if (true === api_get_configuration_value('exercise_text_when_finished_failure')) {
$paramsExtra['text_when_finished_failure'] = $text_when_finished_failure;
}

$allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
if ($allow === true) {
$paramsExtra['show_previous_button'] = $this->showPreviousButton();
Expand Down Expand Up @@ -1758,6 +1788,10 @@ public function save($type_e = '')
'hide_question_title' => $this->getHideQuestionTitle(),
];

if (true === api_get_configuration_value('exercise_text_when_finished_failure')) {
$params['text_when_finished_failure'] = $text_when_finished_failure;
}

$allow = api_get_configuration_value('allow_exercise_categories');
if (true === $allow) {
if (!empty($this->getExerciseCategoryId())) {
Expand Down Expand Up @@ -2505,6 +2539,16 @@ public function createForm($form, $type = 'full')
$editor_config
);

if (true === api_get_configuration_value('exercise_text_when_finished_failure')) {
$form->addHtmlEditor(
'text_when_finished_failure',
get_lang('TextAppearingAtTheEndOfTheTestWhenTheUserHasFailed'),
false,
false,
$editor_config
);
}

$allow = api_get_configuration_value('allow_notification_setting_per_exercise');
if ($allow === true) {
$settings = ExerciseLib::getNotificationSettings();
Expand Down Expand Up @@ -2572,6 +2616,7 @@ public function createForm($form, $type = 'full')
'notifications',
'remedialcourselist',
'advancedcourselist',
'subscribe_session_when_finished_failure',
], //exclude
false, // filter
false, // tag as select
Expand Down Expand Up @@ -2632,6 +2677,25 @@ public function createForm($form, $type = 'full')
}
}

if (true === api_get_configuration_value('exercise_subscribe_session_when_finished_failure')) {
$optionSessionWhenFailure = [];

if ($failureSession = ExerciseLib::getSessionWhenFinishedFailure($this->iid)) {
$defaults['subscribe_session_when_finished_failure'] = $failureSession->getId();
$optionSessionWhenFailure[$failureSession->getId()] = $failureSession->getName();
}

$form->addSelectAjax(
'extra_subscribe_session_when_finished_failure',
get_lang('SubscribeSessionWhenFinishedFailure'),
$optionSessionWhenFailure,
[
'url' => api_get_path(WEB_AJAX_PATH).'session.ajax.php?'
.http_build_query(['a' => 'search_session']),
]
);
}

$settings = api_get_configuration_value('exercise_finished_notification_settings');
if (!empty($settings)) {
$options = [];
Expand Down Expand Up @@ -2687,6 +2751,10 @@ public function createForm($form, $type = 'full')
$defaults['exercise_category_id'] = $this->getExerciseCategoryId();
$defaults['prevent_backwards'] = $this->getPreventBackwards();

if (true === api_get_configuration_value('exercise_text_when_finished_failure')) {
$defaults['text_when_finished_failure'] = $this->getTextWhenFinishedFailure();
}

if (!empty($this->start_time)) {
$defaults['activate_start_date_check'] = 1;
}
Expand Down Expand Up @@ -2715,6 +2783,11 @@ public function createForm($form, $type = 'full')
$defaults['results_disabled'] = 0;
$defaults['randomByCat'] = 0;
$defaults['text_when_finished'] = '';

if (true === api_get_configuration_value('exercise_text_when_finished_failure')) {
$defaults['text_when_finished_failure'] = '';
}

$defaults['start_time'] = date('Y-m-d 12:00:00');
$defaults['display_category_name'] = 1;
$defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
Expand Down Expand Up @@ -2886,6 +2959,11 @@ public function processCreation($form, $type = '')
$this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
$this->updateRandomByCat($form->getSubmitValue('randomByCat'));
$this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));

if (true === api_get_configuration_value('exercise_text_when_finished_failure')) {
$this->setTextWhenFinishedFailure($form->getSubmitValue('text_when_finished_failure'));
}

$this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
$this->updateReviewAnswers($form->getSubmitValue('review_answers'));
$this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
Expand Down Expand Up @@ -11759,6 +11837,34 @@ public static function getFeedbackTypeLiteral(int $feedbackType): string
return $result;
}

/**
* Return the text to display, based on the score and the max score.
*
* @param int|float $score
* @param int|float $maxScore
*/
public function getFinishText($score, $maxScore): string
{
if (true !== api_get_configuration_value('exercise_text_when_finished_failure')) {
return $this->getTextWhenFinished();
}

$passPercentage = $this->selectPassPercentage();

if (!empty($passPercentage)) {
$percentage = float_format(
($score / (0 != $maxScore ? $maxScore : 1)) * 100,
1
);

if ($percentage < $passPercentage) {
return $this->getTextWhenFinishedFailure();
}
}

return $this->getTextWhenFinished();
}

/**
* Get number of questions in exercise by user attempt.
*
Expand Down
14 changes: 7 additions & 7 deletions main/exercise/exercise_show.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,6 @@ function getFCK(vals, marksid) {
$questionList = $question_list_from_database;
}

// Display the text when finished message if we are on a LP #4227
$end_of_message = $objExercise->getTextWhenFinished();
if (!empty($end_of_message) && ($origin === 'learnpath')) {
echo Display::return_message($end_of_message, 'normal', false);
echo "<div class='clear'>&nbsp;</div>";
}

// for each question
$total_weighting = 0;
foreach ($questionList as $questionId) {
Expand Down Expand Up @@ -884,6 +877,13 @@ class="exercise_mark_select"
$exercise_content .= Display::panel($question_content);
} // end of large foreach on questions

// Display the text when finished message if we are on a LP #4227
$end_of_message = $objExercise->getFinishText($totalScore, $totalWeighting);
if (!empty($end_of_message) && ($origin === 'learnpath')) {
echo Display::return_message($end_of_message, 'normal', false);
echo "<div class='clear'>&nbsp;</div>";
}

$totalScoreText = '';
if ($answerType != MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
$pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
Expand Down
73 changes: 58 additions & 15 deletions main/inc/lib/exercise.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* For licensing terms, see /license.txt */

use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
use Chamilo\CoreBundle\Entity\TrackEExercises;
use Chamilo\CourseBundle\Entity\CQuizQuestion;
use ChamiloSession as Session;
Expand Down Expand Up @@ -5368,21 +5369,6 @@ public static function displayQuestionListByAttempt(
);
}

// Display text when test is finished #4074 and for LP #4227
// Allows to do a remove_XSS for end text result of exercise with
// user status COURSEMANAGERLOWSECURITY BT#20194
if (true === api_get_configuration_value('exercise_result_end_text_html_strict_filtering')) {
$endOfMessage = Security::remove_XSS($objExercise->getTextWhenFinished(), COURSEMANAGERLOWSECURITY);
} else {
$endOfMessage = Security::remove_XSS($objExercise->getTextWhenFinished());
}
if (!empty($endOfMessage)) {
echo Display::div(
$endOfMessage,
['id' => 'quiz_end_message']
);
}

$question_list_answers = [];
$category_list = [];
$loadChoiceFromSession = false;
Expand Down Expand Up @@ -5618,6 +5604,22 @@ public static function displayQuestionListByAttempt(
}
}

// Display text when test is finished #4074 and for LP #4227
// Allows to do a remove_XSS for end text result of exercise with
// user status COURSEMANAGERLOWSECURITY BT#20194
$finishMessage = $objExercise->getFinishText($total_score, $total_weight);
if (true === api_get_configuration_value('exercise_result_end_text_html_strict_filtering')) {
$endOfMessage = Security::remove_XSS($finishMessage, COURSEMANAGERLOWSECURITY);
} else {
$endOfMessage = Security::remove_XSS($finishMessage);
}
if (!empty($endOfMessage)) {
echo Display::div(
$endOfMessage,
['id' => 'quiz_end_message']
);
}

$totalScoreText = null;
$certificateBlock = '';
if (($show_results || $show_only_score) && $showTotalScore) {
Expand Down Expand Up @@ -5791,6 +5793,13 @@ public static function displayQuestionListByAttempt(
$total_weight
);

if ($save_user_result
&& !$passed
&& true === api_get_configuration_value('exercise_subscribe_session_when_finished_failure')
) {
self::subscribeSessionWhenFinishedFailure($objExercise->iid);
}

$percentage = 0;
if (!empty($total_weight)) {
$percentage = ($total_score / $total_weight) * 100;
Expand All @@ -5811,6 +5820,26 @@ public static function displayQuestionListByAttempt(
];
}

public static function getSessionWhenFinishedFailure(int $exerciseId): ?SessionEntity
{
$objExtraField = new ExtraField('exercise');
$objExtraFieldValue = new ExtraFieldValue('exercise');

$subsSessionWhenFailureField = $objExtraField->get_handler_field_info_by_field_variable(
'subscribe_session_when_finished_failure'
);
$subsSessionWhenFailureValue = $objExtraFieldValue->get_values_by_handler_and_field_id(
$exerciseId,
$subsSessionWhenFailureField['id']
);

if (!empty($subsSessionWhenFailureValue['value'])) {
return api_get_session_entity((int) $subsSessionWhenFailureValue['value']);
}

return null;
}

/**
* It validates unique score when all user answers are correct by question.
* It is used for global questions.
Expand Down Expand Up @@ -7373,4 +7402,18 @@ public static function exportExerciseAllResultsZip(

return false;
}

private static function subscribeSessionWhenFinishedFailure(int $exerciseId): void
{
$failureSession = self::getSessionWhenFinishedFailure($exerciseId);

if ($failureSession) {
SessionManager::subscribeUsersToSession(
$failureSession->getId(),
[api_get_user_id()],
SESSION_VISIBLE_READ_ONLY,
false
);
}
}
}
13 changes: 13 additions & 0 deletions main/install/configuration.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -2555,6 +2555,19 @@
// Add more speed options to reading comprehension question type (type id = 21) in words per minute
//$_configuration['exercise_question_reading_comprehension_extra_speeds'] = ['speeds' => [70, 110, 170]];

// Text appearing at the end of the test when the user has failed. Requires DB changes.
/*
ALTER TABLE c_quiz ADD text_when_finished_failure LONGTEXT DEFAULT NULL;
*/
// Then add the "@" symbol to the CQuiz class in the ORM\Column() line for its $textWhenFinishedFailure property.
//$_configuration['exercise_text_when_finished_failure'] = false;

// Add an option to subscribe the user at the end of test when the user has failed. Requires DB changes.
/*
INSERT INTO extra_field (extra_field_type, field_type, variable, display_text, default_value, field_order, visible_to_self, visible_to_others, changeable, filter, created_at) VALUES (17, 5, 'subscribe_session_when_finished_failure', 'SubscribeSessionWhenFinishedFailure', '', 0, 1, 0, 1, 0, NOW());
*/
//$_configuration['exercise_subscribe_session_when_finished_failure'] = false;

//hide copy icon in LP's authoring options
//$_configuration['lp_hide_copy_option'] = false;

Expand Down
Loading
Loading