diff --git a/assets/css/app.scss b/assets/css/app.scss
index 5d6286f08b5..f5334d39268 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -908,6 +908,12 @@ img.course-tool__icon {
}
}
+.media-group { border:2px solid #337ab7; background:#f5fafd; padding:1rem; margin:2rem 0; border-radius:4px; }
+.media-content { margin-bottom:1rem; }
+.media-description { font-style:italic; margin-bottom:1rem; }
+.media-children { margin-left:1rem; }
+.media-group h4 { margin-top:0; color:#23527c; }
+
@import "~@fancyapps/fancybox/dist/jquery.fancybox.css";
@import "~timepicker/jquery.timepicker.min.css";
@import "~qtip2/dist/jquery.qtip.min.css";
diff --git a/public/img/icons/22/media.png b/public/img/icons/22/media.png
new file mode 100644
index 00000000000..5aebce0f961
Binary files /dev/null and b/public/img/icons/22/media.png differ
diff --git a/public/img/icons/22/page_break.png b/public/img/icons/22/page_break.png
new file mode 100644
index 00000000000..426ad53ee6d
Binary files /dev/null and b/public/img/icons/22/page_break.png differ
diff --git a/public/img/icons/64/media.png b/public/img/icons/64/media.png
new file mode 100644
index 00000000000..113735ef4f2
Binary files /dev/null and b/public/img/icons/64/media.png differ
diff --git a/public/img/icons/64/page_break.png b/public/img/icons/64/page_break.png
new file mode 100644
index 00000000000..d41ae68dad6
Binary files /dev/null and b/public/img/icons/64/page_break.png differ
diff --git a/public/main/exercise/MediaQuestion.php b/public/main/exercise/MediaQuestion.php
new file mode 100644
index 00000000000..d42a36d5eec
--- /dev/null
+++ b/public/main/exercise/MediaQuestion.php
@@ -0,0 +1,74 @@
+type = MEDIA_QUESTION;
+ // Mark as content so it’s not counted towards score
+ $this->isContent = 1;
+ }
+
+ /**
+ * Form to create / edit a Media item.
+ */
+ public function createForm(&$form, $exercise)
+ {
+ // Title for the media block
+ $form->addText(
+ 'questionName',
+ get_lang('Media Title'),
+ false,
+ ['maxlength' => 255]
+ );
+
+ // WYSIWYG for the media content (could be text, embed code, etc.)
+ $editorConfig = [
+ 'ToolbarSet' => 'TestQuestionDescription',
+ 'Height' => '150'
+ ];
+ $form->addHtmlEditor(
+ 'questionDescription',
+ get_lang('Media Content'),
+ false,
+ false,
+ $editorConfig
+ );
+
+ global $text;
+ $form->addButtonSave($text, 'submitQuestion');
+
+ // Populate defaults if editing
+ $defaults = [
+ 'questionName' => $this->question,
+ 'questionDescription' => $this->description
+ ];
+ $form->setDefaults($defaults);
+ }
+
+ /**
+ * No answers to configure for media.
+ */
+ public function createAnswersForm($form) {}
+
+ public function processAnswersCreation($form, $exercise) {}
+
+ /**
+ * On save, treat like any other question: persist and attach to the exercise.
+ */
+ public function processCreation(FormValidator $form, Exercise $exercise)
+ {
+ $this->updateTitle($form->getSubmitValue('questionName'));
+ $this->updateDescription($form->getSubmitValue('questionDescription'));
+ $this->save($exercise);
+ $exercise->addToList($this->id);
+ }
+}
diff --git a/public/main/exercise/PageBreakQuestion.php b/public/main/exercise/PageBreakQuestion.php
new file mode 100644
index 00000000000..44a430bbc60
--- /dev/null
+++ b/public/main/exercise/PageBreakQuestion.php
@@ -0,0 +1,57 @@
+type = PAGE_BREAK;
+ $this->isContent = 1;
+ }
+
+ public function createForm(&$form, $exercise)
+ {
+ $form->addText(
+ 'questionName',
+ get_lang('Page Title'),
+ false,
+ ['maxlength' => 255]
+ );
+ $editorConfig = [
+ 'ToolbarSet' => 'TestQuestionDescription',
+ 'Height' => '100'
+ ];
+ $form->addHtmlEditor(
+ 'questionDescription',
+ get_lang('Page Introduction'),
+ false,
+ false,
+ $editorConfig
+ );
+
+ global $text;
+ $form->addButtonSave($text, 'submitQuestion');
+
+ $defaults = [
+ 'questionName' => $this->question,
+ 'questionDescription' => $this->description
+ ];
+ $form->setDefaults($defaults);
+ }
+
+ public function createAnswersForm($form) {}
+
+ public function processAnswersCreation($form, $exercise) {}
+
+ public function processCreation(FormValidator $form, Exercise $exercise)
+ {
+ $this->updateTitle($form->getSubmitValue('questionName'));
+ $this->updateDescription($form->getSubmitValue('questionDescription'));
+ $this->save($exercise);
+ $exercise->addToList($this->id);
+ }
+}
diff --git a/public/main/exercise/exercise.class.php b/public/main/exercise/exercise.class.php
index 32dfdfc09be..dcb0f27449c 100644
--- a/public/main/exercise/exercise.class.php
+++ b/public/main/exercise/exercise.class.php
@@ -3143,6 +3143,19 @@ public function save_stat_track_exercise_info(
$clock_expired_time = null;
}
+ $questionList = array_filter(
+ $questionList,
+ function (int $qid) {
+ $q = Question::read($qid);
+ return $q
+ && !in_array(
+ $q->type,
+ [PAGE_BREAK, MEDIA_QUESTION],
+ true
+ );
+ }
+ );
+
$questionList = array_map('intval', $questionList);
$em = Database::getManager();
@@ -5885,6 +5898,13 @@ public function manage_answer(
// Store results directly in the database
// For all in one page exercises, the results will be
// stored by exercise_results.php (using the session)
+ if (in_array(
+ $objQuestionTmp->type,
+ [PAGE_BREAK, MEDIA_QUESTION],
+ true
+ )) {
+ $save_results = false;
+ }
if ($save_results) {
if ($debug) {
error_log("Save question results $save_results");
diff --git a/public/main/exercise/exercise_show.php b/public/main/exercise/exercise_show.php
index 5597c455f87..4ab956c7227 100644
--- a/public/main/exercise/exercise_show.php
+++ b/public/main/exercise/exercise_show.php
@@ -404,6 +404,7 @@ function getFCK(vals, marksid) {
$marksid = '';
$countPendingQuestions = 0;
+$panelsByParent = [];
foreach ($questionList as $questionId) {
$choice = isset($exerciseResult[$questionId]) ? $exerciseResult[$questionId] : '';
// destruction of the Question object
@@ -419,6 +420,11 @@ function getFCK(vals, marksid) {
$questionWeighting = $objQuestionTmp->selectWeighting();
$answerType = $objQuestionTmp->selectType();
+ $objQ = Question::read($questionId, $objExercise->course);
+ if (!$objQ || $objQ->type === MEDIA_QUESTION) {
+ continue;
+ }
+
// Start buffer
ob_start();
if (MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE == $answerType) {
@@ -882,7 +888,10 @@ class="exercise_mark_select"
$counter++;
$questionContent .= $contents;
$questionContent .= '';
- $exercise_content .= Display::panel($questionContent);
+ //$exercise_content .= Display::panel($questionContent);
+ $panelHtml = Display::panel($questionContent);
+ $parentId = (int) $objQ->parent_id;
+ $panelsByParent[$parentId][] = $panelHtml;
} // end of large foreach on questions
// Display the text when finished message if we are on a LP #4227
@@ -966,7 +975,44 @@ class="exercise_mark_select"
}
echo $totalScoreText;
-echo $exercise_content;
+//echo $exercise_content;
+foreach ($panelsByParent as $pid => $panels) {
+ if ($pid !== 0) {
+ $mediaQ = Question::read($pid, $objExercise->course);
+ echo '
';
+ }
+}
// only show "score" in bottom of page if there's exercise content
if ($show_results) {
diff --git a/public/main/exercise/exercise_submit.php b/public/main/exercise/exercise_submit.php
index 84026cdae43..dca92d29a69 100644
--- a/public/main/exercise/exercise_submit.php
+++ b/public/main/exercise/exercise_submit.php
@@ -56,23 +56,8 @@
$htmlHeadXtra[] = '';
$htmlHeadXtra[] = api_get_js('jquery.highlight.js');
}
-
-//$js = '';
-//$htmlHeadXtra[] = $js;
-
-//$htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js');
-//$htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js');
-//$htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js');
-
-//This library is necessary for the time control feature
-//tmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/stylesheet/jquery.epiclock.css');
-//$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
-//$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
-//$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
-//$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
$htmlHeadXtra[] = api_get_build_js('legacy_exercise.js');
$htmlHeadXtra[] = '';
-//$htmlHeadXtra[] = '';
if ('true' === api_get_setting('exercise.quiz_prevent_copy_paste')) {
$htmlHeadXtra[] = '';
}
@@ -130,6 +115,8 @@
$questionCategoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
$current_question = $currentQuestionFromUrl = isset($_REQUEST['num']) ? (int) $_REQUEST['num'] : null;
$currentAnswer = isset($_REQUEST['num_answer']) ? (int) $_REQUEST['num_answer'] : null;
+$page = isset($_REQUEST['page']) ? (int) $_REQUEST['page'] : 1;
+$currentBreakId = isset($_REQUEST['currentBreakId']) ? (int) $_REQUEST['currentBreakId'] : null;
$logInfo = [
'tool' => TOOL_QUIZ,
@@ -378,6 +365,27 @@
// Fix in order to get the correct question list.
$questionListUncompressed = $objExercise->getQuestionListWithMediasUncompressed();
Session::write('question_list_uncompressed', $questionListUncompressed);
+
+if (ONE_PER_PAGE == $objExercise->type) {
+ $filtered = [];
+ foreach ($questionListUncompressed as $qid) {
+ $q = Question::read($qid);
+ if (
+ $q
+ && $q->type !== PAGE_BREAK
+ && $q->type !== MEDIA_QUESTION
+ ) {
+ $filtered[] = $qid;
+ }
+ }
+ $questionListUncompressed = $filtered;
+ Session::write('question_list_uncompressed', $questionListUncompressed);
+
+ if (Session::read('questionList') !== null) {
+ Session::write('questionList', $filtered);
+ }
+}
+
$clock_expired_time = null;
if (empty($exercise_stat_info)) {
$disable = ('true' === api_get_setting('exercise.exercises_disable_new_attempts'));
@@ -447,22 +455,10 @@
if (!empty($resolvedQuestions) &&
!empty($exercise_stat_info['data_tracking'])
) {
- /*$last = current(end($resolvedQuestions));
- $attemptQuestionList = explode(',', $exercise_stat_info['data_tracking']);
- $count = 1;
- foreach ($attemptQuestionList as $question) {
- if ($last['question_id'] == $question) {
- break;
- }
- $count++;
- }
- $current_question = $count;
- */
// Get current question based in data_tracking question list, instead of track_e_attempt order BT#17789.
$resolvedQuestionsQuestionIds = array_keys($resolvedQuestions);
$count = 0;
$attemptQuestionList = explode(',', $exercise_stat_info['data_tracking']);
- //var_dump($attemptQuestionList, $resolvedQuestionsQuestionIds);
foreach ($attemptQuestionList as $index => $question) {
if (in_array($question, $resolvedQuestionsQuestionIds)) {
$count = $index;
@@ -470,7 +466,6 @@
}
}
$current_question = $count;
- //var_dump($current_question, $index);exit;
}
}
}
@@ -493,11 +488,13 @@
// Selects the list of question ID
$questionList = $objExercise->getQuestionList();
- // Media questions.
- $media_is_activated = $objExercise->mediaIsActivated();
+ $questionList = array_filter($questionList, function(int $qid) {
+ $q = Question::read($qid);
+ return $q && $q->type !== MEDIA_QUESTION;
+ });
// Getting order from random
- if (false == $media_is_activated &&
+ if (
(
$objExercise->isRandom() ||
!empty($objExercise->getRandomByCategory()) ||
@@ -507,6 +504,10 @@
!empty($exercise_stat_info['data_tracking'])
) {
$questionList = explode(',', $exercise_stat_info['data_tracking']);
+ $questionList = array_filter($questionList, function(int $qid) {
+ $q = Question::read($qid);
+ return $q && $q->type !== MEDIA_QUESTION;
+ });
$categoryList = [];
if ($allowBlockCategory) {
foreach ($questionList as $question) {
@@ -531,7 +532,11 @@
$myRemindList = array_filter($myRemindList);
}
-$params = "exe_id=$exe_id&exerciseId=$exerciseId&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id&".api_get_cidreq().'&reminder='.$reminder;
+$params = "exe_id=$exe_id&exerciseId=$exerciseId&learnpath_id=$learnpath_id"
+ . "&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id"
+ . "&page=" . ($page ?? 1)
+ . "&" . api_get_cidreq();
+
if (2 === $reminder && empty($myRemindList)) {
if ($debug) {
@@ -638,6 +643,102 @@
}
}
+// Remove any leading page breaks
+while (count($questionList) > 0) {
+ // reset() moves the internal pointer to the first element…
+ $firstId = reset($questionList);
+ // …key() returns its key, so we can unset by that key
+ $firstKey = key($questionList);
+ $q = Question::read((int) $firstId);
+ if ($q && $q->type === PAGE_BREAK) {
+ unset($questionList[$firstKey]);
+ } else {
+ // stop once the first element is not a page break
+ break;
+ }
+}
+
+// Remove any trailing page breaks
+while (count($questionList) > 0) {
+ // end() moves the internal pointer to the last element
+ $lastId = end($questionList);
+ $lastKey = key($questionList);
+ $q = Question::read((int) $lastId);
+ if ($q && $q->type === PAGE_BREAK) {
+ unset($questionList[$lastKey]);
+ } else {
+ // stop once the last element is not a page break
+ break;
+ }
+}
+
+$hasMediaWithChildren = false;
+$mediaQids = array_filter($objExercise->getQuestionOrderedList(), function(int $qid) {
+ $q = Question::read($qid);
+ return $q && $q->type === MEDIA_QUESTION;
+});
+
+if (!empty($mediaQids)) {
+ foreach ($questionListUncompressed as $qid) {
+ $q = Question::read($qid);
+ if ($q && in_array($q->parent_id, $mediaQids, true)) {
+ $hasMediaWithChildren = true;
+ break;
+ }
+ }
+}
+
+$forceGrouped = (ONE_PER_PAGE === $objExercise->type && $hasMediaWithChildren);
+if ($forceGrouped) {
+ $objExercise->type = ALL_ON_ONE_PAGE;
+}
+
+if (ALL_ON_ONE_PAGE === $objExercise->type || $forceGrouped) {
+ $flat = array_filter($questionList, function(int $qid) {
+ $q = Question::read($qid);
+ return $q && $q->type !== MEDIA_QUESTION;
+ });
+
+ if ($hasMediaWithChildren) {
+ $groups = [];
+ $seen = [];
+ foreach ($flat as $qid) {
+ $q = Question::read($qid);
+ if ($q->parent_id > 0) {
+ $pid = $q->parent_id;
+ $groups[$pid]['questions'][] = $qid;
+ $seen[$qid] = true;
+ }
+ }
+ foreach ($flat as $qid) {
+ if (!isset($seen[$qid])) {
+ $groups[$qid]['questions'] = [$qid];
+ }
+ }
+ $pages = array_values($groups);
+ $totalPages = count($pages);
+ $page = min(max(1, $page), $totalPages);
+ $questionList = $pages[$page - 1]['questions'];
+ $currentBreakId = null;
+ } else {
+ $pages = [[]];
+ $breakIds = [null];
+ foreach ($flat as $qid) {
+ $q = Question::read($qid);
+ if ($q->type === PAGE_BREAK) {
+ $pages[] = [];
+ $breakIds[] = $qid;
+ } else {
+ $pages[count($pages) - 1][] = $qid;
+ }
+ }
+ $totalPages = count($pages);
+ $page = min(max(1, $page), $totalPages);
+ $questionList = $pages[$page - 1];
+ $currentBreakId = ($page > 1 ? $breakIds[$page - 1] : null);
+ }
+}
+
$isLastQuestionInCategory = 0;
if ($allowBlockCategory &&
ONE_PER_PAGE == $objExercise->type &&
@@ -683,13 +784,6 @@
$count++;
}
- //var_dump($questionCheck);exit;
- // Use reminder list to get the current question.
- /*if (2 === $reminder && !empty($myRemindList)) {
- $remindQuestionId = current($myRemindList);
- $questionCheck = Question::read($remindQuestionId);
- }*/
-
$categoryId = 0;
if (null !== $questionCheck) {
$categoryId = $questionCheck->category;
@@ -698,12 +792,10 @@
if ($objExercise->review_answers && isset($_GET['category_id'])) {
$categoryId = $_GET['category_id'] ?? 0;
}
- //var_dump($categoryId, $categoryList);
if (!empty($categoryId)) {
$categoryInfo = $categoryList[$categoryId];
$count = 1;
$total = count($categoryList[$categoryId]);
- //var_dump($questionCheck);
foreach ($categoryList[$categoryId] as $checkQuestionId) {
if ((int) $checkQuestionId === (int) $questionCheck->iid) {
break;
@@ -711,7 +803,6 @@
$count++;
}
- //var_dump($count , $total);
if ($count === $total) {
$isLastQuestionInCategory = $categoryId;
if ($isLastQuestionInCategory) {
@@ -731,7 +822,6 @@
// $isLastQuestionInCategory = $categoryId;
}
}
- //var_dump($categoryId, $blockedCategories, $isLastQuestionInCategory);
// Blocked if category was already answered.
if ($categoryId && in_array($categoryId, $blockedCategories)) {
@@ -1334,6 +1424,43 @@ function updateDuration() {
}
}
+ var page = '.(int) $page.';
+ var totalPages = '.(int) ($totalPages ?? 1).';
+ function navigateNext() {
+ var url;
+ if (page === totalPages) {
+ url = "exercise_result.php?'.api_get_cidreq().'&exe_id='.$exe_id.'&learnpath_id='.$learnpath_id.'&learnpath_item_id='.$learnpath_item_id.'&learnpath_item_view_id='.$learnpath_item_view_id.'";
+ } else {
+ url = "'.api_get_self().'?'.api_get_cidreq().'&exerciseId='.$exerciseId.'&page=" + (page + 1) + "&reminder='.$reminder.'";
+ }
+ window.location = url;
+ }
+
+ function save_question_list(question_list) {
+ if (!question_list.length) {
+ return navigateNext();
+ }
+ var saves = $.map(question_list, function(qid) {
+ var my_choice = $(\'*[name*="choice[\'+qid+\']"]\').serialize();
+ var remind_list = $(\'*[name*="remind_list"]\').serialize();
+ var hotspot = $(\'*[name*="hotspot[\'+qid+\']"]\').serialize();
+ var dc = $(\'*[name*="choiceDegreeCertainty[\'+qid+\']"]\').serialize();
+ var dataStr = "'.$params.'&type=simple&question_id="+qid
+ +"&"+my_choice
+ + (hotspot ? "&"+hotspot : "")
+ + (remind_list ? "&"+remind_list : "")
+ + (dc ? "&"+dc : "");
+ return $.ajax({
+ type: "POST",
+ url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=save_exercise_by_now",
+ data: dataStr
+ });
+ });
+ $.when.apply($, saves).always(function(){
+ navigateNext();
+ });
+ }
+
$(function() {
'.$questionTimeCondition.'
//This pre-load the save.png icon
@@ -1376,20 +1503,27 @@ function updateDuration() {
$(\'button[name="save_question_list"]\').on(\'touchstart click\', function (e) {
e.preventDefault();
e.stopPropagation();
- var $this = $(this);
- var questionList = $this.data(\'list\').split(",");
+ var $btn = $(this);
+
+ $(\'button[name="save_question_list"]\').prop(\'disabled\', true);
+ $btn.append(\' ' . addslashes($loading) . '\');
- save_question_list(questionList);
+ var listStr = $btn.data(\'list\') || \'\';
+ if (!listStr) {
+ return navigateNext();
+ }
+ var arr = listStr.toString().split(\',\');
+ save_question_list(arr);
});
- $(\'button[name="check_answers"]\').on(\'touchstart click\', function (e) {
- e.preventDefault();
- e.stopPropagation();
- var $this = $(this);
- var questionId = parseInt($this.data(\'question\')) || 0;
+ $(\'button[name="check_answers"]\').on(\'touchstart click\', function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ var $this = $(this);
+ var questionId = parseInt($this.data(\'question\')) || 0;
- save_now(questionId, "check_answers");
- });
+ save_now(questionId, "check_answers");
+ });
$(\'button[name="save_now"]\').on(\'touchstart click\', function (e) {
e.preventDefault();
@@ -1421,26 +1555,12 @@ function previous_question(question_num) {
}
function previous_question_and_save(previous_question_id, question_id_to_save) {
- var url = "exercise_submit.php?'.$params.'&num="+previous_question_id;
- //Save the current question
- save_now(question_id_to_save, url);
- }
-
- function save_question_list(question_list) {
- $.each(question_list, function(key, question_id) {
- save_now(question_id, null);
- });
-
- var url = "";
- if ('.$reminder.' == 1 ) {
- url = "exercise_reminder.php?'.$params.'&num='.$current_question.'";
- } else if ('.$reminder.' == 2 ) {
- url = "exercise_submit.php?'.$params.'&num='.$current_question.'&remind_question_id='.$remind_question_id.'&reminder=2";
- } else {
- url = "exercise_submit.php?'.$params.'&num='.$current_question.'&remind_question_id='.$remind_question_id.'";
- }
+ save_now(question_id_to_save, null);
+ var url = \'exercise_submit.php?'. api_get_cidreq() .'&exerciseId='. $exerciseId .'&page=\'
+ + previous_question_id
+ + \'&reminder='. $reminder .'\';
window.location = url;
- }
+ }
function redirectExerciseToResult()
{
@@ -1632,9 +1752,12 @@ function validate_all() {
window.quizTimeEnding = false;
';
- echo '';
if (!in_array($origin, ['learnpath', 'embeddable'])) {
diff --git a/public/main/exercise/question.class.php b/public/main/exercise/question.class.php
index cc54ef952d8..33a8fbfd581 100644
--- a/public/main/exercise/question.class.php
+++ b/public/main/exercise/question.class.php
@@ -68,9 +68,10 @@ abstract class Question
UNIQUE_ANSWER_IMAGE => ['UniqueAnswerImage.php', 'UniqueAnswerImage'],
DRAGGABLE => ['Draggable.php', 'Draggable'],
MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable'],
- //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
+ MEDIA_QUESTION => ['MediaQuestion.php', 'MediaQuestion'],
ANNOTATION => ['Annotation.php', 'Annotation'],
READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
+ PAGE_BREAK => ['PageBreakQuestion.php', 'PageBreakQuestion'],
];
/**
@@ -211,6 +212,10 @@ public static function read($id, $course_info = [], $getExerciseList = true)
}
}
+ $objQuestion->parent_id = isset($object->parent_media_id)
+ ? (int) $object->parent_media_id
+ : 0;
+
return $objQuestion;
}
}
@@ -544,7 +549,8 @@ public function save($exercise)
->setType($this->type)
->setExtra($this->extra)
->setLevel((int) $this->level)
- ->setFeedback($this->feedback);
+ ->setFeedback($this->feedback)
+ ->setParentMediaId($this->parent_id);
if (!empty($categoryId)) {
$category = $questionCategoryRepo->find($categoryId);
@@ -586,13 +592,9 @@ public function save($exercise)
->setExtra($this->extra)
->setLevel((int) $this->level)
->setFeedback($this->feedback)
+ ->setParentMediaId($this->parent_id)
->setParent($courseEntity)
- ->addCourseLink(
- $courseEntity,
- api_get_session_entity(),
- api_get_group_entity()
- )
- ;
+ ->addCourseLink($courseEntity, api_get_session_entity(), api_get_group_entity());
$em->persist($question);
$em->flush();
@@ -843,6 +845,7 @@ public function addToList($exerciseId, $fromSave = false)
public function removeFromList($exerciseId, $courseId = 0)
{
$table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
+ $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
$id = (int) $this->id;
$exerciseId = (int) $exerciseId;
@@ -881,6 +884,11 @@ public function removeFromList($exerciseId, $courseId = 0)
quiz_id = $exerciseId";
Database::query($sql);
+ $reset = "UPDATE $tableQuestion
+ SET parent_media_id = NULL
+ WHERE parent_media_id = $id";
+ Database::query($reset);
+
return true;
}
}
@@ -936,6 +944,11 @@ public function delete($deleteFromEx = 0)
}
}
+ $reset = "UPDATE $TBL_QUESTIONS
+ SET parent_media_id = NULL
+ WHERE parent_media_id = $id";
+ Database::query($reset);
+
$sql = "DELETE FROM $TBL_EXERCISE_QUESTION
WHERE question_id = ".$id;
Database::query($sql);
@@ -1260,6 +1273,14 @@ public function createForm(&$form, $exercise)
get_lang('Category'),
TestCategory::getCategoriesIdAndName()
);
+
+ $courseMedias = self::prepare_course_media_select($exercise->iId);
+ $form->addSelect(
+ 'parent_id',
+ get_lang('Attach to media'),
+ $courseMedias
+ );
+
if (EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $exercise->getQuestionSelectionType() &&
('true' === api_get_setting('exercise.allow_mandatory_question_in_category'))
) {
@@ -1310,9 +1331,6 @@ public function createForm(&$form, $exercise)
break;
}
- //Medias
- //$course_medias = self::prepare_course_media_select(api_get_course_int_id());
- //$form->addSelect('parent_id', get_lang('Attach to media'), $course_medias);
}
$form->addElement('html', '');
@@ -1356,13 +1374,15 @@ public function createForm(&$form, $exercise)
$extraField->addElements($form, $this->iid);
// default values
- $defaults = [];
- $defaults['questionName'] = $this->question;
- $defaults['questionDescription'] = $this->description;
- $defaults['questionLevel'] = $this->level;
- $defaults['questionCategory'] = $this->category;
- $defaults['feedback'] = $this->feedback;
- $defaults['mandatory'] = $this->mandatory;
+ $defaults = [
+ 'questionName' => $this->question,
+ 'questionDescription' => $this->description,
+ 'questionLevel' => $this->level,
+ 'questionCategory' => $this->category,
+ 'feedback' => $this->feedback,
+ 'mandatory' => $this->mandatory,
+ 'parent_id' => $this->parent_id,
+ ];
// Came from he question pool
if (isset($_GET['fromExercise'])) {
@@ -1387,6 +1407,7 @@ public function createForm(&$form, $exercise)
*/
public function processCreation(FormValidator $form, Exercise $exercise)
{
+ $this->parent_id = (int) $form->getSubmitValue('parent_id');
$this->updateTitle($form->getSubmitValue('questionName'));
$this->updateDescription($form->getSubmitValue('questionDescription'));
$this->updateLevel($form->getSubmitValue('questionLevel'));
@@ -1896,19 +1917,35 @@ public static function get_count_course_medias($course_id)
*
* @return array
*/
- public static function prepare_course_media_select($course_id)
+ public static function prepare_course_media_select(int $quizId): array
{
- $medias = self::get_course_medias($course_id);
- $media_list = [];
- $media_list[0] = get_lang('Not linked to media');
+ $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
+ $tableRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
- if (!empty($medias)) {
- foreach ($medias as $media) {
- $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
- }
+ $medias = Database::select(
+ '*',
+ "$tableQuestion q
+ JOIN $tableRelQuestion rq ON rq.question_id = q.iid",
+ [
+ 'where' => [
+ 'rq.quiz_id = ? AND (q.parent_media_id IS NULL OR q.parent_media_id = 0) AND q.type = ?'
+ => [$quizId, MEDIA_QUESTION],
+ ],
+ 'order' => 'question ASC',
+ ]
+ );
+
+ $mediaList = [
+ 0 => get_lang('Not linked to media'),
+ ];
+
+ foreach ($medias as $media) {
+ $mediaList[$media['question_id']] = empty($media['question'])
+ ? get_lang('Untitled')
+ : $media['question'];
}
- return $media_list;
+ return $mediaList;
}
/**
diff --git a/public/main/exercise/question_list_admin.inc.php b/public/main/exercise/question_list_admin.inc.php
index ec9fc38a97f..ca5f9db4179 100644
--- a/public/main/exercise/question_list_admin.inc.php
+++ b/public/main/exercise/question_list_admin.inc.php
@@ -127,6 +127,15 @@
selectType() === ONE_PER_PAGE
+ && $objExercise->hasQuestionWithType(PAGE_BREAK)
+) {
+ echo Display::return_message(
+ get_lang('This exercise contains page-break questions, which will only take effect if the exercise is set to “All questions on one page".'),
+ 'warning'
+ );
+}
+
// Filter the type of questions we can add
Question::displayTypeMenu($objExercise);
diff --git a/public/main/inc/lib/api.lib.php b/public/main/inc/lib/api.lib.php
index a855f1a7071..9cca9b5cc63 100644
--- a/public/main/inc/lib/api.lib.php
+++ b/public/main/inc/lib/api.lib.php
@@ -494,6 +494,7 @@
define('ANNOTATION', 20);
define('READING_COMPREHENSION', 21);
define('MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY', 22);
+define('PAGE_BREAK', 23);
define('EXERCISE_CATEGORY_RANDOM_SHUFFLED', 1);
define('EXERCISE_CATEGORY_RANDOM_ORDERED', 2);
diff --git a/public/main/inc/lib/events.lib.php b/public/main/inc/lib/events.lib.php
index 95c53fdddb3..7d0af55ccb4 100644
--- a/public/main/inc/lib/events.lib.php
+++ b/public/main/inc/lib/events.lib.php
@@ -368,6 +368,21 @@ public static function updateEventExercise(
$questionsList = array_map('intval', $questionsList);
}
+ if (!empty($questionsList)) {
+ $questionsList = array_map('intval', $questionsList);
+ $questionsList = array_filter(
+ $questionsList,
+ function (int $qid) {
+ $q = Question::read($qid);
+ return $q && !in_array(
+ $q->type,
+ [PAGE_BREAK, MEDIA_QUESTION],
+ true
+ );
+ }
+ );
+ }
+
if (!empty($remindList)) {
$remindList = array_map('intval', $remindList);
$remindList = array_filter($remindList);
diff --git a/public/main/inc/lib/exercise.lib.php b/public/main/inc/lib/exercise.lib.php
index c1ef62169c5..819d18f7d99 100644
--- a/public/main/inc/lib/exercise.lib.php
+++ b/public/main/inc/lib/exercise.lib.php
@@ -76,6 +76,25 @@ public static function showQuestion(
}
$answerType = $objQuestionTmp->selectType();
+
+ if (MEDIA_QUESTION === $answerType) {
+ $mediaHtml = $objQuestionTmp->selectDescription();
+ if (!empty($mediaHtml)) {
+ echo ''. $mediaHtml .'
';
+ }
+ return 0;
+ }
+
+ if (PAGE_BREAK === $answerType) {
+ $description = $objQuestionTmp->selectDescription();
+ if (!$only_questions && !empty($description)) {
+ echo ''
+ . $description .
+ '
';
+ }
+ return 0;
+ }
+
$s = '';
if (HOT_SPOT != $answerType &&
HOT_SPOT_DELINEATION != $answerType &&
@@ -4426,6 +4445,7 @@ public static function displayQuestionListByAttempt(
$countPendingQuestions = 0;
$result = [];
+ $panelsByParent = [];
// Loop over all question to show results for each of them, one by one
if (!empty($question_list)) {
foreach ($question_list as $questionId) {
@@ -4610,15 +4630,43 @@ public static function displayQuestionListByAttempt(
$calculatedScore['question_content'] = $questionContent;
$attemptResult[] = $calculatedScore;
+ $parentId = intval($objQuestionTmp->parent_id ?: 0);
+ $panelsByParent[$parentId][] = Display::panel($questionContent);
+ }
- if ($objExercise->showExpectedChoice()) {
- $exerciseContent .= Display::panel($questionContent);
- } else {
- // $show_all_but_expected_answer should not happen at
- // the same time as $show_results
- if ($show_results && !$show_only_score) {
- $exerciseContent .= Display::panel($questionContent);
+ foreach ($panelsByParent as $pid => $panels) {
+ if ($pid !== 0) {
+ $mediaQ = Question::read($pid, $objExercise->course);
+ echo '';
}
}
}
@@ -4709,7 +4757,6 @@ public static function displayQuestionListByAttempt(
$exerciseContent
);
- echo $totalScoreText;
echo $certificateBlock;
// Ofaj change BT#11784
diff --git a/public/main/template/default/exercise/partials/result_exercise.html.twig b/public/main/template/default/exercise/partials/result_exercise.html.twig
index e331f809d4a..5f25cef0bc7 100644
--- a/public/main/template/default/exercise/partials/result_exercise.html.twig
+++ b/public/main/template/default/exercise/partials/result_exercise.html.twig
@@ -2,125 +2,116 @@
{% autoescape false %}
-
-
+
+
{% if 'editor.save_titles_as_html'|api_get_setting == 'true' %}
- {{ data.title }}
+
{{ data.title|raw }}
{% else %}
-
{{ data.title }}
+
{{ data.title }}
{% endif %}
-
-
-
-

+
+
+
+
-
-
{{ data.name_url }}
-
+
+
{{ data.name_url|raw }}
{% if signature %}
-

+

{% endif %}
-
-
-
-
{{ 'Username'|trans }}
- {{ 'ToolIcon::MEMBER' | mdi_icon }} {{ data.username }}
+
+
+
+ {{ 'ToolIcon::MEMBER'|mdi_icon }}
+ {{ 'Username'|trans }}:
+ {{ data.username }}
{% if data.start_date %}
-
-
{{ 'Start date'|trans }}
- {{ 'ToolIcon::AGENDA' | mdi_icon }}
- {{ data.start_date }}
+
+ {{ 'ToolIcon::AGENDA'|mdi_icon }}
+ {{ 'Start date'|trans }}:
+ {{ data.start_date }}
{% endif %}
{% if data.duration %}
-
-
{{ 'Duration'|trans }}
- {{ 'alarm' | mdi_icon }}
- {{ data.duration }}
+
+ {{ 'alarm'|mdi_icon }}
+ {{ 'Duration'|trans }}:
+ {{ data.duration }}
{% endif %}
{% if data.ip %}
-
-
{{ 'IP'|trans }}
- {{ 'laptop' | mdi_icon }}
- {{ data.ip }}
+
+ {{ 'laptop'|mdi_icon }}
+ {{ 'IP'|trans }}:
+ {{ data.ip }}
{% endif %}
-
+
+
-
-
+
+
+
{% if data.number_of_answers_saved != data.number_of_answers %}
-
- {{ '%d / %d answers saved.'|trans|format(data.number_of_answers_saved, data.number_of_answers) }}
-
+
+ {{ '%d / %d answers saved.'|trans|format(data.number_of_answers_saved, data.number_of_answers) }}
+
{% else %}
-
- {{ '%d / %d answers saved.'|trans|format(data.number_of_answers_saved, data.number_of_answers) }}
-
- {% endif %}
-
- {% if 'exercise.quiz_confirm_saved_answers'|api_get_setting == 'true' %}
- {% set enable_form = data.track_confirmation.updatedAt is empty and data.track_confirmation.userId == _u.id %}
-
+
+ {{ '%d / %d answers saved.'|trans|format(data.number_of_answers_saved, data.number_of_answers) }}
+
{% endif %}
+ {% if 'exercise.quiz_confirm_saved_answers'|api_get_setting == 'true' %}
+ {% set enable_form = data.track_confirmation.updatedAt is empty and data.track_confirmation.userId == _u.id %}
+
+ {% endif %}