Browse Source

Minor - 1.11.x fixes

Julio Montoya 6 years ago
parent
commit
f78b85b4bd

+ 0 - 1
main/exercise/MatchingDraggable.php

@@ -272,7 +272,6 @@ class MatchingDraggable extends Question
             $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
             $header .= '<th>'.get_lang('Status').'</th>';
         } else {
-            $header .= '<th>'.get_lang('ElementList').'</th>';
             $header .= '<th>'.get_lang('CorrespondsTo').'</th>';
         }
         $header .= '</tr>';

+ 17 - 12
main/exercise/TestCategory.php

@@ -22,6 +22,8 @@ class TestCategory
      */
     public function __construct()
     {
+        $this->name = '';
+        $this->description = '';
     }
 
     /**
@@ -73,15 +75,15 @@ class TestCategory
 
         // check if name already exists
         $sql = "SELECT count(*) AS nb FROM $table
-                WHERE title = '".$this->name."' AND c_id = $courseId";
+                WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
         $result = Database::query($sql);
         $row = Database::fetch_array($result);
         // lets add in BDD if not the same name
         if ($row['nb'] <= 0) {
             $params = [
                 'c_id' => $courseId,
-                'title' => (string) $this->name,
-                'description' => (string) $this->description,
+                'title' => $this->name,
+                'description' => $this->description,
             ];
             $newId = Database::insert($table, $params);
 
@@ -116,7 +118,7 @@ class TestCategory
     {
         $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
         $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
-        $id = intval($id);
+        $id = (int) $id;
         $course_id = api_get_course_int_id();
         $category = $this->getCategory($id);
 
@@ -130,9 +132,9 @@ class TestCategory
                     WHERE category_id = $id AND c_id=".$course_id;
             Database::query($sql);
             // item_property update
-            $course_info = api_get_course_info_by_id($course_id);
+            $courseInfo = api_get_course_info_by_id($course_id);
             api_item_property_update(
-                $course_info,
+                $courseInfo,
                 TOOL_TEST_CATEGORY,
                 $this->id,
                 'TestCategoryDeleted',
@@ -193,7 +195,7 @@ class TestCategory
     public function getCategoryQuestionsNumber()
     {
         $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
-        $id = intval($this->id);
+        $id = (int) $this->id;
         $sql = "SELECT count(*) AS nb
                 FROM $table
                 WHERE category_id = $id AND c_id=".api_get_course_int_id();
@@ -548,7 +550,7 @@ class TestCategory
         $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
         $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
         $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
-        $exerciseId = intval($exerciseId);
+        $exerciseId = (int) $exerciseId;
         $courseId = api_get_course_int_id();
 
         $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
@@ -719,9 +721,9 @@ class TestCategory
     {
         $tbl_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
         $tbl_question_rel_category = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
-        $in_cat_id = intval($in_cat_id);
-        $in_exe_id = intval($in_exe_id);
-        $in_user_id = intval($in_user_id);
+        $in_cat_id = (int) $in_cat_id;
+        $in_exe_id = (int) $in_exe_id;
+        $in_user_id = (int) $in_user_id;
 
         $query = "SELECT DISTINCT
                         marks, 
@@ -933,12 +935,15 @@ class TestCategory
         $courseId = (int) $courseId;
         $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
         $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+        $exercise->id = (int) $exercise->id;
+
         $sql = "SELECT * FROM $table qc
                 LEFT JOIN $categoryTable c
                 ON (qc.c_id = c.c_id AND c.id = qc.category_id)
                 WHERE qc.c_id = $courseId AND exercise_id = {$exercise->id} ";
 
         if (!empty($order)) {
+            $order = Database::escape_string($order);
             $sql .= "ORDER BY $order";
         }
 
@@ -1154,7 +1159,7 @@ class TestCategory
         if (empty($courseId)) {
             $courseId = api_get_course_int_id();
         }
-        $courseId = intval($courseId);
+        $courseId = (int) $courseId;
         $tbl_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
         $sql = "SELECT id FROM $tbl_cat
                 WHERE c_id = $courseId AND title = '".Database::escape_string($title)."'";

+ 1 - 1
main/exercise/UniqueAnswerImage.php

@@ -54,7 +54,7 @@ class UniqueAnswerImage extends UniqueAnswer
 
         $html = '<div class="alert alert-success" role="alert">'.
                 get_lang('UniqueAnswerImagePreferredSize200x150').'</div>';
-        $html .= '<table class="table table-hover">
+        $html .= '<table class="table table-striped table-hover">
             <thead>
                 <tr style="text-align: center;">
                     <th width="10">'.get_lang('Number').'</th>

+ 36 - 34
main/exercise/admin.php

@@ -56,13 +56,13 @@ api_protect_course_script(true);
 $is_allowedToEdit = api_is_allowed_to_edit(null, true, false, false);
 $sessionId = api_get_session_id();
 $studentViewActive = api_is_student_view_active();
+$showPagination = api_get_configuration_value('show_question_pagination');
+
 if (!$is_allowedToEdit) {
     api_not_allowed(true);
 }
 
-if (empty($exerciseId)) {
-    $exerciseId = isset($_GET['exerciseId']) ? intval($_GET['exerciseId']) : '0';
-}
+$exerciseId = isset($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : '0';
 
 /*  stripslashes POST data  */
 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
@@ -79,11 +79,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
 }
 
 $newQuestion = isset($_GET['newQuestion']) ? $_GET['newQuestion'] : 0;
-
-if (empty($modifyAnswers)) {
-    $modifyAnswers = isset($_GET['modifyAnswers']) ? $_GET['modifyAnswers'] : 0;
-}
-
+$modifyAnswers = isset($_GET['modifyAnswers']) ? $_GET['modifyAnswers'] : 0;
 $editQuestion = isset($_GET['editQuestion']) ? $_GET['editQuestion'] : 0;
 
 if (empty($modifyQuestion)) {
@@ -167,7 +163,12 @@ if (!is_object($objExercise)) {
 
     // creation of a new exercise if wrong or not specified exercise ID
     if ($exerciseId) {
-        $objExercise->read($exerciseId);
+        $parseQuestionList = $showPagination > 0 ? false : true;
+        if ($editQuestion) {
+            $parseQuestionList = false;
+            $showPagination = true;
+        }
+        $objExercise->read($exerciseId, $parseQuestionList);
     }
     // saves the object into the session
     Session::write('objExercise', $objExercise);
@@ -186,7 +187,7 @@ if (!$fromExercise) {
     }
 }
 
-$nbrQuestions = $objExercise->selectNbrQuestions();
+$nbrQuestions = $objExercise->getQuestionCount();
 
 // Question object creation.
 if ($editQuestion || $newQuestion || $modifyQuestion || $modifyAnswers) {
@@ -280,24 +281,24 @@ if (api_is_in_gradebook()) {
     ];
 }
 
-$interbreadcrumb[] = ["url" => "exercise.php?".api_get_cidreq(), 'name' => get_lang('Exercises')];
+$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
 if (isset($_GET['newQuestion']) || isset($_GET['editQuestion'])) {
     $interbreadcrumb[] = [
-        "url" => "admin.php?exerciseId=".$objExercise->id.'&'.api_get_cidreq(),
-        "name" => $objExercise->selectTitle(true),
+        'url' => "admin.php?exerciseId=".$objExercise->id.'&'.api_get_cidreq(),
+        'name' => $objExercise->selectTitle(true),
     ];
 } else {
     $interbreadcrumb[] = [
-        "url" => "#",
-        "name" => $objExercise->selectTitle(true),
+        'url' => '#',
+        'name' => $objExercise->selectTitle(true),
     ];
 }
 
 // shows a link to go back to the question pool
 if (!$exerciseId && $nameTools != get_lang('ExerciseManagement')) {
     $interbreadcrumb[] = [
-        "url" => api_get_path(WEB_CODE_PATH)."exercise/question_pool.php?fromExercise=$fromExercise&".api_get_cidreq(),
-        "name" => get_lang('QuestionPool'),
+        'url' => api_get_path(WEB_CODE_PATH)."exercise/question_pool.php?fromExercise=$fromExercise&".api_get_cidreq(),
+        'name' => get_lang('QuestionPool'),
     ];
 }
 
@@ -352,12 +353,14 @@ if ($inATest) {
         Display::return_icon('settings.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
 
     $maxScoreAllQuestions = 0;
-    $questionList = $objExercise->selectQuestionList(true, true);
-    if (!empty($questionList)) {
-        foreach ($questionList as $questionItemId) {
-            $question = Question::read($questionItemId);
-            if ($question) {
-                $maxScoreAllQuestions += $question->selectWeighting();
+    if ($showPagination === false) {
+        $questionList = $objExercise->selectQuestionList(true, true);
+        if (!empty($questionList)) {
+            foreach ($questionList as $questionItemId) {
+                $question = Question::read($questionItemId);
+                if ($question) {
+                    $maxScoreAllQuestions += $question->selectWeighting();
+                }
             }
         }
     }
@@ -366,7 +369,6 @@ if ($inATest) {
     if ($objExercise->added_in_lp()) {
         echo Display::return_message(get_lang('AddedToLPCannotBeAccessed'), 'warning');
     }
-
     if ($editQuestion && $objQuestion->existsInAnotherExercises()) {
         echo Display::return_message(
             Display::returnFontAwesomeIcon('exclamation-triangle"')
@@ -376,17 +378,18 @@ if ($inATest) {
         );
     }
 
-    echo '<div class="alert alert-info">';
-    echo sprintf(
-        get_lang('XQuestionsWithTotalScoreY'),
-        $objExercise->selectNbrQuestions(),
-        $maxScoreAllQuestions
-    );
-
+    $alert = '';
+    if ($showPagination === false) {
+        $alert .= sprintf(
+            get_lang('XQuestionsWithTotalScoreY'),
+            $nbrQuestions,
+            $maxScoreAllQuestions
+        );
+    }
     if ($objExercise->random > 0) {
-        echo '<br />'.sprintf(get_lang('OnlyXQuestionsPickedRandomly'), $objExercise->random);
+        $alert .= '<br />'.sprintf(get_lang('OnlyXQuestionsPickedRandomly'), $objExercise->random);
     }
-    echo '</div>';
+    echo Display::return_message($alert);
 } elseif (isset($_GET['newQuestion'])) {
     // we are in create a new question from question pool not in a test
     echo '<div class="actions">';
@@ -411,7 +414,6 @@ if ($newQuestion || $editQuestion) {
         $objExercise->edit_exercise_in_lp = true;
         require 'question_admin.inc.php';
     }
-
     if ($editQuestion) {
         // Question preview if teacher clicked the "switch to student"
         if ($studentViewActive && $is_allowedToEdit) {

+ 2 - 2
main/exercise/aiken.php

@@ -27,8 +27,8 @@ if (!api_is_allowed_to_edit(null, true)) {
 
 // the breadcrumbs
 $interbreadcrumb[] = [
-    "url" => 'exercise.php?'.api_get_cidreq(),
-    "name" => get_lang('Exercises'),
+    'url' => 'exercise.php?'.api_get_cidreq(),
+    'name' => get_lang('Exercises'),
 ];
 $is_allowedToEdit = api_is_allowed_to_edit(null, true);
 // import file

+ 1 - 1
main/exercise/answer.class.php

@@ -80,7 +80,7 @@ class Answer
             // fills arrays
             $objExercise = new Exercise($this->course_id);
             $exerciseId = isset($_REQUEST['exerciseId']) ? $_REQUEST['exerciseId'] : null;
-            $objExercise->read($exerciseId);
+            $objExercise->read($exerciseId, false);
         } else {
             $objExercise = $exercise;
         }

+ 48 - 16
main/exercise/exercise.class.php

@@ -186,7 +186,6 @@ class Exercise
             $this->text_when_finished = $object->text_when_finished;
             $this->display_category_name = $object->display_category_name;
             $this->pass_percentage = $object->pass_percentage;
-            $this->sessionId = $object->session_id;
             $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
             $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
             $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
@@ -218,10 +217,9 @@ class Exercise
 
             $this->force_edit_exercise_in_lp = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
 
+            $this->edit_exercise_in_lp = true;
             if ($this->exercise_was_added_in_lp) {
                 $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
-            } else {
-                $this->edit_exercise_in_lp = true;
             }
 
             if (!empty($object->end_time)) {
@@ -763,7 +761,7 @@ class Exercise
         $count = 0;
         if (Database::num_rows($result)) {
             $row = Database::fetch_array($result);
-            $count = $row['count'];
+            $count = (int) $row['count'];
         }
 
         return $count;
@@ -1033,7 +1031,7 @@ class Exercise
     /**
      * returns the array with the question ID list.
      *
-     * @param bool $from_db   Whether the results should be fetched in the database or just from memory
+     * @param bool $fromDatabase Whether the results should be fetched in the database or just from memory
      * @param bool $adminView Whether we should return all questions (admin view) or
      *                        just a list limited by the max number of random questions
      *
@@ -1041,9 +1039,9 @@ class Exercise
      *
      * @return array - question ID list
      */
-    public function selectQuestionList($from_db = false, $adminView = false)
+    public function selectQuestionList($fromDatabase = false, $adminView = false)
     {
-        if ($from_db && !empty($this->id)) {
+        if ($fromDatabase && !empty($this->id)) {
             $nbQuestions = $this->getQuestionCount();
             $questionSelectionType = $this->getQuestionSelectionType();
 
@@ -1085,7 +1083,7 @@ class Exercise
      */
     public function selectNbrQuestions()
     {
-        return sizeof($this->questionList);
+        return count($this->questionList);
     }
 
     /**
@@ -1127,7 +1125,7 @@ class Exercise
 
         // Random with no limit
         if ($random == -1) {
-            $randomLimit = "ORDER BY RAND() ";
+            $randomLimit = ' ORDER BY RAND() ';
         }
 
         // Admin see the list in default order
@@ -1488,18 +1486,18 @@ class Exercise
         $propagate_neg = (int) $this->propagate_neg;
         $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
         $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
-        $randomByCat = intval($this->randomByCat);
+        $randomByCat = (int) $this->randomByCat;
         $text_when_finished = $this->text_when_finished;
-        $display_category_name = intval($this->display_category_name);
-        $pass_percentage = intval($this->pass_percentage);
+        $display_category_name = (int) $this->display_category_name;
+        $pass_percentage = (int) $this->pass_percentage;
         $session_id = $this->sessionId;
 
         // If direct we do not show results
-        $results_disabled = intval($this->results_disabled);
+        $results_disabled = (int) $this->results_disabled;
         if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
             $results_disabled = 0;
         }
-        $expired_time = intval($this->expired_time);
+        $expired_time = (int) $this->expired_time;
 
         // Exercise already exists
         if ($id) {
@@ -4329,7 +4327,9 @@ class Exercise
                                             ['style' => 'color: #FF0000; text-decoration: line-through;']
                                         );
                                         if ($this->showExpectedChoice()) {
-                                            $user_answer = '';
+                                            if (isset($real_list[$s_user_answer])) {
+                                                $user_answer = Display::span($real_list[$s_user_answer]);
+                                            }
                                         }
                                     }
                                 }
@@ -4375,7 +4375,7 @@ class Exercise
                                             echo '<td>'.$status.'</td>';
                                         } else {
                                             echo '<td>'.$s_answer_label.'</td>';
-                                            echo '<td>'.$user_answer.'</td>';
+                                            //echo '<td>'.$user_answer.'</td>';
                                             echo '<td>';
                                             if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
                                                 if (isset($real_list[$i_answer_correct_answer]) &&
@@ -8005,6 +8005,38 @@ class Exercise
         return strip_tags(api_html_entity_decode($this->title));
     }
 
+    /**
+     * @param int $start
+     * @param int $lenght
+     *
+     * @return array
+     */
+    public function getQuestionForTeacher($start = 0, $lenght = 10)
+    {
+        $start = (int) $start;
+        $lenght = (int) $lenght;
+
+        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
+        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
+        $sql = "SELECT DISTINCT e.question_id, e.question_order
+                FROM $TBL_EXERCICE_QUESTION e
+                INNER JOIN $TBL_QUESTIONS q
+                ON (e.question_id = q.id AND e.c_id = q.c_id)
+                WHERE
+                    e.c_id = {$this->course_id} AND
+                    e.exercice_id = '".$this->id."'
+                ORDER BY question_order
+                LIMIT $start, $lenght
+            ";
+        $result = Database::query($sql);
+        $questionList = [];
+        while ($object = Database::fetch_object($result)) {
+            $questionList[$object->question_order] = $object->question_id;
+        }
+
+        return $questionList;
+    }
+
     /**
      * Gets the question list ordered by the question_order setting (drag and drop).
      *

+ 92 - 21
main/exercise/exercise.php

@@ -24,9 +24,10 @@ $this_section = SECTION_COURSES;
 
 //$htmlHeadXtra[] = api_get_asset('qtip2/jquery.qtip.min.js');
 //$htmlHeadXtra[] = api_get_css_asset('qtip2/jquery.qtip.min.css');
-
-// Access control
 api_protect_course_script(true);
+
+$limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
+
 require_once 'hotpotatoes.lib.php';
 
 /*  Constants and variables */
@@ -106,6 +107,9 @@ if (api_get_course_setting('enable_exercise_auto_launch') == 1 &&
 $nameTools = get_lang('Exercises');
 $errorXmlExport = null;
 if ($is_allowedToEdit && !empty($choice) && $choice === 'exportqti2') {
+    if ($limitTeacherAccess && !api_is_platform_admin()) {
+        api_not_allowed(true);
+    }
     require_once api_get_path(SYS_CODE_PATH).'exercise/export/qti2/qti2_export.php';
 
     $export = export_exercise_to_qti($exerciseId, true);
@@ -156,7 +160,6 @@ $logInfo = [
     'tool_id_detail' => 0,
     'action' => isset($_REQUEST['learnpath_id']) ? 'learnpath_id' : '',
     'action_details' => isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : '',
-    'current_id' => 0,
 ];
 Event::registerLog($logInfo);
 
@@ -228,6 +231,10 @@ if ($is_allowedToEdit) {
                     case 'delete':
                         // deletes an exercise
                         if ($exercise_action_locked == false) {
+                            if ($limitTeacherAccess && !api_is_platform_admin()) {
+                                // Teacher cannot delete an exercise
+                                break;
+                            }
                             $objExerciseTmp->delete();
                             $link_info = GradebookUtils::isResourceInCourseGradebook(
                                 api_get_course_id(),
@@ -242,6 +249,11 @@ if ($is_allowedToEdit) {
                         }
                         break;
                     case 'enable':
+                        if ($limitTeacherAccess && !api_is_platform_admin()) {
+                            // Teacher change exercise
+                            break;
+                        }
+
                         // enables an exercise
                         if (empty($sessionId)) {
                             $objExerciseTmp->enable();
@@ -264,6 +276,10 @@ if ($is_allowedToEdit) {
                         echo Display::return_message(get_lang('VisibilityChanged'), 'confirmation');
                         break;
                     case 'disable':
+                        if ($limitTeacherAccess && !api_is_platform_admin()) {
+                            // Teacher change exercise
+                            break;
+                        }
                         // disables an exercise
                         if (empty($sessionId)) {
                             $objExerciseTmp->disable();
@@ -307,6 +323,11 @@ if ($is_allowedToEdit) {
                         );
                         break;
                     case 'clean_results':
+                        if ($limitTeacherAccess && !api_is_platform_admin()) {
+                            // Teacher change exercise
+                            break;
+                        }
+
                         //clean student results
                         if ($exercise_action_locked == false) {
                             $quantity_results_deleted = $objExerciseTmp->cleanResults(true);
@@ -338,6 +359,10 @@ if ($is_allowedToEdit) {
     if (!empty($hpchoice)) {
         switch ($hpchoice) {
             case 'delete':
+                if ($limitTeacherAccess && !api_is_platform_admin()) {
+                    // Teacher change exercise
+                    break;
+                }
                 // deletes an exercise
                 $imgparams = [];
                 $imgcount = 0;
@@ -367,7 +392,12 @@ if ($is_allowedToEdit) {
                 }
                 break;
             case 'enable': // enables an exercise
-                $newVisibilityStatus = "1"; //"visible"
+                if ($limitTeacherAccess && !api_is_platform_admin()) {
+                    // Teacher change exercise
+                    break;
+                }
+
+                $newVisibilityStatus = '1'; //"visible"
                 $query = "SELECT id FROM $TBL_DOCUMENT
                           WHERE c_id = $courseId AND path='".Database :: escape_string($file)."'";
                 $res = Database::query($query);
@@ -383,6 +413,10 @@ if ($is_allowedToEdit) {
 
                 break;
             case 'disable': // disables an exercise
+                if ($limitTeacherAccess && !api_is_platform_admin()) {
+                    // Teacher change exercise
+                    break;
+                }
                 $newVisibilityStatus = '0'; //"invisible"
                 $query = "SELECT id FROM $TBL_DOCUMENT
                           WHERE c_id = $courseId AND path='".Database :: escape_string($file)."'";
@@ -417,8 +451,7 @@ HotPotGCt($documentPath, 1, $userId);
 
 //condition for the session
 $course_code = api_get_course_id();
-$session_id = api_get_session_id();
-$condition_session = api_get_session_condition($session_id, true, true);
+$condition_session = api_get_session_condition($sessionId, true, true);
 
 // Only for administrators
 if ($is_allowedToEdit) {
@@ -732,7 +765,7 @@ if (!empty($exerciseList)) {
                 $count_exercise_not_validated = (int) Event::count_exercise_result_not_validated(
                     $my_exercise_id,
                     $courseId,
-                    $session_id
+                    $sessionId
                 );
 
                 $move = Display::return_icon(
@@ -761,7 +794,7 @@ if (!empty($exerciseList)) {
                 $sqlresult = Database::query($sql);
                 $rowi = intval(Database :: result($sqlresult, 0, 0));
 
-                if ($session_id == $row['session_id']) {
+                if ($sessionId == $row['session_id']) {
                     // Questions list
                     $actions = Display::url(
                         Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL),
@@ -769,11 +802,16 @@ if (!empty($exerciseList)) {
                     );
 
                     // Test settings
-                    $actions .= Display::url(
+                    $settings = Display::url(
                         Display::return_icon('settings.png', get_lang('Configure'), '', ICON_SIZE_SMALL),
                         'exercise_admin.php?'.api_get_cidreq().'&exerciseId='.$row['id']
                     );
 
+                    if ($limitTeacherAccess && !api_is_platform_admin()) {
+                        $settings = '';
+                    }
+                    $actions .= $settings;
+
                     // Exercise results
                     $actions .= '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
                         Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
@@ -816,7 +854,7 @@ if (!empty($exerciseList)) {
 
                     // Clean exercise
                     if ($locked == false) {
-                        $actions .= Display::url(
+                        $clean = Display::url(
                             Display::return_icon(
                                 'clean.png',
                                 get_lang('CleanStudentResults'),
@@ -830,7 +868,7 @@ if (!empty($exerciseList)) {
                             ]
                         );
                     } else {
-                        $actions .= Display::return_icon(
+                        $clean = Display::return_icon(
                             'clean_na.png',
                             get_lang('ResourceLockedByGradebook'),
                             '',
@@ -838,10 +876,16 @@ if (!empty($exerciseList)) {
                         );
                     }
 
+                    if ($limitTeacherAccess && !api_is_platform_admin()) {
+                        $clean = '';
+                    }
+
+                    $actions .= $clean;
+
                     // Visible / invisible
                     // Check if this exercise was added in a LP
                     if ($exercise->exercise_was_added_in_lp == true) {
-                        $actions .= Display::return_icon(
+                        $visibility = Display::return_icon(
                             'invisible.png',
                             get_lang('AddedToLPCannotBeAccessed'),
                             '',
@@ -849,7 +893,7 @@ if (!empty($exerciseList)) {
                         );
                     } else {
                         if ($row['active'] == 0 || $visibility == 0) {
-                            $actions .= Display::url(
+                            $visibility = Display::url(
                                 Display::return_icon(
                                     'invisible.png',
                                     get_lang('Activate'),
@@ -860,7 +904,7 @@ if (!empty($exerciseList)) {
                             );
                         } else {
                             // else if not active
-                            $actions .= Display::url(
+                            $visibility = Display::url(
                                 Display::return_icon(
                                     'visible.png',
                                     get_lang('Deactivate'),
@@ -872,7 +916,14 @@ if (!empty($exerciseList)) {
                         }
                     }
                     // Export qti ...
-                    $actions .= Display::url(
+                    if ($limitTeacherAccess && !api_is_platform_admin()) {
+                        $visibility = '';
+                    }
+
+                    $actions .= $visibility;
+
+                    // Export qti ...
+                    $export = Display::url(
                         Display::return_icon(
                             'export_qti2.png',
                             'IMS/QTI',
@@ -881,6 +932,12 @@ if (!empty($exerciseList)) {
                         ),
                         'exercise.php?choice=exportqti2&exerciseId='.$row['id'].'&'.api_get_cidreq()
                     );
+
+                    if ($limitTeacherAccess && !api_is_platform_admin()) {
+                        $export = '';
+                    }
+
+                    $actions .= $export;
                 } else {
                     // not session
                     $actions = Display::return_icon(
@@ -890,7 +947,7 @@ if (!empty($exerciseList)) {
 
                     // Check if this exercise was added in a LP
                     if ($exercise->exercise_was_added_in_lp == true) {
-                        $actions .= Display::return_icon(
+                        $visiblity = Display::return_icon(
                             'invisible.png',
                             get_lang('AddedToLPCannotBeAccessed'),
                             '',
@@ -898,7 +955,7 @@ if (!empty($exerciseList)) {
                         );
                     } else {
                         if ($row['active'] == 0 || $visibility == 0) {
-                            $actions .= Display::url(
+                            $visiblity = Display::url(
                                 Display::return_icon(
                                     'invisible.png',
                                     get_lang('Activate'),
@@ -909,7 +966,7 @@ if (!empty($exerciseList)) {
                             );
                         } else {
                             // else if not active
-                            $actions .= Display::url(
+                            $visiblity = Display::url(
                                 Display::return_icon(
                                     'visible.png',
                                     get_lang('Deactivate'),
@@ -921,6 +978,12 @@ if (!empty($exerciseList)) {
                         }
                     }
 
+                    if ($limitTeacherAccess && !api_is_platform_admin()) {
+                        $visiblity = '';
+                    }
+
+                    $actions .= $visiblity;
+
                     $actions .= '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
                         Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
                     $actions .= Display::url(
@@ -934,9 +997,10 @@ if (!empty($exerciseList)) {
                 }
 
                 // Delete
-                if ($session_id == $row['session_id']) {
+                $delete = '';
+                if ($sessionId == $row['session_id']) {
                     if ($locked == false) {
-                        $actions .= Display::url(
+                        $delete = Display::url(
                             Display::return_icon(
                                 'delete.png',
                                 get_lang('Delete'),
@@ -950,7 +1014,7 @@ if (!empty($exerciseList)) {
                             ]
                         );
                     } else {
-                        $actions .= Display::return_icon(
+                        $delete = Display::return_icon(
                             'delete_na.png',
                             get_lang('ResourceLockedByGradebook'),
                             '',
@@ -959,6 +1023,12 @@ if (!empty($exerciseList)) {
                     }
                 }
 
+                if ($limitTeacherAccess && !api_is_platform_admin()) {
+                    $delete = '';
+                }
+
+                $actions .= $delete;
+
                 // Number of questions
                 $random_label = null;
                 if ($row['random'] > 0 || $row['random'] == -1) {
@@ -1249,6 +1319,7 @@ if (isset($attribute['path']) && is_array($attribute['path'])) {
                     ),
                 ])
             );
+
             $item .= Display::tag('td', '-');
 
             $actions = Display::url(

+ 6 - 0
main/exercise/exercise_admin.php

@@ -19,6 +19,12 @@ if (!api_is_allowed_to_edit(null, true)) {
     api_not_allowed(true);
 }
 
+$limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
+
+if ($limitTeacherAccess && !api_is_platform_admin()) {
+    api_not_allowed(true);
+}
+
 $htmlHeadXtra[] = '<script>
     function activate_start_date() {
         if(document.getElementById(\'start_date_div\').style.display == \'none\') {

+ 3 - 0
main/exercise/exercise_result.php

@@ -71,6 +71,9 @@ $interbreadcrumb[] = [
 $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
 $htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
 $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
+if (api_get_configuration_value('quiz_prevent_copy_paste')) {
+    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.nocopypaste.js"></script>';
+}
 
 if ($origin != 'learnpath') {
     // So we are not in learnpath tool

+ 5 - 5
main/exercise/exercise_show.php

@@ -128,6 +128,7 @@ if (empty($objExercise)) {
     $objExercise->read($exercise_id);
 }
 $feedback_type = $objExercise->feedback_type;
+
 // Only users can see their own results
 if (!$is_allowedToEdit) {
     if ($student_id != $currentUserId) {
@@ -734,10 +735,9 @@ foreach ($questionList as $questionId) {
             if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
                 $url_name = get_lang('EditCommentsAndMarks');
             } else {
+                $url_name = get_lang('AddComments');
                 if ($action == 'edit') {
                     $url_name = get_lang('EditIndividualComment');
-                } else {
-                    $url_name = get_lang('AddComments');
                 }
             }
             echo '<p>';
@@ -1020,7 +1020,7 @@ if ($show_results) {
     echo $totalScoreText;
 }
 
-if ($action == 'export') {
+if ($action === 'export') {
     $content = ob_get_clean();
     // needed in order to mpdf to work
     ob_clean();
@@ -1046,8 +1046,8 @@ if ($action == 'export') {
 
 if ($isFeedbackAllowed) {
     if (is_array($arrid) && is_array($arrmarks)) {
-        $strids = implode(",", $arrid);
-        $marksid = implode(",", $arrmarks);
+        $strids = implode(',', $arrid);
+        $marksid = implode(',', $arrmarks);
     }
 }
 

+ 3 - 2
main/exercise/exercise_submit.php

@@ -63,6 +63,9 @@ $htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
 $htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
 $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
 $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
+if (api_get_configuration_value('quiz_prevent_copy_paste')) {
+    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.nocopypaste.js"></script>';
+}
 
 if (api_get_setting('enable_record_audio') === 'true') {
     $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'rtc/RecordRTC.js"></script>';
@@ -99,8 +102,6 @@ $logInfo = [
     'tool_id_detail' => 0,
     'action' => ((int) $_REQUEST['learnpath_id'] > 0) ? 'learnpath_id' : '',
     'action_details' => ((int) $_REQUEST['learnpath_id'] > 0) ? (int) $_REQUEST['learnpath_id'] : '',
-    'current_id' => $current_question,
-    'info' => '',
 ];
 Event::registerLog($logInfo);
 

+ 21 - 3
main/exercise/export/aiken/aiken_import.inc.php

@@ -191,15 +191,22 @@ function aiken_import_exercise($file)
             if (isset($question_array['description'])) {
                 $question->updateDescription($question_array['description']);
             }
+
             $type = $question->selectType();
             $question->type = constant($type);
             $question->save($exercise);
+
             $last_question_id = $question->selectId();
             //3. Create answer
             $answer = new Answer($last_question_id);
             $answer->new_nbrAnswers = count($question_array['answer']);
             $max_score = 0;
 
+            $scoreFromFile = 0;
+            if (isset($question_array['score']) && !empty($question_array['score'])) {
+                $scoreFromFile = $question_array['score'];
+            }
+
             foreach ($question_array['answer'] as $key => $answers) {
                 $key++;
                 $answer->new_answer[$key] = $answers['value'];
@@ -218,6 +225,14 @@ function aiken_import_exercise($file)
                     $answer->new_weighting[$key] = $question_array['weighting'][$key - 1];
                     $max_score += $question_array['weighting'][$key - 1];
                 }
+
+                if (!empty($scoreFromFile) && $answer->new_correct[$key]) {
+                    $answer->new_weighting[$key] = $scoreFromFile;
+                }
+            }
+
+            if (!empty($scoreFromFile)) {
+                $max_score = $scoreFromFile;
             }
 
             $answer->save();
@@ -260,7 +275,6 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
     $data = file($questionFilePath);
 
     $question_index = 0;
-    $correct_answer = '';
     $answers_array = [];
     $new_question = true;
     foreach ($data as $line => $info) {
@@ -284,6 +298,10 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
             $exercise_info['question'][$question_index]['correct_answers'][] = $correct_answer_index + 1;
             //weight for correct answer
             $exercise_info['question'][$question_index]['weighting'][$correct_answer_index] = 1;
+        } elseif (preg_match('/^SCORE:\s?(.*)/', $info, $matches)) {
+            $exercise_info['question'][$question_index]['score'] = (float) $matches[1];
+        } elseif (preg_match('/^DESCRIPTION:\s?(.*)/', $info, $matches)) {
+            $exercise_info['question'][$question_index]['description'] = $matches[1];
         } elseif (preg_match('/^ANSWER_EXPLANATION:\s?(.*)/', $info, $matches)) {
             //Comment of correct answer
             $correct_answer_index = array_search($matches[1], $answers_array);
@@ -325,10 +343,10 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
                 } else {
                     //Question itself (use a 100-chars long title and a larger description)
                     $exercise_info['question'][$question_index]['title'] = trim(substr($info, 0, 100)).'...';
-                    $exercise_info['question'][$question_index]['description'] = $info;
+                    //$exercise_info['question'][$question_index]['description'] = $info;
                 }
             } else {
-                $exercise_info['question'][$question_index]['description'] = $info;
+                //$exercise_info['question'][$question_index]['description'] = $info;
             }
         }
     }

+ 266 - 705
main/exercise/export/exercise_import.inc.php

@@ -2,6 +2,7 @@
 /* For licensing terms, see /license.txt */
 
 use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
+use Symfony\Component\DomCrawler\Crawler;
 
 /**
  * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
@@ -65,12 +66,7 @@ function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
  */
 function import_exercise($file)
 {
-    global $exercise_info;
-    global $element_pile;
-    global $non_HTML_tag_to_avoid;
-    global $record_item_body;
-    // used to specify the question directory where files could be found in relation in any question
-    global $questionTempDir;
+    global $exerciseInfo;
     global $resourcesLinks;
 
     $baseWorkDir = api_get_path(SYS_ARCHIVE_PATH).'qti2/';
@@ -85,12 +81,9 @@ function import_exercise($file)
     }
 
     // set some default values for the new exercise
-    $exercise_info = [];
-    $exercise_info['name'] = preg_replace('/.zip$/i', '', $file);
-    $exercise_info['question'] = [];
-    $element_pile = [];
-    // create parser and array to retrieve info from manifest
-    $element_pile = []; //pile to known the depth in which we are
+    $exerciseInfo = [];
+    $exerciseInfo['name'] = preg_replace('/.zip$/i', '', $file);
+    $exerciseInfo['question'] = [];
 
     // if file is not a .zip, then we cancel all
     if (!preg_match('/.zip$/i', $file)) {
@@ -106,7 +99,7 @@ function import_exercise($file)
 
     // find the different manifests for each question and parse them.
     $exerciseHandle = opendir($baseWorkDir);
-    $file_found = false;
+    $fileFound = false;
     $result = false;
     $filePath = null;
     $resourcesLinks = [];
@@ -125,7 +118,7 @@ function import_exercise($file)
                     if ($isQti) {
                         $result = qti_parse_file($baseWorkDir, $file, $questionFile);
                         $filePath = $baseWorkDir.$file;
-                        $file_found = true;
+                        $fileFound = true;
                     } else {
                         $isManifest = isQtiManifest($baseWorkDir.'/'.$file.'/'.$questionFile);
                         if ($isManifest) {
@@ -139,7 +132,7 @@ function import_exercise($file)
             if ($isQti) {
                 $result = qti_parse_file($baseWorkDir, '', $file);
                 $filePath = $baseWorkDir.'/'.$file;
-                $file_found = true;
+                $fileFound = true;
             } else {
                 $isManifest = isQtiManifest($baseWorkDir.'/'.$file);
                 if ($isManifest) {
@@ -149,7 +142,7 @@ function import_exercise($file)
         }
     }
 
-    if (!$file_found) {
+    if (!$fileFound) {
         return 'NoXMLFileFoundInTheZip';
     }
 
@@ -157,23 +150,20 @@ function import_exercise($file)
         return false;
     }
 
-    $doc = new DOMDocument();
-    $doc->load($filePath);
-
     // 1. Create exercise.
     $exercise = new Exercise();
-    $exercise->exercise = $exercise_info['name'];
+    $exercise->exercise = $exerciseInfo['name'];
 
     // Random QTI support
-    if (isset($exercise_info['order_type'])) {
-        if ($exercise_info['order_type'] == 'Random') {
+    if (isset($exerciseInfo['order_type'])) {
+        if ($exerciseInfo['order_type'] == 'Random') {
             $exercise->setQuestionSelectionType(2);
             $exercise->random = -1;
         }
     }
 
-    if (!empty($exercise_info['description'])) {
-        $exercise->updateDescription(formatText(strip_tags($exercise_info['description'])));
+    if (!empty($exerciseInfo['description'])) {
+        $exercise->updateDescription(formatText(strip_tags($exerciseInfo['description'])));
     }
 
     $exercise->save();
@@ -181,7 +171,7 @@ function import_exercise($file)
     $courseId = api_get_course_int_id();
     if (!empty($last_exercise_id)) {
         // For each question found...
-        foreach ($exercise_info['question'] as $question_array) {
+        foreach ($exerciseInfo['question'] as $question_array) {
             //2. Create question
             $question = new Ims2Question();
             $question->type = $question_array['type'];
@@ -300,7 +290,6 @@ function formatText($text)
  */
 function qti_parse_file($exercisePath, $file, $questionFile)
 {
-    global $non_HTML_tag_to_avoid;
     global $record_item_body;
     global $questionTempDir;
 
@@ -311,10 +300,13 @@ function qti_parse_file($exercisePath, $file, $questionFile)
         Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error'));
 
         return false;
-    } else {
-        $data = fread($fp, filesize($questionFilePath));
     }
 
+    $data = fread($fp, filesize($questionFilePath));
+
+    //close file
+    fclose($fp);
+
     //parse XML question file
     //$data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
     $data = ChamiloApi::stripGivenTags($data, ['p', 'front']);
@@ -327,53 +319,11 @@ function qti_parse_file($exercisePath, $file, $questionFile)
 
     //used global variable start values declaration:
     $record_item_body = false;
-    $non_HTML_tag_to_avoid = [
-        "SIMPLECHOICE",
-        "CHOICEINTERACTION",
-        "INLINECHOICEINTERACTION",
-        "INLINECHOICE",
-        "SIMPLEMATCHSET",
-        "SIMPLEASSOCIABLECHOICE",
-        "TEXTENTRYINTERACTION",
-        "FEEDBACKINLINE",
-        "MATCHINTERACTION",
-        'EXTENDEDTEXTINTERACTION',
-        "ITEMBODY",
-        "BR",
-        "IMG",
-    ];
-
-    $question_format_supported = true;
-    $xml_parser = xml_parser_create();
-    xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, false);
 
-    if ($qtiMainVersion == 1) {
-        xml_set_element_handler(
-            $xml_parser,
-            'startElementQti1',
-            'endElementQti1'
-        );
-        xml_set_character_data_handler($xml_parser, 'elementDataQti1');
-    } else {
-        xml_set_element_handler(
-            $xml_parser,
-            'startElementQti2',
-            'endElementQti2'
-        );
-        xml_set_character_data_handler($xml_parser, 'elementDataQti2');
-    }
-
-    if (!xml_parse($xml_parser, $data, feof($fp))) {
-        // if reading of the xml file in not successful :
-        // set errorFound, set error msg, break while statement
+    if ($qtiMainVersion != 2) {
         Display::addFlash(
             Display::return_message(
-                get_lang('Error reading XML file').
-                sprintf(
-                    '[%d:%d]',
-                    xml_get_current_line_number($xml_parser),
-                    xml_get_current_column_number($xml_parser)
-                ),
+                get_lang('UnsupportedQtiVersion'),
                 'error'
             )
         );
@@ -381,682 +331,293 @@ function qti_parse_file($exercisePath, $file, $questionFile)
         return false;
     }
 
-    //close file
-    fclose($fp);
-    if (!$question_format_supported) {
-        Display::addFlash(
-            Display::return_message(
-                get_lang(
-                    'Unknown question format in file %file',
-                    [
-                        '%file' => $questionFile,
-                    ]
-                ),
-                'error'
-            )
-        );
-
-        return false;
-    }
+    parseQti2($data);
 
     return true;
 }
 
 /**
- * Function used by the SAX xml parser when the parser meets a opening tag.
+ * Function used to parser a QTI2 xml file.
  *
- * @param object $parser     xml parser created with "xml_parser_create()"
- * @param string $name       name of the element
- * @param array  $attributes
+ * @param string $xmlData
  */
-function startElementQti2($parser, $name, $attributes)
+function parseQti2($xmlData)
 {
-    global $element_pile;
-    global $exercise_info;
-    global $current_question_ident;
-    global $current_answer_id;
-    global $current_match_set;
-    global $currentAssociableChoice;
-    global $current_question_item_body;
-    global $record_item_body;
-    global $non_HTML_tag_to_avoid;
-    global $current_inlinechoice_id;
-    global $cardinality;
+    global $exerciseInfo;
     global $questionTempDir;
+    global $resourcesLinks;
 
-    array_push($element_pile, $name);
-    $current_element = end($element_pile);
-    if (sizeof($element_pile) >= 2) {
-        $parent_element = $element_pile[sizeof($element_pile) - 2];
-    } else {
-        $parent_element = '';
-    }
-
-    if (sizeof($element_pile) >= 3) {
-        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
-    } else {
-        $grand_parent_element = '';
-    }
-    if (sizeof($element_pile) >= 4) {
-        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
-    } else {
-        $great_grand_parent_element = '';
-    }
+    $crawler = new Crawler($xmlData);
+    $nodes = $crawler->filter('*');
+
+    $currentQuestionIdent = '';
+    $currentAnswerId = '';
+    $currentQuestionItemBody = '';
+    $cardinality = '';
+    $nonHTMLTagToAvoid = [
+        "simpleChoice",
+        "choiceInteraction",
+        "inlineChoiceInteraction",
+        "inlineChoice",
+        "soMPLEMATCHSET",
+        "simpleAssociableChoice",
+        "textEntryInteraction",
+        "feedbackInline",
+        "matchInteraction",
+        'extendedTextInteraction',
+        "itemBody",
+        "br",
+        "img",
+    ];
+    $currentMatchSet = null;
 
-    if ($record_item_body) {
-        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
-            $current_question_item_body .= "<".$name;
-            foreach ($attributes as $attribute_name => $attribute_value) {
-                $current_question_item_body .= " ".$attribute_name."=\"".$attribute_value."\"";
-            }
-            $current_question_item_body .= ">";
-        } else {
-            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
-            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
-            if ($current_element == 'INLINECHOICEINTERACTION') {
-                $current_question_item_body .= "**claroline_start**".$attributes['RESPONSEIDENTIFIER']."**claroline_end**";
-            }
-            if ($current_element == 'TEXTENTRYINTERACTION') {
-                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
-                $current_question_item_body .= "[".$correct_answer_value."]";
-            }
-            if ($current_element == 'BR') {
-                $current_question_item_body .= "<br />";
-            }
+    /** @var DOMElement $node */
+    foreach ($nodes as $node) {
+        if ('#text' === $node->nodeName) {
+            continue;
         }
-    }
 
-    switch ($current_element) {
-        case 'ASSESSMENTITEM':
-            // retrieve current question
-            $current_question_ident = $attributes['IDENTIFIER'];
-            $exercise_info['question'][$current_question_ident] = [];
-            $exercise_info['question'][$current_question_ident]['answer'] = [];
-            $exercise_info['question'][$current_question_ident]['correct_answers'] = [];
-            $exercise_info['question'][$current_question_ident]['title'] = isset($attributes['TITLE']) ? $attributes['TITLE'] : '';
-            $exercise_info['question'][$current_question_ident]['category'] = isset($attributes['CATEGORY']) ? $attributes['CATEGORY'] : '';
-            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
-            break;
-        case 'SECTION':
-            //retrieve exercise name
-            if (isset($attributes['TITLE']) && !empty($attributes['TITLE'])) {
-                $exercise_info['name'] = $attributes['TITLE'];
-            }
-            break;
-        case 'RESPONSEDECLARATION':
-            // Retrieve question type
-            if ('multiple' == $attributes['CARDINALITY']) {
-                $exercise_info['question'][$current_question_ident]['type'] = MCMA;
-                $cardinality = 'multiple';
-            }
-            if ('single' == $attributes['CARDINALITY']) {
-                $exercise_info['question'][$current_question_ident]['type'] = MCUA;
-                $cardinality = 'single';
-            }
-            //needed for FIB
-            $current_answer_id = $attributes['IDENTIFIER'];
-            break;
-        case 'INLINECHOICEINTERACTION':
-            $exercise_info['question'][$current_question_ident]['type'] = FIB;
-            $exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL';
-            $current_answer_id = $attributes['RESPONSEIDENTIFIER'];
-            break;
-        case 'INLINECHOICE':
-            $current_inlinechoice_id = $attributes['IDENTIFIER'];
-            break;
-        case 'TEXTENTRYINTERACTION':
-            $exercise_info['question'][$current_question_ident]['type'] = FIB;
-            $exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL';
-            $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
-            //replace claroline tags
-            break;
-        case 'MATCHINTERACTION':
-            $exercise_info['question'][$current_question_ident]['type'] = MATCHING;
-            break;
-        case 'EXTENDEDTEXTINTERACTION':
-            $exercise_info['question'][$current_question_ident]['type'] = FREE_ANSWER;
-            $exercise_info['question'][$current_question_ident]['description'] = '';
-            break;
-        case 'SIMPLEMATCHSET':
-            if (!isset($current_match_set)) {
-                $current_match_set = 1;
-            } else {
-                $current_match_set++;
-            }
-            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set] = [];
-            break;
-        case 'SIMPLEASSOCIABLECHOICE':
-            $currentAssociableChoice = $attributes['IDENTIFIER'];
-            break;
-        //retrieve answers id for MCUA and MCMA questions
-        case 'SIMPLECHOICE':
-            $current_answer_id = $attributes['IDENTIFIER'];
-            if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id])) {
-                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = [];
-            }
-            break;
-        case 'MAPENTRY':
-            if ($parent_element == 'MAPPING' || $parent_element == 'MAPENTRY') {
-                $answer_id = $attributes['MAPKEY'];
-                if (!isset($exercise_info['question'][$current_question_ident]['weighting'])) {
-                    $exercise_info['question'][$current_question_ident]['weighting'] = [];
+        switch ($node->nodeName) {
+            case 'assessmentItem':
+                $currentQuestionIdent = $node->getAttribute('identifier');
+
+                $exerciseInfo['question'][$currentQuestionIdent] = [
+                    'answer' => [],
+                    'correct_answers' => [],
+                    'title' => $node->getAttribute('title'),
+                    'category' => $node->getAttribute('category'),
+                    'type' => '',
+                    'tempdir' => $questionTempDir,
+                ];
+                break;
+            case 'section':
+                $title = $node->getAttribute('title');
+
+                if (!empty($title)) {
+                    $exerciseInfo['name'] = $title;
+                }
+                break;
+            case 'responseDeclaration':
+                if ('multiple' === $node->getAttribute('cardinality')) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['type'] = MCMA;
+                    $cardinality = 'multiple';
                 }
-                $exercise_info['question'][$current_question_ident]['weighting'][$answer_id] = $attributes['MAPPEDVALUE'];
-            }
-            break;
-        case 'MAPPING':
-            if (isset($attributes['DEFAULTVALUE'])) {
-                $exercise_info['question'][$current_question_ident]['default_weighting'] = $attributes['DEFAULTVALUE'];
-            }
-            // no break ?
-        case 'ITEMBODY':
-            $record_item_body = true;
-            $current_question_item_body = '';
-            break;
-        case 'IMG':
-            $exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
-            break;
-        case 'ORDER':
-            if (isset($attributes['ORDER_TYPE'])) {
-                $exercise_info['order_type'] = $attributes['ORDER_TYPE'];
-            }
-            break;
-    }
-}
-
-/**
- * Function used by the SAX xml parser when the parser meets a closing tag.
- *
- * @param $parser xml parser created with "xml_parser_create()"
- * @param $name name of the element
- */
-function endElementQti2($parser, $name)
-{
-    global $element_pile;
-    global $exercise_info;
-    global $current_question_ident;
-    global $record_item_body;
-    global $current_question_item_body;
-    global $non_HTML_tag_to_avoid;
-    global $cardinality;
-
-    array_push($element_pile, $name);
-    $current_element = end($element_pile);
-    if (sizeof($element_pile) >= 2) {
-        $parent_element = $element_pile[sizeof($element_pile) - 2];
-    } else {
-        $parent_element = '';
-    }
-    if (sizeof($element_pile) >= 3) {
-        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
-    } else {
-        $grand_parent_element = '';
-    }
-    if (sizeof($element_pile) >= 4) {
-        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
-    } else {
-        $great_grand_parent_element = '';
-    }
 
-    //treat the record of the full content of itembody tag:
-    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
-        $current_question_item_body .= "</".$name.">";
-    }
+                if ('single' === $node->getAttribute('cardinality')) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['type'] = MCUA;
+                    $cardinality = 'single';
+                }
 
-    switch ($name) {
-        case 'ITEMBODY':
-            $record_item_body = false;
-            if ($exercise_info['question'][$current_question_ident]['type'] == FIB) {
-                $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
-            } else {
-                if ($exercise_info['question'][$current_question_ident]['type'] == FREE_ANSWER) {
-                    $current_question_item_body = trim($current_question_item_body);
-                    if (!empty($current_question_item_body)) {
-                        $exercise_info['question'][$current_question_ident]['description'] = $current_question_item_body;
+                $currentAnswerId = $node->getAttribute('identifier');
+                break;
+            case 'inlineChoiceInteraction':
+                $exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB;
+                $exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'LISTBOX_FILL';
+                $currentAnswerId = $node->getAttribute('responseIdentifier');
+                break;
+            case 'inlineChoice':
+                $answerIdentifier = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId];
+
+                if ($node->getAttribute('identifier') == $answerIdentifier) {
+                    $currentQuestionItemBody = str_replace(
+                        "**claroline_start**".$currentAnswerId."**claroline_end**",
+                        "[".$node->nodeValue."]",
+                        $currentQuestionItemBody
+                    );
+                } else {
+                    if (!isset($exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'])) {
+                        $exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'] = [];
                     }
+
+                    $exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'][] = $node->nodeValue;
+                }
+                break;
+            case 'textEntryInteraction':
+                $exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB;
+                $exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'TEXTFIELD_FILL';
+                $exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody;
+                break;
+            case 'matchInteraction':
+                $exerciseInfo['question'][$currentQuestionIdent]['type'] = MATCHING;
+                break;
+            case 'extendedTextInteraction':
+                $exerciseInfo['question'][$currentQuestionIdent]['type'] = FREE_ANSWER;
+                $exerciseInfo['question'][$currentQuestionIdent]['description'] = $node->nodeValue;
+                break;
+            case 'simpleMatchSet':
+                if (!isset($currentMatchSet)) {
+                    $currentMatchSet = 1;
                 } else {
-                    $exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body;
+                    $currentMatchSet++;
+                }
+                $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet] = [];
+                break;
+            case 'simpleAssociableChoice':
+                $currentAssociableChoice = $node->getAttribute('identifier');
+
+                $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet][$currentAssociableChoice] = trim($node->nodeValue);
+                break;
+            case 'simpleChoice':
+                $currentAnswerId = $node->getAttribute('identifier');
+                if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId])) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId] = [];
                 }
-            }
-            break;
-    }
-    array_pop($element_pile);
-}
-
-/**
- * @param $parser
- * @param $data
- */
-function elementDataQti2($parser, $data)
-{
-    global $element_pile;
-    global $exercise_info;
-    global $current_question_ident;
-    global $current_answer_id;
-    global $current_match_set;
-    global $currentAssociableChoice;
-    global $current_question_item_body;
-    global $record_item_body;
-    global $non_HTML_tag_to_avoid;
-    global $current_inlinechoice_id;
-    global $cardinality;
-    global $resourcesLinks;
-
-    $current_element = end($element_pile);
-    if (sizeof($element_pile) >= 2) {
-        $parent_element = $element_pile[sizeof($element_pile) - 2];
-    } else {
-        $parent_element = '';
-    }
-    if (sizeof($element_pile) >= 3) {
-        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
-    } else {
-        $grand_parent_element = '';
-    }
-    if (sizeof($element_pile) >= 4) {
-        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
-    } else {
-        $great_grand_parent_element = '';
-    }
-
-    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
-
-    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
-        $current_question_item_body .= $data;
-    }
 
-    switch ($current_element) {
-        case 'EXTENDEDTEXTINTERACTION':
-            $exercise_info['question'][$current_question_ident]['description'] .= $data;
-            break;
-        case 'SIMPLECHOICE':
-            if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'])) {
-                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] = trim($data);
-            } else {
-                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] .= ''.trim($data);
-            }
-            break;
-        case 'FEEDBACKINLINE':
-            if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'])) {
-                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] = trim($data);
-            } else {
-                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] .= ' '.trim($data);
-            }
-            break;
-        case 'SIMPLEASSOCIABLECHOICE':
-            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set][$currentAssociableChoice] = trim($data);
-            break;
-        case 'VALUE':
-            if ($parent_element == 'CORRECTRESPONSE') {
-                if ($cardinality == 'single') {
-                    $exercise_info['question'][$current_question_ident]['correct_answers'][$data] = $data;
+                if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'])) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] = trim(
+                        $node->nodeValue
+                    );
                 } else {
-                    $exercise_info['question'][$current_question_ident]['correct_answers'][] = $data;
+                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] .= ''
+                        .trim($node->nodeValue);
                 }
-            }
+                break;
+            case 'mapEntry':
+                if (in_array($node->parentNode->nodeName, ['mapping', 'mapEntry'])) {
+                    $answer_id = $node->getAttribute('mapKey');
 
-            // Getting score of free answer
-            if ($grand_parent_element == 'OUTCOMEDECLARATION') {
-                if (!empty(trim($data))) {
-                    $exercise_info['question'][$current_question_ident]['weighting'][0] = trim($data);
-                }
-            }
+                    if (!isset($exerciseInfo['question'][$currentQuestionIdent]['weighting'])) {
+                        $exerciseInfo['question'][$currentQuestionIdent]['weighting'] = [];
+                    }
 
-            break;
-        case 'ITEMBODY':
-            // Replace relative links by links to the documents in the course
-            // $resourcesLinks is only defined by qtiProcessManifest()
-            if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
-                foreach ($resourcesLinks['manifest'] as $key => $value) {
-                    $data = preg_replace('|'.$value.'|', $resourcesLinks['web'][$key], $data);
-                }
-            }
-            $current_question_item_body .= $data;
-            break;
-        case 'INLINECHOICE':
-            // if this is the right answer, then we must replace the claroline tags in the FIB text bye the answer between "[" and "]" :
-            $answer_identifier = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
-            if ($current_inlinechoice_id == $answer_identifier) {
-                $current_question_item_body = str_replace(
-                    "**claroline_start**".$current_answer_id."**claroline_end**",
-                    "[".$data."]",
-                    $current_question_item_body
-                );
-            } else {
-                if (!isset($exercise_info['question'][$current_question_ident]['wrong_answers'])) {
-                    $exercise_info['question'][$current_question_ident]['wrong_answers'] = [];
+                    $exerciseInfo['question'][$currentQuestionIdent]['weighting'][$answer_id] = $node->getAttribute(
+                        'mappedValue'
+                    );
                 }
-                $exercise_info['question'][$current_question_ident]['wrong_answers'][] = $data;
-            }
-            break;
-        case 'MATTEXT':
-            if ($grand_parent_element == 'FLOW_MAT' &&
-                ($great_grand_parent_element == 'PRESENTATION_MATERIAL' || $great_grand_parent_element == 'SECTION')
-            ) {
-                if (!empty(trim($data))) {
-                    $exercise_info['description'] = $data;
+                break;
+            case 'mapping':
+                $defaultValue = $node->getAttribute('defaultValue');
+
+                if (!empty($defaultValue)) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['default_weighting'] = $defaultValue;
                 }
-            }
-            break;
-    }
-}
+                // no break ?
+            case 'itemBody':
+                $nodeValue = $node->nodeValue;
 
-/**
- * Function used by the SAX xml parser when the parser meets a opening tag for QTI1.
- *
- * @param object $parser     xml parser created with "xml_parser_create()"
- * @param string $name       name of the element
- * @param array  $attributes
- */
-function startElementQti1($parser, $name, $attributes)
-{
-    global $element_pile;
-    global $exercise_info;
-    global $current_question_ident;
-    global $current_answer_id;
-    global $current_match_set;
-    global $currentAssociableChoice;
-    global $current_question_item_body;
-    global $record_item_body;
-    global $non_HTML_tag_to_avoid;
-    global $current_inlinechoice_id;
-    global $cardinality;
-    global $questionTempDir;
-    global $lastLabelFieldName;
-    global $lastLabelFieldValue;
-
-    array_push($element_pile, $name);
-    $current_element = end($element_pile);
-    if (sizeof($element_pile) >= 2) {
-        $parent_element = $element_pile[sizeof($element_pile) - 2];
-    } else {
-        $parent_element = '';
-    }
-    if (sizeof($element_pile) >= 3) {
-        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
-    } else {
-        $grand_parent_element = "";
-    }
-    if (sizeof($element_pile) >= 4) {
-        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
-    } else {
-        $great_grand_parent_element = "";
-    }
+                $currentQuestionItemBody = '';
 
-    if ($record_item_body) {
-        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
-            $current_question_item_body .= "<".$name;
-            foreach ($attributes as $attribute_name => $attribute_value) {
-                $current_question_item_body .= " ".$attribute_name."=\"".$attribute_value."\"";
-            }
-            $current_question_item_body .= ">";
-        } else {
-            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
-            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
-            if ($current_element == 'INLINECHOICEINTERACTION') {
-                $current_question_item_body .= "**claroline_start**".$attributes['RESPONSEIDENTIFIER']."**claroline_end**";
-            }
-            if ($current_element == 'TEXTENTRYINTERACTION') {
-                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
-                $current_question_item_body .= "[".$correct_answer_value."]";
-            }
-            if ($current_element == 'BR') {
-                $current_question_item_body .= "<br />";
-            }
-        }
-    }
+                /** @var DOMElement $childNode */
+                foreach ($node->childNodes as $childNode) {
+                    if ('#text' === $childNode->nodeName) {
+                        continue;
+                    }
 
-    switch ($current_element) {
-        case 'ASSESSMENT':
-            // This is the assessment element: we don't care, we just want questions
-            if (!empty($attributes['TITLE'])) {
-                $exercise_info['name'] = $attributes['TITLE'];
-            }
-            break;
-        case 'ITEM':
-            //retrieve current question
-            $current_question_ident = $attributes['IDENT'];
-            $exercise_info['question'][$current_question_ident] = [];
-            $exercise_info['question'][$current_question_ident]['answer'] = [];
-            $exercise_info['question'][$current_question_ident]['correct_answers'] = [];
-            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
-            break;
-        case 'SECTION':
-            break;
-        case 'RESPONSE_LID':
-            // Retrieve question type
-            if ("multiple" == strtolower($attributes['RCARDINALITY'])) {
-                $cardinality = 'multiple';
-            }
-            if ("single" == strtolower($attributes['RCARDINALITY'])) {
-                $cardinality = 'single';
-            }
-            //needed for FIB
-            $current_answer_id = $attributes['IDENT'];
-            $current_question_item_body = '';
-            break;
-        case 'RENDER_CHOICE':
-            break;
-        case 'RESPONSE_LABEL':
-            if (!empty($attributes['IDENT'])) {
-                $current_answer_id = $attributes['IDENT'];
-                //set the placeholder for the answer to come (in endElementQti1)
-                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = '';
-            }
-            break;
-        case 'DECVAR':
-            if ($parent_element == 'OUTCOMES' && $grand_parent_element == 'RESPROCESSING') {
-                // The following attributes are available
-                //$attributes['VARTYPE'];
-                //$attributes['DEFAULTVAL'];
-                //$attributes['MINVALUE'];
-                //$attributes['MAXVALUE'];
-            }
-            break;
-        case 'VAREQUAL':
-            if ($parent_element == 'CONDITIONVAR' && $grand_parent_element == 'RESPCONDITION') {
-                // The following attributes are available
-                //$attributes['RESPIDENT']
-            }
-            break;
-        case 'SETVAR':
-            if ($parent_element == 'RESPCONDITION') {
-                // The following attributes are available
-                //$attributes['ACTION']
-            }
-            break;
-        case 'IMG':
-            break;
-        case 'MATTEXT':
-            if ($parent_element == 'MATERIAL') {
-                if ($grand_parent_element == 'PRESENTATION') {
-                    $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
+                    if (!in_array($childNode->nodeName, $nonHTMLTagToAvoid)) {
+                        $currentQuestionItemBody .= '<'.$childNode->nodeName;
+
+                        if ($childNode->attributes) {
+                            foreach ($childNode->attributes as $attribute) {
+                                $currentQuestionItemBody .= ' '.$attribute->nodeName.'="'.$attribute->nodeValue.'"';
+                            }
+                        }
+
+                        $currentQuestionItemBody .= '>'
+                            .$childNode->nodeValue
+                            .'</'.$node->nodeName.'>';
+
+                        continue;
+                    }
+
+                    if ('inlineChoiceInteraction' === $childNode->nodeName) {
+                        $currentQuestionItemBody .= "**claroline_start**"
+                            .$childNode->attr('responseIdentifier')
+                            ."**claroline_end**";
+
+                        continue;
+                    }
+
+                    if ('textEntryInteraction' === $childNode->nodeName) {
+                        $correct_answer_value = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId];
+                        $currentQuestionItemBody .= "[".$correct_answer_value."]";
+
+                        continue;
+                    }
+
+                    if ('br' === $childNode->nodeName) {
+                        $currentQuestionItemBody .= '<br>';
+                    }
                 }
-            }
-            break;
-    }
-}
 
-/**
- * Function used by the SAX xml parser when the parser meets a closing tag for QTI1.
- *
- * @param object $parser     xml parser created with "xml_parser_create()"
- * @param string $name       name of the element
- * @param array  $attributes The element attributes
- */
-function endElementQti1($parser, $name, $attributes)
-{
-    global $element_pile;
-    global $exercise_info;
-    global $current_question_ident;
-    global $record_item_body;
-    global $current_question_item_body;
-    global $non_HTML_tag_to_avoid;
-    global $cardinality;
-    global $lastLabelFieldName;
-    global $lastLabelFieldValue;
-    global $resourcesLinks;
+                // Replace relative links by links to the documents in the course
+                // $resourcesLinks is only defined by qtiProcessManifest()
+                if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
+                    foreach ($resourcesLinks['manifest'] as $key => $value) {
+                        $nodeValue = preg_replace('|'.$value.'|', $resourcesLinks['web'][$key], $nodeValue);
+                    }
+                }
 
-    $current_element = end($element_pile);
-    if (sizeof($element_pile) >= 2) {
-        $parent_element = $element_pile[sizeof($element_pile) - 2];
-    } else {
-        $parent_element = "";
-    }
-    if (sizeof($element_pile) >= 3) {
-        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
-    } else {
-        $grand_parent_element = "";
-    }
-    if (sizeof($element_pile) >= 4) {
-        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
-    } else {
-        $great_grand_parent_element = "";
-    }
+                $currentQuestionItemBody .= $node->firstChild->nodeValue;
 
-    //treat the record of the full content of itembody tag :
-    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
-        $current_question_item_body .= "</".$name.">";
-    }
+                if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FIB) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody;
+                } else {
+                    if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FREE_ANSWER) {
+                        $currentQuestionItemBody = trim($currentQuestionItemBody);
 
-    switch ($name) {
-        case 'MATTEXT':
-            if ($parent_element == 'MATERIAL') {
-                // For some reason an item in a hierarchy <item><presentation><material><mattext> doesn't seem to
-                // catch the grandfather 'presentation', so we check for 'item' as a patch (great-grand-father)
-                if ($grand_parent_element == 'PRESENTATION' || $grand_parent_element == 'ITEM') {
-                    $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
-                    $current_question_item_body = '';
-                } elseif ($grand_parent_element == 'RESPONSE_LABEL') {
-                    $last = '';
-                    foreach ($exercise_info['question'][$current_question_ident]['answer'] as $key => $value) {
-                        $last = $key;
+                        if (!empty($currentQuestionItemBody)) {
+                            $exerciseInfo['question'][$currentQuestionIdent]['description'] = $currentQuestionItemBody;
+                        }
+                    } else {
+                        $exerciseInfo['question'][$currentQuestionIdent]['statement'] = $currentQuestionItemBody;
                     }
-                    $exercise_info['question'][$current_question_ident]['answer'][$last]['value'] = $current_question_item_body;
-                    $current_question_item_body = '';
                 }
-            }
-            // no break ?
-        case 'RESPONSE_LID':
-            // Retrieve question type
-            if (!isset($exercise_info['question'][$current_question_ident]['type'])) {
-                if ("multiple" == strtolower($attributes['RCARDINALITY'])) {
-                    $exercise_info['question'][$current_question_ident]['type'] = MCMA;
+                break;
+            case 'img':
+                $exerciseInfo['question'][$currentQuestionIdent]['attached_file_url'] = $node->getAttribute('src');
+                break;
+            case 'order':
+                $orderType = $node->getAttribute('order_type');
+
+                if (!empty($orderType)) {
+                    $exerciseInfo['order_type'] = $orderType;
                 }
-                if ("single" == strtolower($attributes['RCARDINALITY'])) {
-                    $exercise_info['question'][$current_question_ident]['type'] = MCUA;
+                break;
+            case 'feedbackInline':
+                if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'])) {
+                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId] = trim(
+                        $node->nodeValue
+                    );
+                } else {
+                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] .= ''
+                        .trim(
+                            $node->nodeValue
+                        );
                 }
-            }
-            $current_question_item_body = '';
-            //needed for FIB
-            $current_answer_id = $attributes['IDENT'];
-            break;
-        case 'ITEMMETADATA':
-            $current_question_item_body = '';
-            break;
-    }
-    array_pop($element_pile);
-}
+                break;
+            case 'value':
+                if ('correctResponse' === $node->parentNode->nodeName) {
+                    $nodeValue = trim($node->nodeValue);
 
-/**
- * QTI1 element parser.
- *
- * @param $parser
- * @param $data
- */
-function elementDataQti1($parser, $data)
-{
-    global $element_pile;
-    global $exercise_info;
-    global $current_question_ident;
-    global $current_answer_id;
-    global $current_match_set;
-    global $currentAssociableChoice;
-    global $current_question_item_body;
-    global $record_item_body;
-    global $non_HTML_tag_to_avoid;
-    global $current_inlinechoice_id;
-    global $cardinality;
-    global $lastLabelFieldName;
-    global $lastLabelFieldValue;
-    global $resourcesLinks;
-
-    $current_element = end($element_pile);
-    if (sizeof($element_pile) >= 2) {
-        $parent_element = $element_pile[sizeof($element_pile) - 2];
-    } else {
-        $parent_element = "";
-    }
-    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
+                    if ('single' === $cardinality) {
+                        $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$nodeValue] = $nodeValue;
+                    } else {
+                        $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][] = $nodeValue;
+                    }
+                }
 
-    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
-        $current_question_item_body .= $data;
-    }
+                if ('outcomeDeclaration' === $node->parentNode->parentNode->nodeName) {
+                    $nodeValue = trim($node->nodeValue);
 
-    switch ($current_element) {
-        case 'FIELDLABEL':
-            if (!empty($data)) {
-                $lastLabelFieldName = $current_element;
-                $lastLabelFieldValue = $data;
-            }
-            // no break ?
-        case 'FIELDENTRY':
-            $current_question_item_body = $data;
-            switch ($lastLabelFieldValue) {
-                case 'cc_profile':
-                    // The following values might be proprietary in MATRIX software. No specific reference
-                    // in QTI doc: http://www.imsglobal.org/question/qtiv1p2/imsqti_asi_infov1p2.html#1415855
-                    switch ($data) {
-                        case 'cc.true_false.v0p1':
-                            //this is a true-false question (translated to multiple choice in Chamilo because true-false comes with "I don't know")
-                            $exercise_info['question'][$current_question_ident]['type'] = MCUA;
-                            break;
-                        case 'cc.multiple_choice.v0p1':
-                            //this is a multiple choice (unique answer) question
-                            $exercise_info['question'][$current_question_ident]['type'] = MCUA;
-                            break;
-                        case 'cc.multiple_response.v0p1':
-                            //this is a multiple choice (unique answer) question
-                            $exercise_info['question'][$current_question_ident]['type'] = MCMA;
-                            break;
+                    if (!empty($nodeValue)) {
+                        $exerciseInfo['question'][$currentQuestionIdent]['weighting'][0] = $nodeValue;
                     }
-                    break;
-                case 'cc_weighting':
-                    //defines the total weight of the question
-                    $exercise_info['question'][$current_question_ident]['default_weighting'] = $lastLabelFieldValue;
-                    break;
-                case 'assessment_question_identifierref':
-                    //placeholder - not used yet
-                    // Possible values are not defined by qti v1.2
-                    break;
-            }
-            break;
-        case 'MATTEXT':
-            // Replace relative links by links to the documents in the course
-            // $resourcesLinks is only defined by qtiProcessManifest()
-            if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
-                foreach ($resourcesLinks['manifest'] as $key => $value) {
-                    $data = preg_replace('|'.$value.'|', $resourcesLinks['web'][$key], $data);
                 }
-            }
-            if (!empty($current_question_item_body)) {
-                $current_question_item_body .= $data;
-            } else {
-                $current_question_item_body = $data;
-            }
-            break;
-        case 'VAREQUAL':
-            $lastLabelFieldName = 'VAREQUAL';
-            $lastLabelFieldValue = $data;
-            break;
-        case 'SETVAR':
-            if ($parent_element == 'RESPCONDITION') {
-                // The following attributes are available
-                //$attributes['ACTION']
-                $exercise_info['question'][$current_question_ident]['correct_answers'][] = $lastLabelFieldValue;
-                $exercise_info['question'][$current_question_ident]['weighting'][$lastLabelFieldValue] = $data;
-            }
-            break;
+                break;
+            case 'mattext':
+                if ('flow_mat' === $node->parentNode->parentNode->nodeName &&
+                    ('presentation_material' === $node->parentNode->parentNode->parentNode->nodeName ||
+                        'section' === $node->parentNode->parentNode->parentNode->nodeName
+                    )
+                ) {
+                    $nodeValue = trim($node->nodeValue);
+
+                    if (!empty($nodeValue)) {
+                        $exerciseInfo['description'] = $node->nodeValue;
+                    }
+                }
+                break;
+        }
     }
 }
 

+ 0 - 2
main/exercise/overview.php

@@ -45,8 +45,6 @@ $logInfo = [
     'tool_id_detail' => 0,
     'action' => isset($_REQUEST['learnpath_id']) ? 'learnpath_id' : '',
     'action_details' => isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : '',
-    'current_id' => 0,
-    'info' => '',
 ];
 Event::registerLog($logInfo);
 

+ 25 - 5
main/exercise/question.class.php

@@ -18,6 +18,7 @@ use Chamilo\CourseBundle\Entity\CQuizAnswer;
 abstract class Question
 {
     public $id;
+    public $iid;
     public $question;
     public $description;
     public $weighting;
@@ -38,6 +39,7 @@ abstract class Question
     public $questionTypeWithFeedback;
     public $extra;
     public $export = false;
+    public $code;
     public static $questionTypes = [
         UNIQUE_ANSWER => ['unique_answer.class.php', 'UniqueAnswer'],
         MULTIPLE_ANSWER => ['multiple_answer.class.php', 'MultipleAnswer'],
@@ -76,6 +78,7 @@ abstract class Question
     public function __construct()
     {
         $this->id = 0;
+        $this->iid = 0;
         $this->question = '';
         $this->description = '';
         $this->weighting = 0;
@@ -154,6 +157,7 @@ abstract class Question
             $objQuestion = self::getInstance($object->type);
             if (!empty($objQuestion)) {
                 $objQuestion->id = (int) $id;
+                $objQuestion->iid = (int) $object->iid;
                 $objQuestion->question = $object->question;
                 $objQuestion->description = $object->description;
                 $objQuestion->weighting = $object->ponderation;
@@ -165,6 +169,7 @@ abstract class Question
                 $objQuestion->course = $course_info;
                 $objQuestion->feedback = isset($object->feedback) ? $object->feedback : '';
                 $objQuestion->category = TestCategory::getCategoryForQuestion($id, $course_id);
+                $objQuestion->code = isset($object->code) ? $object->code : '';
 
                 $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
                 $sql = "SELECT DISTINCT q.exercice_id
@@ -229,8 +234,12 @@ abstract class Question
     public function getTitleToDisplay($itemNumber)
     {
         $showQuestionTitleHtml = api_get_configuration_value('save_titles_as_html');
+        $title = '';
+        if (api_get_configuration_value('show_question_id')) {
+            $title .= '<h4>#'.$this->course['code'].'-'.$this->iid.'</h4>';
+        }
 
-        $title = $showQuestionTitleHtml ? '' : '<strong>';
+        $title .= $showQuestionTitleHtml ? '' : '<strong>';
         $title .= $itemNumber.'. '.$this->selectTitle();
         $title .= $showQuestionTitleHtml ? '' : '</strong>';
 
@@ -403,7 +412,7 @@ abstract class Question
      */
     public function selectNbrExercises()
     {
-        return sizeof($this->exerciseList);
+        return count($this->exerciseList);
     }
 
     /**
@@ -423,7 +432,7 @@ abstract class Question
      */
     public function updateParentId($id)
     {
-        $this->parent_id = intval($id);
+        $this->parent_id = (int) $id;
     }
 
     /**
@@ -934,6 +943,12 @@ abstract class Question
                 $params,
                 ['c_id = ? AND id = ?' => [$c_id, $id]]
             );
+
+            Event::addEvent(
+                LOG_QUESTION_UPDATED,
+                LOG_QUESTION_ID,
+                $this->iid
+            );
             $this->saveCategory($categoryId);
 
             if (!empty($exerciseId)) {
@@ -991,6 +1006,12 @@ abstract class Question
                 $sql = "UPDATE $TBL_QUESTIONS SET id = iid WHERE iid = {$this->id}";
                 Database::query($sql);
 
+                Event::addEvent(
+                    LOG_QUESTION_CREATED,
+                    LOG_QUESTION_ID,
+                    $this->id
+                );
+
                 api_item_property_update(
                     $this->course,
                     TOOL_QUIZ,
@@ -1733,7 +1754,6 @@ abstract class Question
         if ($this->type != MEDIA_QUESTION) {
             $this->save($exercise);
 
-            // modify the exercise
             $exercise->addToList($this->id);
             $exercise->update_question_positions();
         }
@@ -1759,7 +1779,7 @@ abstract class Question
      *
      * @param Exercise $objExercise
      */
-    public static function display_type_menu($objExercise)
+    public static function displayTypeMenu($objExercise)
     {
         $feedback_type = $objExercise->feedback_type;
         $exerciseId = $objExercise->id;

+ 12 - 2
main/exercise/question_admin.inc.php

@@ -35,8 +35,13 @@ if (is_object($objQuestion)) {
     $typesInformation = Question::get_question_type_list();
     $form_title_extra = isset($typesInformation[$type][1]) ? get_lang($typesInformation[$type][1]) : null;
 
+    $code = '';
+    if (isset($objQuestion->code) && !empty($objQuestion->code)) {
+        $code = ' ('.$objQuestion->code.')';
+    }
+
     // form title
-    $form->addHeader($text.': '.$form_title_extra);
+    $form->addHeader($text.': '.$form_title_extra.$code);
 
     // question form elements
     $objQuestion->createForm($form, $objExercise);
@@ -63,7 +68,12 @@ if (is_object($objQuestion)) {
                 echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&message=ItemUpdated"</script>';
             } else {
                 // New question
-                echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&message=ItemAdded"</script>';
+                $page = '';
+                $length = api_get_configuration_value('question_pagination_length');
+                if ($length) {
+                    $page = round($objExercise->get_count_question_list() / $length);
+                }
+                echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&page='.$page.'&message=ItemAdded"</script>';
             }
         } else {
             echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&hotspotadmin='.$objQuestion->id.'&'.api_get_cidreq().'"</script>';

+ 62 - 35
main/exercise/question_list_admin.inc.php

@@ -14,9 +14,14 @@ use ChamiloSession as Session;
  *    This script allows to manage the question list
  *    It is included from the script admin.php
  */
+$limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
 
 // deletes a question from the exercise (not from the data base)
 if ($deleteQuestion) {
+    if ($limitTeacherAccess && !api_is_platform_admin()) {
+        exit;
+    }
+
     // if the question exists
     if ($objQuestionTmp = Question::read($deleteQuestion)) {
         $objQuestionTmp->delete($exerciseId);
@@ -32,10 +37,10 @@ if ($deleteQuestion) {
 $ajax_url = api_get_path(WEB_AJAX_PATH)."exercise.ajax.php?".api_get_cidreq()."&exercise_id=".intval($exerciseId);
 ?>
 <div id="dialog-confirm"
-     title="<?php echo get_lang("ConfirmYourChoice"); ?>"
+     title="<?php echo get_lang('ConfirmYourChoice'); ?>"
      style="display:none;">
     <p>
-        <?php echo get_lang("AreYouSureToDelete"); ?>
+        <?php echo get_lang('AreYouSureToDelete'); ?>
     </p>
 </div>
 
@@ -144,9 +149,7 @@ $ajax_url = api_get_path(WEB_AJAX_PATH)."exercise.ajax.php?".api_get_cidreq()."&
 <?php
 
 //we filter the type of questions we can add
-Question::display_type_menu($objExercise);
-// Re sets the question list
-$objExercise->setQuestionList();
+Question::displayTypeMenu($objExercise);
 
 echo '<div id="message"></div>';
 $token = Security::get_token();
@@ -156,10 +159,50 @@ Session::erase('less_answer');
 // If we are in a test
 $inATest = isset($exerciseId) && $exerciseId > 0;
 if (!$inATest) {
-    echo "<div class='alert alert-warning'>"
-        .get_lang("ChoiceQuestionType")
-        ."</div>";
+    echo "<div class='alert alert-warning'>".get_lang('ChoiceQuestionType')."</div>";
 } else {
+    if ($nbrQuestions) {
+        // In the building exercise mode show question list ordered as is.
+        $objExercise->setCategoriesGrouping(false);
+
+        // In building mode show all questions not render by teacher order.
+        $objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED;
+        $alloQuestionOrdering = true;
+        $showPagination = api_get_configuration_value('show_question_pagination');
+        if (!empty($showPagination) && $nbrQuestions > $showPagination) {
+            $page = isset($_GET['page']) ? (int) $_GET['page'] : 1;
+            $length = api_get_configuration_value('question_pagination_length');
+            $url = api_get_self().'?'.api_get_cidreq();
+            // Use pagination for exercise with more than 200 questions.
+            $alloQuestionOrdering = false;
+            $start = ($page - 1) * $length;
+            $questionList = $objExercise->getQuestionForTeacher($start, $length);
+            $paginator = new Knp\Component\Pager\Paginator();
+            $pagination = $paginator->paginate([]);
+            $pagination->setTotalItemCount($nbrQuestions);
+            $pagination->setItemNumberPerPage($length);
+            $pagination->setCurrentPageNumber($page);
+            $pagination->renderer = function ($data) use ($url) {
+                $render = '<ul class="pagination">';
+                for ($i = 1; $i <= $data['pageCount']; $i++) {
+                    //foreach ($data['pagesInRange'] as $page) {
+                    $page = (int) $i;
+                    $pageContent = '<li><a href="'.$url.'&page='.$page.'">'.$page.'</a></li>';
+                    if ($data['current'] == $page) {
+                        $pageContent = '<li class="active"><a href="#" >'.$page.'</a></li>';
+                    }
+                    $render .= $pageContent;
+                }
+                $render .= '</ul>';
+
+                return $render;
+            };
+            echo $pagination;
+        } else {
+            // Classic order
+            $questionList = $objExercise->selectQuestionList(true, true);
+        }
+
     echo '
         <div class="row hidden-xs">
             <div class="col-sm-5"><strong>'.get_lang('Questions').'</strong></div>
@@ -172,26 +215,7 @@ if (!$inATest) {
         <div id="question_list">
     ';
 
-    if ($nbrQuestions) {
-        // Always getting list from DB
-        //$questionList = $objExercise->selectQuestionList(true);
-
-        // In the building exercise mode show question list ordered as is.
-        $objExercise->setCategoriesGrouping(false);
-
-        // Show exercises as in category settings
-        //$questionList = $objExercise->getQuestionListWithMediasUncompressed();
-
-        // In building mode show all questions not render by teacher order.
-        $objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED;
-
-        // Get question list
-        $questionList = $objExercise->selectQuestionList(true, true);
-
-        $category_list = TestCategory::getListOfCategoriesNameForTest(
-            $objExercise->id,
-            false
-        );
+        $category_list = TestCategory::getListOfCategoriesNameForTest($objExercise->id, false);
 
         if (is_array($questionList)) {
             foreach ($questionList as $id) {
@@ -242,7 +266,6 @@ if (!$inATest) {
                         ['class' => 'btn btn-default btn-sm']
                     );
                 $delete_link = null;
-
                 if ($objExercise->edit_exercise_in_lp == true) {
                     $delete_link = Display::url(
                         Display::return_icon(
@@ -263,6 +286,10 @@ if (!$inATest) {
                     );
                 }
 
+                if ($limitTeacherAccess && !api_is_platform_admin()) {
+                    $delete_link = '';
+                }
+
                 $btnActions = implode(
                     PHP_EOL,
                     [$edit_link, $clone_link, $delete_link]
@@ -270,7 +297,10 @@ if (!$inATest) {
 
                 $title = Security::remove_XSS($objQuestionTmp->selectTitle());
                 $title = strip_tags($title);
-                $move = Display::returnFontAwesomeIcon("arrows moved", 1, true);
+                $move = '&nbsp;';
+                if ($alloQuestionOrdering) {
+                    $move = Display::returnFontAwesomeIcon('arrows moved', 1, true);
+                }
 
                 // Question name
                 $questionName =
@@ -300,8 +330,7 @@ if (!$inATest) {
                 // Question score
                 $questionScore = $objQuestionTmp->selectWeighting();
 
-                echo '
-                    <div id="question_id_list_'.$id.'">
+                echo '<div id="question_id_list_'.$id.'">
                         <div class="header_operations" data-exercise="'.$objExercise->selectId().'"
                             data-question="'.$id.'">
                             <div class="row">
@@ -336,9 +365,7 @@ if (!$inATest) {
                 unset($objQuestionTmp);
             }
         }
-    }
-
-    if (!$nbrQuestions) {
+    } else {
         echo Display::return_message(get_lang('NoQuestion'), 'warning');
     }
     echo '</div>'; //question list div

+ 409 - 330
main/exercise/question_pool.php

@@ -2,6 +2,7 @@
 /* For licensing terms, see /license.txt */
 
 use ChamiloSession as Session;
+use Knp\Component\Pager\Paginator;
 
 /**
  * Question Pool
@@ -27,7 +28,6 @@ $exerciseId = isset($_REQUEST['exerciseId']) ? intval($_REQUEST['exerciseId']) :
 $courseCategoryId = isset($_REQUEST['courseCategoryId']) ? intval($_REQUEST['courseCategoryId']) : null;
 $exerciseLevel = isset($_REQUEST['exerciseLevel']) ? intval($_REQUEST['exerciseLevel']) : -1;
 $answerType = isset($_REQUEST['answerType']) ? intval($_REQUEST['answerType']) : null;
-$page = isset($_REQUEST['page']) ? intval($_REQUEST['page']) : 0;
 $question_copy = isset($_REQUEST['question_copy']) ? intval($_REQUEST['question_copy']) : 0;
 $session_id = isset($_REQUEST['session_id']) ? intval($_REQUEST['session_id']) : null;
 $selected_course = isset($_GET['selected_course']) ? intval($_GET['selected_course']) : null;
@@ -35,6 +35,9 @@ $selected_course = isset($_GET['selected_course']) ? intval($_GET['selected_cour
 $course_id_changed = isset($_GET['course_id_changed']) ? intval($_GET['course_id_changed']) : null;
 // save the id of the previous exercise selected by user to reset menu if we detect that user change course hub 13-10-2011
 $exercise_id_changed = isset($_GET['exercise_id_changed']) ? intval($_GET['exercise_id_changed']) : null;
+$questionId = isset($_GET['question_id']) && !empty($_GET['question_id']) ? (int) $_GET['question_id'] : '';
+$description = isset($_GET['description']) ? Database::escape_string($_GET['description']) : '';
+$page = isset($_GET['page']) ? (int) $_GET['page'] : 1;
 
 // by default when we go to the page for the first time, we select the current course
 if (!isset($_GET['selected_course']) && !isset($_GET['exerciseId'])) {
@@ -49,12 +52,12 @@ if (empty($objExercise) && !empty($fromExercise)) {
 }
 
 $nameTools = get_lang('QuestionPool');
-$interbreadcrumb[] = ["url" => "exercise.php?".api_get_cidreq(), "name" => get_lang('Exercises')];
+$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
 
 if (!empty($objExercise)) {
     $interbreadcrumb[] = [
-        "url" => "admin.php?exerciseId=".$objExercise->id."&".api_get_cidreq(),
-        "name" => $objExercise->selectTitle(true),
+        'url' => 'admin.php?exerciseId='.$objExercise->id.'&'.api_get_cidreq(),
+        'name' => $objExercise->selectTitle(true),
     ];
 }
 
@@ -71,9 +74,7 @@ if ($is_allowedToEdit) {
         $old_question_obj = Question::read($old_question_id, $origin_course_id);
         $courseId = $current_course['real_id'];
         if ($old_question_obj) {
-            $old_question_obj->updateTitle(
-                $old_question_obj->selectTitle().' - '.get_lang('Copy')
-            );
+            $old_question_obj->updateTitle($old_question_obj->selectTitle().' - '.get_lang('Copy'));
             //Duplicating the source question, in the current course
             $new_id = $old_question_obj->duplicate($current_course);
             //Reading new question
@@ -97,6 +98,10 @@ if ($is_allowedToEdit) {
 
     // Deletes a question from the database and all exercises
     if ($delete) {
+        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
+        if ($limitTeacherAccess && !api_is_platform_admin()) {
+            api_not_allowed(true);
+        }
         // Construction of the Question object
         $objQuestionTmp = Question::read($delete);
         // if the question exists
@@ -207,11 +212,22 @@ Display::display_header($nameTools, 'Exercise');
 
 // Menu
 echo '<div class="actions">';
-if (isset($type)) {
-    $url = api_get_self().'?type=1';
-} else {
-    $url = api_get_self();
-}
+$url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query(
+    [
+        'fromExercise' => $fromExercise,
+        'session_id' => $session_id,
+        'selected_course' => $selected_course,
+        'courseCategoryId' => $courseCategoryId,
+        'exerciseId' => $exerciseId,
+        'exerciseLevel' => $exerciseLevel,
+        'answerType' => $answerType,
+        'question_id' => $questionId,
+        'description' => Security::remove_XSS($description),
+        'course_id_changed' => $course_id_changed,
+        'exercise_id_changed' => $exercise_id_changed,
+    ]
+);
+
 if (isset($fromExercise) && $fromExercise > 0) {
     echo '<a href="admin.php?'.api_get_cidreq().'&exerciseId='.$fromExercise.'">'.
             Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
@@ -219,35 +235,36 @@ if (isset($fromExercise) && $fromExercise > 0) {
 } else {
     echo '<a href="exercise.php?'.api_get_cidreq().'">'.
         Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).'</a>';
-    echo "<a href='admin.php?exerciseId=0'>".Display::return_icon('add_question.gif', get_lang('NewQu'), '', ICON_SIZE_MEDIUM)."</a>";
+    echo "<a href='admin.php?exerciseId=0'>".
+        Display::return_icon('add_question.gif', get_lang('NewQu'), '', ICON_SIZE_MEDIUM)."</a>";
     $titleAdd = get_lang('ManageAllQuestions');
 }
 echo '</div>';
 
-if ($displayMessage != "") {
+if ($displayMessage != '') {
     echo Display::return_message($displayMessage, 'confirm');
-    $displayMessage = "";
+    $displayMessage = '';
 }
 
 // Form
 echo '<form class="form-horizontal" name="question_pool" method="GET" action="'.$url.'">';
 // Title
 echo '<legend>'.$nameTools.' - '.$titleAdd.'</legend>';
-if (isset($type)) {
-    echo '<input type="hidden" name="type" value="1">';
-}
 echo '<input type="hidden" name="fromExercise" value="'.$fromExercise.'">';
 
 // Session list, if sessions are used.
 $sessionList = SessionManager::get_sessions_by_user(api_get_user_id(), api_is_platform_admin());
-$tabAttrParam = ['onchange' => 'submit_form(this)'];
-$labelFormRow = get_lang('Session');
 $session_select_list = [];
 foreach ($sessionList as $item) {
     $session_select_list[$item['session_id']] = $item['session_name'];
 }
-$select_session_html = Display::select('session_id', $session_select_list, $session_id, $tabAttrParam);
-echo Display::form_row($labelFormRow, $select_session_html);
+$select_session_html = Display::select(
+    'session_id',
+    $session_select_list,
+    $session_id,
+    ['onchange' => 'submit_form(this)']
+);
+echo Display::form_row(get_lang('Session'), $select_session_html);
 
 // Course list, get course list of session, or for course where user is admin
 if (!empty($session_id) && $session_id != '-1' && !empty($sessionList)) {
@@ -311,17 +328,8 @@ if ($course_id_changed) {
 }
 
 $course_id = $course_info['real_id'];
-// Redefining table calls
-$TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
-$TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
-$TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
-$TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
-$TBL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
-$TBL_COURSE_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
-
-// Get course categories for the selected course
 
-// get category list for the course $selected_course
+// Get category list for the course $selected_course
 $categoryList = TestCategory::getCategoriesIdAndName($selected_course);
 $selectCourseCategory = Display::select(
     'courseCategoryId',
@@ -339,7 +347,8 @@ $exercise_list = ExerciseLib::get_all_exercises_for_course_id(
     $selected_course,
     false
 );
-//Exercise List
+
+// Exercise List
 $my_exercise_list = [];
 $my_exercise_list['0'] = get_lang('AllExercises');
 $my_exercise_list['-1'] = get_lang('OrphanQuestions');
@@ -356,6 +365,7 @@ if (is_array($exercise_list)) {
 if ($exercise_id_changed == 1) {
     reset_menu_lvl_type();
 }
+
 $select_exercise_html = Display::select(
     'exerciseId',
     $my_exercise_list,
@@ -376,6 +386,7 @@ $levels = [
     4 => 4,
     5 => 5,
 ];
+
 $select_difficulty_html = Display::select(
     'exerciseLevel',
     $levels,
@@ -407,6 +418,7 @@ if (!empty($_course)) {
         }
     }
 }
+
 // Answer type list
 $select_answer_html = Display::select(
     'answerType',
@@ -417,220 +429,215 @@ $select_answer_html = Display::select(
 );
 
 echo Display::form_row(get_lang('AnswerType'), $select_answer_html);
-$button = '<button class="btn btn-primary save" type="submit" name="name" value="'.get_lang('Filter').'">'.get_lang('Filter').'</button>';
+echo Display::form_row(get_lang('Id'), Display::input('text', 'question_id', $questionId));
+echo Display::form_row(
+    get_lang('Description'),
+    Display::input('text', 'description', Security::remove_XSS($description))
+);
+
+$button = '<button class="btn btn-primary save" type="submit" name="name" value="'.get_lang('Filter').'">'.
+    get_lang('Filter').'</button>';
 echo Display::form_row('', $button);
 echo "<input type='hidden' id='course_id_changed' name='course_id_changed' value='0' />";
 echo "<input type='hidden' id='exercise_id_changed' name='exercise_id_changed' value='0' />";
 ?>
 </form>
 <div class="clear"></div>
-<form method="post" action="<?php echo $url.'?'.api_get_cidreq().'&fromExercise='.$fromExercise; ?>" >
 <?php
-echo '<input type="hidden" name="course_id" value="'.$selected_course.'">';
-$mainQuestionList = [];
-
-// if we have selected an exercise in the list-box 'Filter'
-if ($exerciseId > 0) {
-    $where = '';
-    $from = '';
-    if (isset($courseCategoryId) && $courseCategoryId > 0) {
-        $from = ", $TBL_COURSE_REL_CATEGORY crc ";
-        $where .= " AND 
-                crc.c_id = $selected_course AND 
-                crc.question_id = qu.id AND 
-                crc.category_id = $courseCategoryId";
-    }
-    if (isset($exerciseLevel) && $exerciseLevel != -1) {
-        $where .= ' AND level='.$exerciseLevel;
-    }
-    if (isset($answerType) && $answerType > 0) {
-        $where .= ' AND type='.$answerType;
-    }
-    $sql = "SELECT DISTINCT
-                id,
-                question,
-                type,
-                level
-            FROM
-                $TBL_EXERCISE_QUESTION qt,
-                $TBL_QUESTIONS qu
-                $from
-            WHERE
-                qt.question_id = qu.id
-                AND qt.exercice_id=$exerciseId
-                AND qt.c_id=$selected_course
-                AND qu.c_id=$selected_course
-                $where
-            ORDER BY question_order";
 
-    $result = Database::query($sql);
-    while ($row = Database::fetch_array($result, 'ASSOC')) {
-        $mainQuestionList[] = $row;
-    }
-} elseif ($exerciseId == -1) {
-    // If we have selected the option 'Orphan questions' in the list-box 'Filter'
-    $level_where = '';
-    $from = '';
-    if (isset($courseCategoryId) && $courseCategoryId > 0) {
-        $from = " INNER JOIN $TBL_COURSE_REL_CATEGORY crc 
-                  ON crc.question_id = q.id AND crc.c_id = q.c_id ";
-        $level_where .= " AND
-                crc.c_id = $selected_course AND
-                crc.category_id = $courseCategoryId";
-    }
-    if (isset($exerciseLevel) && $exerciseLevel != -1) {
-        $level_where = ' AND level='.$exerciseLevel;
-    }
-    $answer_where = '';
-    if (isset($answerType) && $answerType > 0 - 1) {
-        $answer_where = ' AND type='.$answerType;
-    }
+function getQuestions(
+    $getCount,
+    $start,
+    $length,
+    $exerciseId,
+    $courseCategoryId,
+    $selected_course,
+    $session_id,
+    $exerciseLevel,
+    $answerType,
+    $questionId,
+    $description
+) {
+    $start = (int) $start;
+    $length = (int) $length;
+    $exerciseId = (int) $exerciseId;
+    $courseCategoryId = (int) $courseCategoryId;
+    $selected_course = (int) $selected_course;
+    $session_id = (int) $session_id;
+    $exerciseLevel = (int) $exerciseLevel;
+    $answerType = (int) $answerType;
+    $questionId = (int) $questionId;
+    $description  = Database::escape_string($description);
+
+    $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
+    $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
+    $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
+    $TBL_COURSE_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
+
+    // if we have selected an exercise in the list-box 'Filter'
+    if ($exerciseId > 0) {
+        $where = '';
+        $from = '';
+        if (isset($courseCategoryId) && $courseCategoryId > 0) {
+            $from = ", $TBL_COURSE_REL_CATEGORY crc ";
+            $where .= " AND 
+                    crc.c_id = $selected_course AND 
+                    crc.question_id = qu.id AND 
+                    crc.category_id = $courseCategoryId";
+        }
+        if (isset($exerciseLevel) && $exerciseLevel != -1) {
+            $where .= ' AND level='.$exerciseLevel;
+        }
+        if (isset($answerType) && $answerType > 0) {
+            $where .= ' AND type='.$answerType;
+        }
 
-    // @todo fix this query with the new id field
-    $sql = " (
-                SELECT q.* FROM $TBL_QUESTIONS q
-                INNER JOIN $TBL_EXERCISE_QUESTION r
-                ON (q.c_id = r.c_id AND q.id = r.question_id)
-                INNER JOIN $TBL_EXERCISES ex
-                ON (ex.id = r.exercice_id AND ex.c_id = r.c_id )
-                $from
-                WHERE
-                    ex.c_id = '$selected_course' AND
-                    ex.active = '-1'
-                    $level_where 
-                    $answer_where
-             )
-             UNION
-             (
-                SELECT q.* FROM $TBL_QUESTIONS q
-                LEFT OUTER JOIN $TBL_EXERCISE_QUESTION r
-                ON (q.c_id = r.c_id AND q.id = r.question_id)
-                $from
-                WHERE
-                    q.c_id = '$selected_course' AND
-                    r.question_id is null
-                    $level_where 
-                    $answer_where
-             )
-             UNION
-             (
-                SELECT q.* FROM $TBL_QUESTIONS q
-                INNER JOIN $TBL_EXERCISE_QUESTION r
-                ON (q.c_id = r.c_id AND q.id = r.question_id)
-                $from
+        if (!empty($questionId)) {
+            $where .= ' AND qu.iid='.$questionId;
+        }
+
+        if (!empty($description)) {
+            $where .= " AND qu.description LIKE '%$description%'";
+        }
+
+        $select = 'DISTINCT
+                    id,
+                    question,
+                    type,
+                    level,  
+                    qt.exercice_id exerciseId';
+        if ($getCount) {
+            $select = 'count(qu.iid) as count';
+        }
+        $sql = "SELECT $select
+                FROM
+                    $TBL_EXERCISE_QUESTION qt,
+                    $TBL_QUESTIONS qu
+                    $from
                 WHERE
-                    r.c_id = '$selected_course' AND
-                    (r.exercice_id = '-1' OR r.exercice_id = '0')
-                    $level_where 
-                    $answer_where
-             )";
-    $result = Database::query($sql);
-    while ($row = Database::fetch_array($result, 'ASSOC')) {
-        $mainQuestionList[] = $row;
-    }
-} else {
-    // All tests for selected course
-    // If we have not selected any option in the list-box 'Filter'
-    $filter = '';
-    $from = '';
-
-    if (isset($courseCategoryId) && $courseCategoryId > 0) {
-        $from = ", $TBL_COURSE_REL_CATEGORY crc ";
-        $filter .= " AND
+                    qt.question_id = qu.id AND 
+                    qt.exercice_id = $exerciseId AND 
+                    qt.c_id = $selected_course  AND 
+                    qu.c_id = $selected_course
+                    $where
+                ORDER BY question_order";
+    } elseif ($exerciseId == -1) {
+        // If we have selected the option 'Orphan questions' in the list-box 'Filter'
+        $level_where = '';
+        $from = '';
+        if (isset($courseCategoryId) && $courseCategoryId > 0) {
+            $from = " INNER JOIN $TBL_COURSE_REL_CATEGORY crc 
+                      ON crc.question_id = q.id AND crc.c_id = q.c_id ";
+            $level_where .= " AND
                     crc.c_id = $selected_course AND
-                    crc.question_id = qu.id AND
                     crc.category_id = $courseCategoryId";
-    }
-    if (isset($exerciseLevel) && $exerciseLevel != -1) {
-        $filter .= ' AND level='.$exerciseLevel.' ';
-    }
-    if (isset($answerType) && $answerType > 0) {
-        $filter .= ' AND qu.type='.$answerType.' ';
-    }
+        }
+        if (isset($exerciseLevel) && $exerciseLevel != -1) {
+            $level_where = ' AND level='.$exerciseLevel;
+        }
+        $answer_where = '';
+        if (isset($answerType) && $answerType > 0 - 1) {
+            $answer_where = ' AND type='.$answerType;
+        }
 
-    if (!empty($session_id) && $session_id != '-1') {
-        $mainQuestionList = [];
-        if (!empty($course_list)) {
-            foreach ($course_list as $course_item) {
-                $courseItemId = $course_item['real_id'];
+        if (!empty($questionId)) {
+            $answer_where .= ' AND q.iid='.$questionId;
+        }
 
-                if (!empty($selected_course) && $selected_course != '-1') {
-                    if ($selected_course != $courseItemId) {
-                        continue;
-                    }
-                }
+        if (!empty($description)) {
+            $answer_where .= " AND q.description LIKE '%$description%'";
+        }
 
-                $exerciseList = ExerciseLib::get_all_exercises($course_item, $session_id);
-
-                if (!empty($exerciseList)) {
-                    foreach ($exerciseList as $exercise) {
-                        $my_exercise = new Exercise($courseItemId);
-                        $my_exercise->read($exercise['id']);
-                        if (!empty($my_exercise)) {
-                            if (!empty($my_exercise->questionList)) {
-                                foreach ($my_exercise->questionList as $question_id) {
-                                    $question_obj = Question::read(
-                                        $question_id,
-                                        $courseItemId
-                                    );
-
-                                    if ($exerciseLevel != '-1') {
-                                        if ($exerciseLevel != $question_obj->level) {
-                                            continue;
-                                        }
-                                    }
-
-                                    if ($answerType > 0) {
-                                        if ($answerType != $question_obj->type) {
-                                            continue;
-                                        }
-                                    }
-
-                                    $categoryIdFromQuestion = TestCategory::getCategoryForQuestion(
-                                        $question_obj->id,
-                                        $selected_course
-                                    );
-
-                                    if ($courseCategoryId > 0 &&
-                                        $categoryIdFromQuestion != $courseCategoryId
-                                    ) {
-                                        continue;
-                                    }
-
-                                    if (!empty($objExercise) &&
-                                        $objExercise->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT
-                                    ) {
-                                        if ($question_obj->type == HOT_SPOT_DELINEATION) {
-                                            continue;
-                                        }
-                                    }
-
-                                    $question_row = [
-                                        'id' => $question_obj->id,
-                                        'question' => $question_obj->question,
-                                        'type' => $question_obj->type,
-                                        'level' => $question_obj->level,
-                                        'exercise_id' => $exercise['id'],
-                                        'exercise_name' => $exercise['title'],
-                                        'course_id' => $courseItemId,
-                                    ];
-                                    $mainQuestionList[] = $question_row;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
+        $select = ' q.*, r.exercice_id exerciseId  ';
+        if ($getCount) {
+            $select = 'count(q.iid) as count';
+        }
+
+        // @todo fix this query with the new id field
+        $sql = " (
+                    SELECT $select
+                    FROM $TBL_QUESTIONS q
+                    INNER JOIN $TBL_EXERCISE_QUESTION r
+                    ON (q.c_id = r.c_id AND q.id = r.question_id)
+                    INNER JOIN $TBL_EXERCISES ex
+                    ON (ex.id = r.exercice_id AND ex.c_id = r.c_id)
+                    $from
+                    WHERE
+                        ex.c_id = '$selected_course' AND
+                        ex.active = '-1'
+                        $level_where 
+                        $answer_where
+                )                  
+                UNION                 
+                (
+                    SELECT $select
+                    FROM $TBL_QUESTIONS q
+                    LEFT OUTER JOIN $TBL_EXERCISE_QUESTION r
+                    ON (q.c_id = r.c_id AND q.id = r.question_id)
+                    $from
+                    WHERE
+                        q.c_id = '$selected_course' AND
+                        r.question_id is null
+                        $level_where 
+                        $answer_where
+                )                  
+                UNION                 
+                (
+                        SELECT $select
+                        FROM $TBL_QUESTIONS q
+                        INNER JOIN $TBL_EXERCISE_QUESTION r
+                        ON (q.c_id = r.c_id AND q.id = r.question_id)
+                        $from
+                        WHERE
+                            r.c_id = '$selected_course' AND
+                            (r.exercice_id = '-1' OR r.exercice_id = '0')
+                            $level_where 
+                            $answer_where
+                    ) 
+                 ";
+        if ($getCount) {
+            $sql = "SELECT SUM(count) count FROM ($sql) as total";
         }
     } else {
+        // All tests for selected course
+        // If we have not selected any option in the list-box 'Filter'
+        $filter = '';
+        $from = '';
+        if (isset($courseCategoryId) && $courseCategoryId > 0) {
+            $from = ", $TBL_COURSE_REL_CATEGORY crc ";
+            $filter .= " AND
+                        crc.c_id = $selected_course AND
+                        crc.question_id = qu.id AND
+                        crc.category_id = $courseCategoryId";
+        }
+        if (isset($exerciseLevel) && $exerciseLevel != -1) {
+            $filter .= ' AND level='.$exerciseLevel.' ';
+        }
+        if (isset($answerType) && $answerType > 0) {
+            $filter .= ' AND qu.type='.$answerType.' ';
+        }
+
+        if (!empty($questionId)) {
+            $filter .= ' AND qu.iid='.$questionId;
+        }
+
+        if (!empty($description)) {
+            $filter .= " AND qu.description LIKE '%$description%'";
+        }
+
         if ($session_id == -1 || empty($session_id)) {
             $session_id = 0;
         }
-
         $sessionCondition = api_get_session_condition($session_id, true, 'q.session_id');
 
+        $select = 'qu.id, question, qu.type, level, q.session_id, qt.exercice_id exerciseId  ';
+        if ($getCount) {
+            $select = 'count(qu.iid) as count';
+        }
+
         // All tests for the course selected, not in session
-        $sql = "SELECT DISTINCT qu.id, question, qu.type, level, q.session_id
+        $sql = "SELECT DISTINCT
+                    $select
                 FROM
                 $TBL_QUESTIONS as qu,
                 $TBL_EXERCISE_QUESTION as qt,
@@ -640,20 +647,90 @@ if ($exerciseId > 0) {
                     qu.c_id = $selected_course AND
                     qt.c_id = $selected_course AND
                     q.c_id = $selected_course AND
-                    qu.id = qt.question_id 
-                    $sessionCondition AND                    
-                    q.id = qt.exercice_id $filter
+                    qu.id = qt.question_id
+                    $sessionCondition AND
+                    q.id = qt.exercice_id
+                    $filter
                 ORDER BY session_id ASC";
+    }
+
+    if ($getCount) {
         $result = Database::query($sql);
-        while ($row = Database::fetch_array($result, 'ASSOC')) {
-            $mainQuestionList[] = $row;
-        }
+        $row = Database::fetch_array($result, 'ASSOC');
+
+        return (int) $row['count'];
     }
-    // forces the value to 0
-    $exerciseId = 0;
+
+    $sql .= " LIMIT $start, $length";
+
+    $result = Database::query($sql);
+
+    $mainQuestionList = [];
+    while ($row = Database::fetch_array($result, 'ASSOC')) {
+        $mainQuestionList[] = $row;
+    }
+
+    return $mainQuestionList;
 }
 
-$nbrQuestions = count($mainQuestionList);
+$nbrQuestions = getQuestions(
+    true,
+    null,
+    null,
+    $exerciseId,
+    $courseCategoryId,
+    $selected_course,
+    $session_id,
+    $exerciseLevel,
+    $answerType,
+    $questionId,
+    $description
+);
+
+$length = api_get_configuration_value('question_pagination_length');
+if (empty($length)) {
+    $length = 20;
+}
+
+$start = ($page - 1) * $length;
+
+$paginator = new Paginator();
+$pagination = $paginator->paginate([]);
+$pagination->setTotalItemCount($nbrQuestions);
+$pagination->setItemNumberPerPage($length);
+$pagination->setCurrentPageNumber($page);
+
+$pagination->renderer = function ($data) use ($url) {
+    $render = '';
+    if ($data['pageCount'] > 1) {
+        $render = '<ul class="pagination">';
+        for ($i = 1; $i <= $data['pageCount']; $i++) {
+            $page = (int) $i;
+            $pageContent = '<li><a href="'.$url.'&page='.$page.'">'.$page.'</a></li>';
+            if ($data['current'] == $page) {
+                $pageContent = '<li class="active"><a href="#" >'.$page.'</a></li>';
+            }
+            $render .= $pageContent;
+        }
+        $render .= '</ul>';
+    }
+
+    return $render;
+};
+
+$mainQuestionList = getQuestions(
+    false,
+    $start,
+    $length,
+    $exerciseId,
+    $courseCategoryId,
+    $selected_course,
+    $session_id,
+    $exerciseLevel,
+    $answerType,
+    $questionId,
+    $description
+);
 
 // build the line of the array to display questions
 // Actions are different if you launch the question_pool page
@@ -674,80 +751,41 @@ $nbrQuestions = count($mainQuestionList);
 */
 
 if ($fromExercise <= 0) {
-    // NOT IN A TEST - IN THE COURSE
+    // NOT IN A TEST - NOT IN THE COURSE
+    $actionLabel = get_lang('Reuse');
+    $actionIcon1 = get_lang('MustBeInATest');
+    $actionIcon2 = '';
+    // We are not in this course, to messy if we link to the question in another course
+    $questionTagA = 0;
     if ($selected_course == api_get_course_int_id()) {
+        // NOT IN A TEST - IN THE COURSE
         $actionLabel = get_lang('Modify');
         $actionIcon1 = 'edit';
         $actionIcon2 = 'delete';
         // We are in the course, question title can be a link to the question edit page
         $questionTagA = 1;
-    } else { // NOT IN A TEST - NOT IN THE COURSE
-        $actionLabel = get_lang('Reuse');
-        $actionIcon1 = get_lang('MustBeInATest');
-        $actionIcon2 = '';
-        // We are not in this course, to messy if we link to the question in another course
-        $questionTagA = 0;
     }
 } else {
-    // IN A TEST - IN THE COURSE
+    // IN A TEST - NOT IN THE COURSE
+    $actionLabel = get_lang('Reuse');
+    $actionIcon1 = 'clone';
+    $actionIcon2 = '';
+    $questionTagA = 0;
+
     if ($selected_course == api_get_course_int_id()) {
+        // IN A TEST - IN THE COURSE
         $actionLabel = get_lang('Reuse');
         $actionIcon1 = 'add';
         $actionIcon2 = '';
         $questionTagA = 1;
-    } else {
-        // IN A TEST - NOT IN THE COURSE
-        $actionLabel = get_lang('Reuse');
-        $actionIcon1 = 'clone';
-        $actionIcon2 = '';
-        $questionTagA = 0;
     }
 }
 
-// Display table
-$header = [
-    [
-        get_lang('QuestionUpperCaseFirstLetter'),
-        false,
-        ['style' => 'text-align:center'],
-        '',
-    ],
-    [
-        get_lang('Type'),
-        false,
-        ['style' => 'text-align:center'],
-        ['style' => 'text-align:center'],
-        '',
-    ],
-    [
-        get_lang('QuestionCategory'),
-        false,
-        ['style' => 'text-align:center'],
-        ['style' => 'text-align:center'],
-        '',
-    ],
-    [
-        get_lang('Difficulty'),
-        false,
-        ['style' => 'text-align:center'],
-        ['style' => 'text-align:center'],
-        '',
-    ],
-    [
-        $actionLabel,
-        false,
-        ['style' => 'text-align:center'],
-        ['style' => 'text-align:center'],
-        '',
-    ],
-];
-
 $data = [];
-
 if (is_array($mainQuestionList)) {
     foreach ($mainQuestionList as $question) {
         $row = [];
-
+        // This function checks if the question can be read
         $question_type = get_question_type_for_question($selected_course, $question['id']);
 
         if (empty($question_type)) {
@@ -756,16 +794,17 @@ if (is_array($mainQuestionList)) {
 
         $sessionId = isset($question['session_id']) ? $question['session_id'] : null;
         $exerciseName = isset($question['exercise_name']) ? '<br />('.$question['exercise_id'].') ' : null;
-        $row[] = get_a_tag_for_question(
+        $row[] = getLinkForQuestion(
             $questionTagA,
             $fromExercise,
             $question['id'],
             $question['type'],
             $question['question'],
-            $sessionId
+            $sessionId,
+            $question['exerciseId']
         ).$exerciseName;
         $row[] = $question_type;
-        $row[] = get_question_categorie_for_question($selected_course, $question['id']);
+        $row[] = TestCategory::getCategoryNameForQuestion($question['id'], $selected_course);
         $row[] = $question['level'];
         $row[] = get_action_icon_for_question(
             $actionIcon1,
@@ -778,7 +817,7 @@ if (is_array($mainQuestionList)) {
             $exerciseLevel,
             $answerType,
             $session_id,
-            $exerciseId
+            $question['exerciseId']
         ).'&nbsp;'.
         get_action_icon_for_question(
             $actionIcon2,
@@ -791,12 +830,52 @@ if (is_array($mainQuestionList)) {
             $exerciseLevel,
             $answerType,
             $session_id,
-            $exerciseId
+            $question['exerciseId']
         );
         $data[] = $row;
     }
 }
 
+// Display table
+$header = [
+    [
+        get_lang('QuestionUpperCaseFirstLetter'),
+        false,
+        ['style' => 'text-align:center'],
+        '',
+    ],
+    [
+        get_lang('Type'),
+        false,
+        ['style' => 'text-align:center'],
+        ['style' => 'text-align:center'],
+        '',
+    ],
+    [
+        get_lang('QuestionCategory'),
+        false,
+        ['style' => 'text-align:center'],
+        ['style' => 'text-align:center'],
+        '',
+    ],
+    [
+        get_lang('Difficulty'),
+        false,
+        ['style' => 'text-align:center'],
+        ['style' => 'text-align:center'],
+        '',
+    ],
+    [
+        $actionLabel,
+        false,
+        ['style' => 'text-align:center'],
+        ['style' => 'text-align:center'],
+        '',
+    ],
+];
+
+echo $pagination;
+
 Display::display_sortable_table(
     $header,
     $data,
@@ -804,10 +883,6 @@ Display::display_sortable_table(
     ['per_page_default' => 999, 'per_page' => 999, 'page_nr' => 1]
 );
 
-if (!$nbrQuestions) {
-    echo get_lang('NoQuestion');
-}
-
 Display::display_footer();
 
 /**
@@ -842,35 +917,44 @@ function reset_menu_exo_lvl_type()
  *
  * @param int    $in_addA
  * @param int    $in_fromex
- * @param int    $in_questionid
- * @param int    $in_questiontype
- * @param string $in_questionname
+ * @param int    $questionId
+ * @param int    $questiontype
+ * @param string $questionName
  * @param int    $sessionId
+ * @param int    $exerciseId
  *
  * @return string
  *
  * @author hubert.borderiou
  */
-function get_a_tag_for_question(
+function getLinkForQuestion(
     $in_addA,
-    $in_fromex,
-    $in_questionid,
-    $in_questiontype,
-    $in_questionname,
-    $sessionId
+    $fromExercise,
+    $questionId,
+    $questionType,
+    $questionName,
+    $sessionId,
+    $exerciseId
 ) {
-    $res = $in_questionname;
-    $sessionIcon = null;
+    $result = $questionName;
     if ($in_addA) {
+        $sessionIcon = '';
         if (!empty($sessionId) && $sessionId != -1) {
             $sessionIcon = ' '.Display::return_icon('star.png', get_lang('Session'));
         }
-        $res = "<a href='admin.php?".api_get_cidreq()."&editQuestion=$in_questionid&type=$in_questiontype&fromExercise=$in_fromex'>".
-            $res.$sessionIcon.
-            "</a>";
+        $exerciseId = (int) $exerciseId;
+        $questionId = (int) $questionId;
+        $questionType = (int) $questionType;
+        $fromExercise = (int) $fromExercise;
+
+        $result = Display::url(
+            $questionName.$sessionIcon,
+            "admin.php?".api_get_cidreq().
+            "&exerciseId=$exerciseId&editQuestion=$questionId&type=$questionType&fromExercise=$fromExercise"
+        );
     }
 
-    return $res;
+    return $result;
 }
 
 /**
@@ -900,21 +984,28 @@ function get_action_icon_for_question(
     $in_session_id,
     $in_exercise_id
 ) {
+    $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
     $getParams = "&selected_course=$in_selected_course&courseCategoryId=$in_courseCategoryId&exerciseId=$in_exercise_id&exerciseLevel=$in_exerciseLevel&answerType=$in_answerType&session_id=$in_session_id";
+    $res = '';
     switch ($in_action) {
         case 'delete':
-            $res = "<a href='".api_get_self()."?".api_get_cidreq().$getParams."&delete=$in_questionid' onclick='return confirm_your_choice()'>";
-            $res .= Display::return_icon("delete.png", get_lang('Delete'));
+            if ($limitTeacherAccess && !api_is_platform_admin()) {
+                break;
+            }
+            $res = "<a href='".api_get_self()."?".
+                api_get_cidreq().$getParams."&delete=$in_questionid' onclick='return confirm_your_choice()'>";
+            $res .= Display::return_icon('delete.png', get_lang('Delete'));
             $res .= "</a>";
             break;
         case 'edit':
-            $res = get_a_tag_for_question(
+            $res = getLinkForQuestion(
                 1,
                 $from_exercise,
                 $in_questionid,
                 $in_questiontype,
-                Display::return_icon("edit.png", get_lang('Modify')),
-                $in_session_id
+                Display::return_icon('edit.png', get_lang('Modify')),
+                $in_session_id,
+                $in_exercise_id
             );
             break;
         case 'add':
@@ -923,7 +1014,8 @@ function get_action_icon_for_question(
             $myObjEx->read($from_exercise);
             $res = "-";
             if (!$myObjEx->isInList($in_questionid)) {
-                $res = "<a href='".api_get_self()."?".api_get_cidreq().$getParams."&recup=$in_questionid&fromExercise=$from_exercise'>";
+                $res = "<a href='".api_get_self()."?".
+                    api_get_cidreq().$getParams."&recup=$in_questionid&fromExercise=$from_exercise'>";
                 $res .= Display::return_icon("view_more_stats.gif", get_lang('InsertALinkToThisQuestionInTheExercise'));
                 $res .= "</a>";
             }
@@ -957,20 +1049,7 @@ function get_question_type_for_question($in_selectedcourse, $in_questionid)
     if (!empty($myObjQuestion)) {
         list($typeImg, $typeExpl) = $myObjQuestion->get_type_icon_html();
         $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, [], 32), []);
-        unset($myObjQuestion);
     }
 
     return $questionType;
 }
-
-/**
- * Return the name of the category for the question in a course.
- *
- * @author hubert.borderiou 13-10-2011
- */
-function get_question_categorie_for_question($courseId, $questionId)
-{
-    $cat = TestCategory::getCategoryNameForQuestion($questionId, $courseId);
-
-    return $cat;
-}

+ 8 - 3
main/exercise/upload_exercise.php

@@ -129,13 +129,18 @@ function lp_upload_quiz_main()
  */
 function lp_upload_quiz_action_handling()
 {
-    $_course = api_get_course_info();
-    $courseId = $_course['real_id'];
-
     if (!isset($_POST['submit_upload_quiz'])) {
         return;
     }
 
+    $_course = api_get_course_info();
+
+    if (empty($_course)) {
+        return false;
+    }
+
+    $courseId = $_course['real_id'];
+
     // Get the extension of the document.
     $path_info = pathinfo($_FILES['user_upload_quiz']['name']);