From e2aa9598d28e11e5234b19192af1578d41c4ed22 Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Thu, 22 May 2025 11:19:47 +0200 Subject: [PATCH] Quiz: Add media question support (partial from very old code - requires review) - refs #2676 --- main/exercise/exercise.class.php | 22 +- main/exercise/exercise.php | 1 + main/exercise/exercise_show.php | 26 +- main/exercise/exercise_submit.php | 366 +++++++++--------- main/exercise/fill_blanks.class.php | 4 +- main/exercise/freeanswer.class.php | 4 +- .../exercise/global_multiple_answer.class.php | 4 +- main/exercise/matching.class.php | 4 +- main/exercise/multiple_answer.class.php | 4 +- .../multiple_answer_combination.class.php | 4 +- .../multiple_answer_true_false.class.php | 4 +- main/exercise/oral_expression.class.php | 4 +- main/exercise/question.class.php | 159 +++++++- main/exercise/question_admin.inc.php | 108 ++++-- main/exercise/question_list_admin.inc.php | 13 +- main/exercise/unique_answer.class.php | 4 +- .../unique_answer_no_option.class.php | 4 +- main/inc/ajax/exercise.ajax.php | 30 +- main/inc/lib/exercise.lib.php | 29 +- 19 files changed, 547 insertions(+), 247 deletions(-) diff --git a/main/exercise/exercise.class.php b/main/exercise/exercise.class.php index 1396013fbc1..80bc1c53f9e 100755 --- a/main/exercise/exercise.class.php +++ b/main/exercise/exercise.class.php @@ -3514,7 +3514,15 @@ public function show_button( int $lpItemId = 0, int $lpItemViewId = 0 ) { + $nbrQuestions = $this->countQuestionsInExercise(); + $media_questions = $this->get_media_list(); + $media_active = $this->media_is_activated($media_questions); + + if ($media_active) { + $nbrQuestions = $this->get_count_questions_when_using_medias(); + } + $buttonList = []; $html = $label = ''; $hotspotGet = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null; @@ -7564,9 +7572,9 @@ public function get_validated_question_list() return $questionList; } - // Problem, random by category has been selected and + // Problem, random by category has been selected, and // we have no $this->isRandom number of question selected - // Should not happened + // Should not happen return []; } @@ -12245,8 +12253,6 @@ private function sendNotificationForOralQuestions( private function setMediaList($questionList) { $mediaList = []; - /* - * Media feature is not activated in 1.11.x if (!empty($questionList)) { foreach ($questionList as $questionId) { $objQuestionTmp = Question::read($questionId, $this->course_id); @@ -12258,7 +12264,7 @@ private function setMediaList($questionList) $mediaList[999][] = $objQuestionTmp->iid; } } - }*/ + } $this->mediaList = $mediaList; } @@ -12394,4 +12400,10 @@ private function setResultDisabledGroup(FormValidator $form) get_lang('ShowResultsToStudents') ); } + public function get_count_questions_when_using_medias() { + $media_questions = $this->getMediaList(); + $questions_with_no_group = isset($media_questions[999]) ? count($media_questions[999]) : 0; + + return count($media_questions) - 1 + $questions_with_no_group; + } } diff --git a/main/exercise/exercise.php b/main/exercise/exercise.php index b3502ad6c8b..d0c22b4dd4a 100644 --- a/main/exercise/exercise.php +++ b/main/exercise/exercise.php @@ -635,6 +635,7 @@ function showUserToSendNotificacion(element) { $actionsLeft .= ''; $actionsLeft .= Display::return_icon('database.png', get_lang('QuestionPool'), '', ICON_SIZE_MEDIUM); $actionsLeft .= ''; + //$actionsLeft .= Display::url(Display::return_icon('looknfeel.png', get_lang('Media')), 'media.php?' . api_get_cidreq()); // end question category $actionsLeft .= ''. diff --git a/main/exercise/exercise_show.php b/main/exercise/exercise_show.php index f456f3c9fed..c4f0ff0bf8e 100755 --- a/main/exercise/exercise_show.php +++ b/main/exercise/exercise_show.php @@ -391,6 +391,9 @@ function getFCK(vals, marksid) { $counter = 1; $exercise_content = ''; $category_list = []; +$mediaList = []; +$tempParentId = null; +$mediaCounter = 0; $useAdvancedEditor = true; if (!empty($maxEditors) && count($questionList) > $maxEditors) { @@ -863,13 +866,34 @@ class="exercise_mark_select" $contents = ob_get_clean(); $question_content = '
'; + $showMedia = false; + + $counterToShow = $counter; + + if ($objQuestionTmp->parent_id != 0) { + + if (!in_array($objQuestionTmp->parent_id, $media_list)) { + $media_list[] = $objQuestionTmp->parent_id; + $show_media = true; + } + if ($tempParentId == $objQuestionTmp->parent_id) { + $mediaCounter++; + } else { + $mediaCounter = 0; + } + $counterToShow = chr(97 + $mediaCounter); + $tempParentId = $objQuestionTmp->parent_id; + } + if ($show_results && $objQuestionTmp) { $objQuestionTmp->export = $action === 'export'; // Shows question title an description $question_content .= $objQuestionTmp->return_header( $objExercise, $counter, - $score + $score, + $showMedia, + $mediaCounter ); } $counter++; diff --git a/main/exercise/exercise_submit.php b/main/exercise/exercise_submit.php index 5060cbb3e6d..f8ee1a9c872 100755 --- a/main/exercise/exercise_submit.php +++ b/main/exercise/exercise_submit.php @@ -372,6 +372,7 @@ // Fix in order to get the correct question list. $questionListUncompressed = $objExercise->getQuestionListWithMediasUncompressed(); Session::write('question_list_uncompressed', $questionListUncompressed); + $clock_expired_time = null; if (empty($exercise_stat_info)) { $disable = api_get_configuration_value('exercises_disable_new_attempts'); @@ -379,7 +380,7 @@ api_not_allowed(true); } $total_weight = 0; - $questionList = $objExercise->get_validated_question_list(); + $questionList = $objExercise->get_question_list(true); foreach ($questionListUncompressed as $question_id) { $objQuestionTmp = Question::read($question_id); $total_weight += (float) $objQuestionTmp->weighting; @@ -614,13 +615,18 @@ //Sends the exercise form when the expired time is finished. $htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left); } -// in LP's is enabled the "remember question" feature? + +$media_questions = $objExercise->getMediaList(); +$media_question_is_active = $objExercise->mediaIsActivated($media_questions); + +// If the "remind question" option is enabled, in learning path if (!isset($_SESSION['questionList'])) { // selects the list of question ID - $questionList = $objExercise->get_validated_question_list(); - if ($objExercise->isRandom() && !empty($exercise_stat_info['data_tracking'])) { + $questionList = $objExercise->get_question_list(false); + if ($media_is_activated == false && $objExercise->isRandom() && !empty($exercise_stat_info['data_tracking'])) { $questionList = explode(',', $exercise_stat_info['data_tracking']); } + Session::write('questionList', $questionList); } else { if (isset($objExercise) && isset($_SESSION['objExercise'])) { @@ -733,6 +739,10 @@ if (!empty($questionList)) { $question_count = count($questionList); } +if ($media_question_is_active) { + $question_count = $objExercise->get_count_questions_when_using_medias(); +} + if ($current_question > $question_count) { // If time control then don't change the current question, otherwise there will be a loop. @@ -1468,6 +1478,7 @@ function save_now(question_id, url_extra) { $("#save_for_now_"+question_id).html(\''.$loading.'\'); $.ajax({ type:"post", + async: false, url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=save_exercise_by_now", data: dataparam, success: function(return_value) { @@ -1596,6 +1607,7 @@ function save_now_all(validate) { $.ajax({ type:"post", + async: false, url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=save_exercise_by_now", data: requestData, success: function(return_value) { @@ -1651,214 +1663,216 @@ function validate_all() { ) { $remind_list = explode(',', $exercise_stat_info['questions_to_check']); } +render_question_list( + $objExercise, + $questionList, + $current_question, + $exerciseResult, + $attempt_list, + $remind_list, + $media_questions +); +echo ''; -foreach ($questionList as $questionId) { - // for sequential exercises - if (ONE_PER_PAGE == $objExercise->type) { - // if it is not the right question, goes to the next loop iteration - if ($current_question != $i) { - $i++; - continue; - } else { - if (!in_array($objExercise->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) { - // if the user has already answered this question - if (isset($exerciseResult[$questionId])) { - // construction of the Question object - $objQuestionTmp = Question::read($questionId); - $questionName = $objQuestionTmp->selectTitle(); - // destruction of the Question object - unset($objQuestionTmp); - echo Display::return_message(get_lang('AlreadyAnswered')); - $i++; - break; +function render_question_list( + $objExercise, + $questionList, + $current_question, + $exerciseResult, + $attempt_list, + $remind_list, + $media_questions = [], + $exerciseInSession, + $learnpath_id = null, + $learnpath_item_id = null, + $learnpath_item_view_id = null, + $myRemindList = [], + $showPreviousButton = false +) { + global $origin; + + $i = 1; + + //Normal question list render + foreach ($questionList as $questionId) { + // for sequential exercises + if (ONE_PER_PAGE == $objExercise->type) { + // if it is not the right question, goes to the next loop iteration + if ($current_question != $i) { + $i++; + continue; + } else { + if (!in_array($objExercise->getFeedbackType(), + [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) { + // if the user has already answered this question + if (isset($exerciseResult[$questionId])) { + // construction of the Question object + $objQuestionTmp = Question::read($questionId); + //$questionName = $objQuestionTmp->selectTitle(); + // destruction of the Question object + unset($objQuestionTmp); + echo Display::return_message(get_lang('AlreadyAnswered')); + $i++; + break; + } } - } - if (1 === $exerciseInSession->getPreventBackwards()) { - if (isset($attempt_list[$questionId])) { - echo Display::return_message(get_lang('AlreadyAnswered')); - $i++; - break; + if (1 === $exerciseInSession->getPreventBackwards()) { + if (isset($attempt_list[$questionId])) { + echo Display::return_message(get_lang('AlreadyAnswered')); + $i++; + break; + } } } } - } - $user_choice = null; - if (isset($attempt_list[$questionId])) { - $user_choice = $attempt_list[$questionId]; - } elseif ($objExercise->getSaveCorrectAnswers()) { - $correctAnswers = []; - switch ($objExercise->getSaveCorrectAnswers()) { - case 1: - $correctAnswers = $objExercise->getCorrectAnswersInAllAttempts( - $learnpath_id, - $learnpath_item_id - ); - break; - case 2: - $correctAnswers = $objExercise->getAnswersInAllAttempts( - $learnpath_id, - $learnpath_item_id, - false - ); - break; + + // Media question rendering + + if (isset($media_questions) && !empty($media_questions)) { + $media_question_list = $media_questions[$questionId]; + $objQuestionTmp = Question::read($questionId); + + $counter = 1; + if ($objQuestionTmp->type == MEDIA_QUESTION) { + echo $objQuestionTmp->show_media_content(); + $count_of_questions_inside_media = count($media_question_list); + + } + //Show questions that belongs to a media + if (!empty($media_question_list)) { + $letterCounter = 97; + foreach ($media_question_list as $my_question_id) { + if ($counter == $count_of_questions_inside_media) { + $last_question_in_media = true; + } + render_question( + $objExercise, + $my_question_id, + $attempt_list, + $remind_list, + chr($letterCounter), + $current_question, + true, + $count_of_questions_inside_media, + $last_question_in_media + ); + $letterCounter++; + $counter++; + } + } else { + render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question); + } + } else { + // Normal question rendering + render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question); } - if (isset($correctAnswers[$questionId])) { - $user_choice = $correctAnswers[$questionId]; + $i++; + // for sequential exercises + if ($objExercise->type == ONE_PER_PAGE) { + // quits the loop + break; } } - $remind_highlight = ''; - // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery - if ($objExercise->type == ALL_ON_ONE_PAGE && - isset($_GET['reminder']) && $_GET['reminder'] == 2 - ) { + if ($objExercise->type == ALL_ON_ONE_PAGE) { + $exerciseActions = $objExercise->show_button( + $questionId, + $current_question, + [], + '', + [], + true, + $learnpath_id, + $learnpath_item_id, + $learnpath_item_view_id + ); + echo Display::div($exerciseActions, ['class' => 'exercise_actions']); + echo '
'; + } +} + +function render_question( + $objExercise, + $questionId, + $attempt_list, + $remind_list, + $i, + $current_question, + $question_in_media = [], + $last_question_in_media = false +) { + global $origin; + $user_choice = $attempt_list[$questionId]; + + $remind_highlight = null; + + //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery + if ($objExercise->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) { $remind_highlight = 'no_remind_highlight'; } - $exerciseActions = ''; + + $attributes = array('id' => 'remind_list['.$questionId.']'); + $is_remind_on = false; - $attributes = ['id' => 'remind_list['.$questionId.']']; if (in_array($questionId, $remind_list)) { $is_remind_on = true; $attributes['checked'] = 1; - $remind_question = true; $remind_highlight = ' remind_highlight '; } - $openDescription = api_get_configuration_value('quiz_question_description_open_by_default') ? true : false; - - // Showing the exercise description - if (!empty($objExercise->description)) { - if ($objExercise->type == ONE_PER_PAGE || ($objExercise->type != ONE_PER_PAGE && $i == 1)) { - echo Display::panelCollapse( - ''.get_lang('ExerciseDescriptionLabel').'', - Security::remove_XSS($objExercise->description, COURSEMANAGERLOWSECURITY), - 'exercise-description', - [], - 'description', - 'exercise-collapse', - $openDescription, - true - ); - } - } + //Showing the question - echo '
'; - $showQuestion = true; - $exerciseResultFromSession = Session::read('exerciseResult'); - if ($objExercise->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_POPUP && - isset($exerciseResultFromSession[$questionId]) - ) { - $showQuestion = false; - } + $exercise_actions = null; - // Shows the question and its answers - if ($showQuestion) { - ExerciseLib::showQuestion( - $objExercise, - $questionId, - false, - $origin, - $i, - $objExercise->getHideQuestionTitle() ? false : true, - false, - $user_choice, - false, - null, - false, - true - ); - } else { - echo Display::return_message(get_lang('AlreadyAnswered')); - } + echo '
'; + + // Shows the question + possible answers + showQuestion($questionId, false, $origin, $i, true, false, $user_choice, false); - // Button save and continue + // Button to save and continue switch ($objExercise->type) { case ONE_PER_PAGE: - $exerciseActions .= $objExercise->show_button( - $questionId, - $current_question, - [], - [], - $myRemindList, - $showPreviousButton, - $learnpath_id, - $learnpath_item_id, - $learnpath_item_view_id - ); + $exercise_actions .= $objExercise->show_button($questionId, $current_question); break; - case ALL_ON_ONE_PAGE: - if (api_is_allowed_to_session_edit()) { - $button = [ - Display::button( - 'save_now', - get_lang('SaveForNow'), - [ - 'type' => 'button', - 'class' => 'btn btn-info', - 'data-question' => $questionId, - ] - ), - ' ', - ]; - $exerciseActions .= Display::div( - implode(PHP_EOL, $button), - ['class' => 'exercise_save_now_button'] - ); - } + case ALL_ON_ONE_PAGE : + $button = ''.get_lang('SaveForNow').''; + $button .= ' '; + $exercise_actions .= Display::div($button, array('class' => 'exercise_save_now_button')); break; } - // Checkbox review answers - if ($objExercise->review_answers) { - $remind_question_div = Display::tag( - 'label', - Display::input( - 'checkbox', - 'remind_list['.$questionId.']', - '', - $attributes - ).get_lang('ReviewQuestionLater'), - [ - 'class' => 'checkbox', - 'for' => 'remind_list['.$questionId.']', - ] - ); - $exerciseActions .= Display::div( - $remind_question_div, - ['class' => 'exercise_save_now_button'] - ); + if (!empty($questions_in_media)) { + /*$button = ''.get_lang('SaveForNow').''; + $button .= ' '; + $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button')); + $exercise_actions .= $objExercise->show_button($questionId, $current_question);*/ + $count_of_questions_inside_media = count($questions_in_media); + if ($count_of_questions_inside_media > 1) { + $button = ''.get_lang('SaveForNow').''; + $button .= ' '; + $exercise_actions = Display::div($button, array('class' => 'exercise_save_now_button')); + } + + if ($last_question_in_media) { + $exercise_actions = $objExercise->show_button($questionId, $current_question, $questions_in_media); + } } - echo Display::div($exerciseActions, ['class' => 'form-actions']); - echo '
'; - $i++; - // for sequential exercises - if ($objExercise->type == ONE_PER_PAGE) { - // quits the loop - break; + //Checkbox review answers + if ($objExercise->review_answers) { + $remind_question_div = Display::tag('label', + Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), + array('class' => 'checkbox', 'for' => 'remind_list['.$questionId.']')); + $exercise_actions .= Display::div($remind_question_div, array('class' => 'exercise_save_now_button')); } -} -if ($objExercise->type == ALL_ON_ONE_PAGE) { - $exerciseActions = $objExercise->show_button( - $questionId, - $current_question, - [], - '', - [], - true, - $learnpath_id, - $learnpath_item_id, - $learnpath_item_view_id - ); - echo Display::div($exerciseActions, ['class' => 'exercise_actions']); - echo '
'; + echo Display::div($exercise_actions, array('class' => 'form-actions')); + echo '
'; } -echo ''; if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) { // So we are not in learnpath tool diff --git a/main/exercise/fill_blanks.class.php b/main/exercise/fill_blanks.class.php index d9d27207beb..af51ecf3474 100755 --- a/main/exercise/fill_blanks.class.php +++ b/main/exercise/fill_blanks.class.php @@ -512,9 +512,9 @@ function ($matches) use ($blankStartSeparator, $blankEndSeparator) { /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= ' diff --git a/main/exercise/freeanswer.class.php b/main/exercise/freeanswer.class.php index 093039cc909..126622146f2 100755 --- a/main/exercise/freeanswer.class.php +++ b/main/exercise/freeanswer.class.php @@ -54,10 +54,10 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { $score['revised'] = $this->isQuestionWaitingReview($score); - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'.get_lang('Answer').'
diff --git a/main/exercise/global_multiple_answer.class.php b/main/exercise/global_multiple_answer.class.php index 46b4092d269..75694bdd112 100755 --- a/main/exercise/global_multiple_answer.class.php +++ b/main/exercise/global_multiple_answer.class.php @@ -252,9 +252,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'.get_lang('Answer').'
'; if (!in_array($exercise->results_disabled, [RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER])) { diff --git a/main/exercise/matching.class.php b/main/exercise/matching.class.php index d28cba7f13f..8b80b922dd5 100755 --- a/main/exercise/matching.class.php +++ b/main/exercise/matching.class.php @@ -299,9 +299,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'; $header .= ''; diff --git a/main/exercise/multiple_answer.class.php b/main/exercise/multiple_answer.class.php index 3f33e6a51ba..04e4dd93225 100755 --- a/main/exercise/multiple_answer.class.php +++ b/main/exercise/multiple_answer.class.php @@ -276,9 +276,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'; $header .= ''; diff --git a/main/exercise/multiple_answer_combination.class.php b/main/exercise/multiple_answer_combination.class.php index a951b2c7bd0..4cc69f361a3 100755 --- a/main/exercise/multiple_answer_combination.class.php +++ b/main/exercise/multiple_answer_combination.class.php @@ -225,9 +225,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'.get_lang('Choice').'
'; if (!in_array($exercise->results_disabled, [ diff --git a/main/exercise/multiple_answer_true_false.class.php b/main/exercise/multiple_answer_true_false.class.php index b971ef4d087..94a2272cfb1 100755 --- a/main/exercise/multiple_answer_true_false.class.php +++ b/main/exercise/multiple_answer_true_false.class.php @@ -315,9 +315,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'; if (!in_array($exercise->results_disabled, [ diff --git a/main/exercise/oral_expression.class.php b/main/exercise/oral_expression.class.php index a1166d1c1f1..d68a066c8e7 100755 --- a/main/exercise/oral_expression.class.php +++ b/main/exercise/oral_expression.class.php @@ -66,10 +66,10 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { $score['revised'] = $this->isQuestionWaitingReview($score); - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
diff --git a/main/exercise/question.class.php b/main/exercise/question.class.php index 6ecce2e3729..fd793020675 100755 --- a/main/exercise/question.class.php +++ b/main/exercise/question.class.php @@ -69,7 +69,7 @@ abstract class Question DRAGGABLE => ['Draggable.php', 'Draggable'], MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable'], MATCHING_DRAGGABLE_COMBINATION => ['MatchingDraggableCombination.php', 'MatchingDraggableCombination'], - //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion') + MEDIA_QUESTION => ['media_question.class.php' , 'MediaQuestion'], ANNOTATION => ['Annotation.php', 'Annotation'], READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'], UPLOAD_ANSWER => ['UploadAnswer.php', 'UploadAnswer'], @@ -179,6 +179,7 @@ public static function read($id, $course_info = [], $getExerciseList = true) $objQuestion->course = $course_info; $objQuestion->feedback = isset($object->feedback) ? $object->feedback : ''; $objQuestion->code = isset($object->code) ? $object->code : ''; + $objQuestion->parent_id = $object->parent_id; $categoryInfo = TestCategory::getCategoryInfoForQuestion($id, $course_id); if (!empty($categoryInfo)) { @@ -1022,6 +1023,7 @@ public function save($exercise) $extra = $this->extra; $c_id = $this->course['real_id']; $categoryId = $this->category; + $parent_id = $this->parent_id; // question already exists if (!empty($id)) { @@ -1034,6 +1036,7 @@ public function save($exercise) 'picture' => $picture, 'extra' => $extra, 'level' => $level, + 'parent_id' => $parent_id, ]; if ($exercise->questionFeedbackEnabled) { $params['feedback'] = $this->feedback; @@ -1088,6 +1091,7 @@ public function save($exercise) 'picture' => $picture, 'extra' => $extra, 'level' => $level, + 'parent_id' => $parent_id, ]; if ($exercise->questionFeedbackEnabled) { @@ -1445,6 +1449,18 @@ public function delete(int $deleteFromEx = 0, bool $deletePicture = true): bool $id = (int) $this->iid; + if ($this->type == MEDIA_QUESTION) { + // Removing media for attached questions + + $sql = "UPDATE $TBL_QUESTIONS SET parent_id = '' WHERE parent_id = $id"; + Database::query($sql); + + $sql = "DELETE FROM $TBL_QUESTIONS WHERE c_id = $courseId AND iid= ".Database::escape_string($id); + Database::query($sql); + return true; + } + + // if the question must be removed from all exercises if (!$deleteFromEx) { $courseFilter = " AND c_id = $courseId"; @@ -1568,6 +1584,7 @@ public function duplicate($courseInfo = []) $type = $this->type; $level = (int) $this->level; $extra = $this->extra; + $parent_id = $this->parent_id; // Using the same method used in the course copy to transform URLs if ($this->course['id'] != $courseInfo['id']) { @@ -1598,6 +1615,7 @@ public function duplicate($courseInfo = []) 'type' => $type, 'level' => $level, 'extra' => $extra, + 'parent_id' => $parent_id, ]; $newQuestionId = Database::insert($questionTable, $params); @@ -1814,6 +1832,9 @@ public function createForm(&$form, $exercise) $editorConfig ); + // hidden value + $form->addElement('hidden', 'myid', intval($_REQUEST['myid'])); + if ($this->type != MEDIA_QUESTION) { // Advanced parameters. $form->addElement( @@ -1831,6 +1852,16 @@ public function createForm(&$form, $exercise) TestCategory::getCategoriesIdAndName() ); + // Media + $course_medias = Question::prepare_course_media_select(api_get_course_int_id()); + $form->addElement( + 'select', + 'parent_id', + get_lang('AttachToMedia'), + $course_medias, + ['id' => 'parent_id'] + ); + if (EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $exercise->getQuestionSelectionType() && api_get_configuration_value('allow_mandatory_question_in_category') ) { @@ -1934,7 +1965,7 @@ public function createForm(&$form, $exercise) // default values - // Came from he question pool + // Came from the question pool if (isset($_GET['fromExercise']) || (!isset($_GET['newQuestion']) || $isContent) ) { @@ -1986,6 +2017,7 @@ public function createForm(&$form, $exercise) */ public function processCreation($form, $exercise) { + //$this->updateParentId($form->getSubmitValue('parent_id')); $this->updateTitle($form->getSubmitValue('questionName')); $this->updateDescription($form->getSubmitValue('questionDescription')); $this->updateLevel($form->getSubmitValue('questionLevel')); @@ -2219,7 +2251,7 @@ public static function readQuestionOption($question_id, $course_id) * * @return string HTML string with the header of the question (before the answers table) */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { $counterLabel = ''; if (!empty($counter)) { @@ -2298,8 +2330,7 @@ public function return_header(Exercise $exercise, $counter = null, $score = []) if ($exercise->display_category_name) { $header = TestCategory::returnCategoryAndTitle($this->iid); } - $show_media = ''; - if ($show_media) { + if ($showMedia) { $header .= $this->show_media_content(); } @@ -2863,4 +2894,122 @@ private function resizePicture($Dimension, $Max) return false; } + + public static function getMediaLabels() { + // Shows media questions + $courseMedias = Question::prepare_course_media_select(api_get_course_int_id()); + $labels = null; + if (!empty($courseMedias)) { + $labels .= get_lang('MediaQuestion').' '; + foreach ($courseMedias as $mediaId => $media) { + + $editLink = ''.Display::return_icon('edit.png',get_lang('Modify'), array(), ICON_SIZE_SMALL).''; + $deleteLink = ''.Display::return_icon('delete.png',get_lang('Delete'), array(), ICON_SIZE_SMALL).''; + + if (!empty($mediaId)) { + $labels .= Display::label($media).$editLink.$deleteLink.' '; + } + } + } + + return $labels; + } + + static function getMediaLabel($title) { + return Display::label($title, 'warning'); + } + + /** + * @param $exerciseId + * @param $mediaId + * @return array|bool + */ + public function getQuestionsPerMediaWithCategories($exerciseId, $mediaId) + { + $exerciseId = intval($exerciseId); + $mediaId = intval($mediaId); + $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION); + $questionRelExerciseTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); + + $sql = "SELECT * FROM $questionTable q INNER JOIN $questionRelExerciseTable r ON (q.iid = r.question_id) + WHERE (r.exercise_id = $exerciseId AND q.parent_id = $mediaId) "; + + $result = Database::query($sql); + if (Database::num_rows($result)) { + return Database::store_result($result); + } + return false; + } + /** + * @param int $exerciseId + * @param int $mediaId + * @return array + */ + public function getQuestionCategoriesOfMediaQuestions($exerciseId, $mediaId) + { + $questions = $this->getQuestionsPerMediaWithCategories($exerciseId, $mediaId); + $questionCategoryList = array(); + if (!empty($questions)) { + foreach ($questions as $question) { + $categories = TestCategory::getCategoryForQuestionWithCategoryData($question['iid']); + if (!empty($categories)) { + foreach ($categories as $category) { + $questionCategoryList[$question['iid']][] = $category['iid']; + } + } + } + } + return $questionCategoryList; + } + + /** + * @param int $exerciseId + * @param int $mediaId + * @return array + */ + public function allQuestionWithMediaHaveTheSameCategory($exerciseId, $mediaId, $categoryListToCompare = array(), $ignoreQuestionId = null, $returnCategoryId = false) + { + $questions = $this->getQuestionCategoriesOfMediaQuestions($exerciseId, $mediaId); + $result = false; + $categoryId = null; + if (empty($questions)) { + $result = true; + } else { + $tempArray = array(); + foreach ($questions as $categories) { + $diff = array_diff($tempArray, $categories); + $categoryId = $categories[0]; + $tempArray = $categories; + if (empty($diff)) { + $result = true; + continue; + } else { + $result = false; + break; + } + } + } + if (isset($categoryListToCompare) && !empty($categoryListToCompare)) { + $result = false; + foreach ($questions as $questionId => $categories) { + if ($ignoreQuestionId == $questionId) { + continue; + } + $diff = array_diff($categoryListToCompare, $categories); + $categoryId = $categories[0]; + if (empty($diff)) { + $result = true; + continue; + } else { + $result = false; + break; + } + } + } + + if ($returnCategoryId) { + return $categoryId; + } + return $result; + } } diff --git a/main/exercise/question_admin.inc.php b/main/exercise/question_admin.inc.php index 2560d044104..c8ab543c9d5 100755 --- a/main/exercise/question_admin.inc.php +++ b/main/exercise/question_admin.inc.php @@ -51,41 +51,91 @@ } // FORM VALIDATION + //$result = $objQuestion->allQuestionWithMediaHaveTheSameCategory($exerciseId, 100); + if (isset($_POST['submitQuestion'])) { - $validationResult = true; - if (method_exists($objQuestion, 'validateAnswers')) { - $validationResult = $objQuestion->validateAnswers($form); - } - if (is_array($validationResult) && !empty($validationResult['errors'])) { - echo Display::return_message(implode("
", $validationResult['errors']), 'error', false); - } elseif ($form->validate()) { - $objQuestion->processCreation($form, $objExercise); - $objQuestion->processAnswersCreation($form, $objExercise); - if (in_array($objQuestion->type, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_DELINEATION])) { - echo ''; - } elseif (in_array($objQuestion->type, [MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION])) { - $url = 'admin.php?'.api_get_cidreq().'&'.http_build_query(['exerciseId' => $exerciseId, 'page' => $page, 'mad_admin' => $objQuestion->iid]); - echo ''; - } else { - if (isset($_GET['editQuestion'])) { - if (empty($exerciseId)) { - Display::addFlash(Display::return_message(get_lang('ItemUpdated'))); - $url = 'admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&editQuestion='.$objQuestion->iid; - echo ''; - exit; + // Media is selected? + $parentId = $form->getSubmitValue('parent_id'); + $categories = $form->getSubmitValue('questionCategory'); + $process = true; + $message = null; + + // A media question was sent + if (isset($parentId) && !empty($parentId)) { + // No allowing 2 categories if a media was selected + $tryAgain = Display::url( + get_lang('TryAgain'), + api_get_path(WEB_CODE_PATH).'exercise/admin.php?exerciseId='.$exerciseId.'&myid=1&editQuestion='.$objQuestion->id.'&'.api_get_cidreq(), + array('class' => 'btn') + ); + + if (isset($categories) && !empty($categories)) { + + if (count($categories) > 1) { + $message = Display::display_warning_message(get_lang('WhenUsingAMediaQuestionYouCantAddMoreThanOneCategory')); + $message .= ' '.$tryAgain; + + $process = false; + } + + $questionCategoriesOfMediaQuestions = $objQuestion->getQuestionCategoriesOfMediaQuestions($exerciseId, $parentId); + + if (!empty($questionCategoriesOfMediaQuestions)) { + // Check if the media sent matches other medias sent before + $result = $objQuestion->allQuestionWithMediaHaveTheSameCategory($exerciseId, $parentId, $categories, $objQuestion->id); + + if ($result == false) { + $message = Display::display_warning_message(get_lang('TheSelectedCategoryDoesNotMatchWithTheOtherQuestionWithTheSameMediaQuestion')); + $message .= ' '.$tryAgain; + $process = false; } - echo ''; + } + } else { + if (!empty($objQuestion->category_list)) { + $message = Display::display_warning_message(get_lang('YouMustProvideACategoryBecauseTheCurrentCategoryDoesNotMatchOtherMediaQuestions')); + $message .= ' '.$tryAgain; + $process = false; + } + } + } + + if ($process) { + + $validationResult = true; + if (method_exists($objQuestion, 'validateAnswers')) { + $validationResult = $objQuestion->validateAnswers($form); + } + if (is_array($validationResult) && !empty($validationResult['errors'])) { + echo Display::return_message(implode("
", $validationResult['errors']), 'error', false); + } elseif ($form->validate()) { + $objQuestion->processCreation($form, $objExercise); + $objQuestion->processAnswersCreation($form, $objExercise); + if (in_array($objQuestion->type, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_DELINEATION])) { + echo ''; + } elseif (in_array($objQuestion->type, [MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION])) { + $url = 'admin.php?'.api_get_cidreq().'&'.http_build_query(['exerciseId' => $exerciseId, 'page' => $page, 'mad_admin' => $objQuestion->iid]); + echo ''; } else { - // New question - $page = 1; - $length = api_get_configuration_value('question_pagination_length'); - if (!empty($length)) { - $page = round($objExercise->getQuestionCount() / $length); + if (isset($_GET['editQuestion'])) { + if (empty($exerciseId)) { + Display::addFlash(Display::return_message(get_lang('ItemUpdated'))); + $url = 'admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&editQuestion='.$objQuestion->iid; + echo ''; + exit; + } + echo ''; + } else { + // New question + $page = 1; + $length = api_get_configuration_value('question_pagination_length'); + if (!empty($length)) { + $page = round($objExercise->getQuestionCount() / $length); + } + echo ''; } - echo ''; } + exit(); } - exit(); } } diff --git a/main/exercise/question_list_admin.inc.php b/main/exercise/question_list_admin.inc.php index 997ebbe0590..d8a7b239e81 100755 --- a/main/exercise/question_list_admin.inc.php +++ b/main/exercise/question_list_admin.inc.php @@ -196,6 +196,8 @@ function loadEditor(button, questionId) { //deletes a session when using don't know question type (ugly fix) Session::erase('less_answer'); +echo Question::getMediaLabels(); + // If we are in a test $inATest = isset($exerciseId) && $exerciseId > 0; if (!$inATest) { @@ -379,14 +381,21 @@ function loadEditor(button, questionId) { // Question type $typeImg = $objQuestionTmp->getTypePicture(); $typeExpl = $objQuestionTmp->getExplanation(); - $questionType = Display::return_icon($typeImg, $typeExpl); + + $question_media = null; + if (!empty($objQuestionTmp->parent_id)) { + $objQuestionMedia = Question::read($objQuestionTmp->parent_id); + $question_media = ' '.Question::getMediaLabel($objQuestionMedia->question); + } + + $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media); // Question category $txtQuestionCat = Security::remove_XSS( TestCategory::getCategoryNameForQuestion($objQuestionTmp->iid) ); if (empty($txtQuestionCat)) { - $txtQuestionCat = '-'; + $txtQuestionCat = ''; } // Question level diff --git a/main/exercise/unique_answer.class.php b/main/exercise/unique_answer.class.php index e1864faf731..02a4c5d6f15 100755 --- a/main/exercise/unique_answer.class.php +++ b/main/exercise/unique_answer.class.php @@ -496,9 +496,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'.get_lang("Answer").'
'; $header .= ''; diff --git a/main/exercise/unique_answer_no_option.class.php b/main/exercise/unique_answer_no_option.class.php index 4a583798395..365a7999522 100755 --- a/main/exercise/unique_answer_no_option.class.php +++ b/main/exercise/unique_answer_no_option.class.php @@ -395,9 +395,9 @@ public function processAnswersCreation($form, $exercise) /** * {@inheritdoc} */ - public function return_header(Exercise $exercise, $counter = null, $score = []) + public function return_header(Exercise $exercise, $counter = null, $score = [], $showMedia = false) { - $header = parent::return_header($exercise, $counter, $score); + $header = parent::return_header($exercise, $counter, $score, $showMedia); $header .= '
'.get_lang('Choice').'
'; if (!in_array($exercise->results_disabled, [ diff --git a/main/inc/ajax/exercise.ajax.php b/main/inc/ajax/exercise.ajax.php index 87381e2957a..ebdf7bda419 100755 --- a/main/inc/ajax/exercise.ajax.php +++ b/main/inc/ajax/exercise.ajax.php @@ -559,13 +559,6 @@ $exercise_id = $exercise_stat_info['exe_exo_id']; $attemptList = []; // First time here we create an attempt (getting the exe_id). - if (!empty($exercise_stat_info)) { - // We know the user we get the exe_id. - $exeId = $exercise_stat_info['exe_id']; - $total_score = $exercise_stat_info['exe_result']; - // Getting the list of attempts - $attemptList = Event::getAllExerciseEventByExeId($exeId); - } // No exe id? Can't save answer. if (empty($exeId)) { @@ -1266,6 +1259,29 @@ function (array $exercise) { } } break; + case 'get_categories_by_media': + $questionId = $_REQUEST['questionId']; + $mediaId = $_REQUEST['mediaId']; + $exerciseId = $_REQUEST['exerciseId']; + $question = Question::read($questionId); + if (empty($mediaId)) { + echo 0; + break; + } + $categoryId = $question->allQuestionWithMediaHaveTheSameCategory($exerciseId, $mediaId, null, null, true); + + if (!empty($categoryId)) { + $category = new Testcategory($categoryId); + echo json_encode( + array( + 'title' => $category->title, + 'value' => $category->id + ) + ); + } else { + echo -1; + } + break; default: echo ''; } diff --git a/main/inc/lib/exercise.lib.php b/main/inc/lib/exercise.lib.php index dd0676e0ac1..fd23b172638 100644 --- a/main/inc/lib/exercise.lib.php +++ b/main/inc/lib/exercise.lib.php @@ -5386,12 +5386,15 @@ public static function displayQuestionListByAttempt( } $question_list_answers = []; + $mediaList = []; $category_list = []; $loadChoiceFromSession = false; $fromDatabase = true; $exerciseResult = null; $exerciseResultCoordinates = null; $delineationResults = null; + $tempParentId = null; + $mediaCounter = 0; if (true === $save_user_result && in_array( $objExercise->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP] @@ -5581,16 +5584,38 @@ public static function displayQuestionListByAttempt( $question_content = ''; if ($show_results) { + $showMedia = false; $question_content = '
'; if (false === $showQuestionScore) { $score = []; } + /*if ($objQuestionTmp->parent_id != 0 && !in_array($objQuestionTmp->parent_id, $mediaList)) { + $showMedia = true; + $mediaList[] = $objQuestionTmp->parent_id; + }*/ + $counterToShow = $counter; + + if ($objQuestionTmp->parent_id != 0) { + + if (!in_array($objQuestionTmp->parent_id, $media_list)) { + $media_list[] = $objQuestionTmp->parent_id; + $show_media = true; + } + if ($tempParentId == $objQuestionTmp->parent_id) { + $mediaCounter++; + } else { + $mediaCounter = 0; + } + $counterToShow = chr(97 + $mediaCounter); + $tempParentId = $objQuestionTmp->parent_id; + } // Shows question title an description $question_content .= $objQuestionTmp->return_header( $objExercise, - $counter, - $score + $counterToShow, + $score, + $showMedia ); } $counter++;