Issues (2089)

public/main/exercise/admin.php (2 issues)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Enums\ActionIcon;
6
use ChamiloSession as Session;
7
8
/**
9
 * Exercise administration
10
 * This script allows to manage (create, modify) an exercise and its questions.
11
 *
12
 *  Following scripts are includes for a best code understanding :
13
 *
14
 * - exercise.class.php : for the creation of an Exercise object
15
 * - question.class.php : for the creation of a Question object
16
 * - answer.class.php : for the creation of an Answer object
17
 * - exercise.lib.php : functions used in the exercise tool
18
 * - exercise_admin.inc.php : management of the exercise
19
 * - question_admin.inc.php : management of a question (statement & answers)
20
 * - question_list_admin.inc.php : management of the question list
21
 *
22
 * Main variables used in this script :
23
 *
24
 * - $objAnswer : answer object
25
 * - $exerciseId : the exercise ID
26
 * - $picturePath : the path of question pictures
27
 * - $newQuestion : ask to create a new question
28
 * - $modifyQuestion : ID of the question to modify
29
 * - $editQuestion : ID of the question to edit
30
 * - $submitQuestion : ask to save question modifications
31
 * - $cancelQuestion : ask to cancel question modifications
32
 * - $deleteQuestion : ID of the question to delete
33
 * - $moveUp : ID of the question to move up
34
 * - $moveDown : ID of the question to move down
35
 * - $modifyExercise : ID of the exercise to modify
36
 * - $submitExercise : ask to save exercise modifications
37
 * - $cancelExercise : ask to cancel exercise modifications
38
 * - $modifyAnswers : ID of the question which we want to modify answers for
39
 * - $cancelAnswers : ask to cancel answer modifications
40
 * - $buttonBack : ask to go back to the previous page in answers of type "Fill in blanks"
41
 *
42
 * @author Olivier Brouckaert
43
 * Modified by Hubert Borderiou 21-10-2011 Question by category
44
 */
45
require_once __DIR__.'/../inc/global.inc.php';
46
$current_course_tool = TOOL_QUIZ;
47
$this_section = SECTION_COURSES;
48
49
if (isset($_GET['r']) && 1 == $_GET['r']) {
50
    Exercise::cleanSessionVariables();
51
}
52
// Access control
53
api_protect_course_script(true);
54
55
$is_allowedToEdit = api_is_allowed_to_edit(null, true, false, false);
56
$sessionId = api_get_session_id();
57
$studentViewActive = api_is_student_view_active();
58
$showPagination = 'true' === api_get_setting('exercise.show_question_pagination');
59
60
if (!$is_allowedToEdit) {
61
    api_not_allowed(true);
62
}
63
64
$exerciseId = isset($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : 0;
65
if (0 === $exerciseId && isset($_POST['exerciseId'])) {
66
    $exerciseId = (int) $_POST['exerciseId'];
67
}
68
$newQuestion = $_GET['newQuestion'] ?? 0;
69
$modifyAnswers = $_GET['modifyAnswers'] ?? 0;
70
$editQuestion = $_GET['editQuestion'] ?? 0;
71
$page = isset($_GET['page']) && !empty($_GET['page']) ? (int) $_GET['page'] : 1;
72
$modifyQuestion = $_GET['modifyQuestion'] ?? 0;
73
$deleteQuestion = $_GET['deleteQuestion'] ?? 0;
74
$cloneQuestion = $_REQUEST['clone_question'] ?? 0;
75
if (empty($questionId)) {
76
    $questionId = Session::read('questionId');
77
}
78
if (empty($modifyExercise)) {
79
    $modifyExercise = $_GET['modifyExercise'] ?? null;
80
}
81
82
$fromExercise = $fromExercise ?? null;
83
$cancelExercise = $cancelExercise ?? null;
84
$cancelAnswers = $cancelAnswers ?? null;
85
$modifyIn = $modifyIn ?? null;
86
$cancelQuestion = $cancelQuestion ?? null;
87
88
/* Cleaning all incomplete attempts of the admin/teacher to avoid weird problems
89
    when changing the exercise settings, number of questions, etc */
90
Event::delete_all_incomplete_attempts(
0 ignored issues
show
The method delete_all_incomplete_attempts() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

90
Event::/** @scrutinizer ignore-call */ 
91
       delete_all_incomplete_attempts(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
91
    api_get_user_id(),
92
    $exerciseId,
93
    api_get_course_int_id(),
94
    api_get_session_id()
95
);
96
97
// get from session
98
$objExercise = Session::read('objExercise');
99
$objQuestion = Session::read('objQuestion');
100
101
if (isset($_REQUEST['convertAnswer'])) {
102
    $objQuestion = $objQuestion->swapSimpleAnswerTypes();
103
    Session::write('objQuestion', $objQuestion);
104
}
105
$objAnswer = Session::read('objAnswer');
106
$_course = api_get_course_info();
107
108
// tables used in the exercise tool.
109
if (!empty($_GET['action']) && 'exportqti2' === $_GET['action'] && !empty($_GET['questionId'])) {
110
    require_once 'export/qti2/qti2_export.php';
111
    $export = export_question_qti($_GET['questionId'], true);
112
    $qid = (int) $_GET['questionId'];
113
    $name = 'qti2_export_'.$qid.'.zip';
114
    $zip = api_create_zip($name);
115
    $zip->addFile("qti2export_$qid.xml", $export);
116
    $zip->finish();
117
    exit;
118
}
119
120
// Exercise object creation.
121
if (!($objExercise instanceof Exercise)) {
122
    // creation of a new exercise if wrong or not specified exercise ID
123
    if ($exerciseId) {
124
        $objExercise = new Exercise();
125
        $parseQuestionList = $showPagination > 0 ? false : true;
126
        if ($editQuestion) {
127
            $parseQuestionList = false;
128
            $showPagination = true;
129
        }
130
        $objExercise->read($exerciseId, $parseQuestionList);
131
        Session::write('objExercise', $objExercise);
132
    }
133
}
134
// Exercise can be edited in their course.
135
if (empty($objExercise)) {
136
    Session::erase('objExercise');
137
    header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
138
    exit;
139
}
140
141
// Exercise can be edited in their course.
142
if ($objExercise->sessionId != $sessionId) {
143
    api_not_allowed(true);
144
}
145
146
// doesn't select the exercise ID if we come from the question pool
147
if (!$fromExercise) {
148
    // gets the right exercise ID, and if 0 creates a new exercise
149
    if (!$exerciseId = $objExercise->getId()) {
150
        $modifyExercise = 'yes';
151
    }
152
}
153
154
$nbrQuestions = $objExercise->getQuestionCount();
155
156
// Question object creation.
157
if ($editQuestion || $newQuestion || $modifyQuestion || $modifyAnswers) {
158
    if ($editQuestion || $newQuestion) {
159
        // reads question data
160
        if ($editQuestion) {
161
            // question not found
162
            if (!$objQuestion = Question::read($editQuestion)) {
163
                api_not_allowed(true);
164
            }
165
            // saves the object into the session
166
            Session::write('objQuestion', $objQuestion);
167
        }
168
    }
169
170
    // checks if the object exists
171
    if (is_object($objQuestion)) {
172
        // gets the question ID
173
        $questionId = $objQuestion->getId();
174
    }
175
}
176
177
// if cancelling an exercise
178
if ($cancelExercise) {
179
    // existing exercise
180
    if ($exerciseId) {
181
        unset($modifyExercise);
182
    } else {
183
        // new exercise
184
        // goes back to the exercise list
185
        header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
186
        exit();
187
    }
188
}
189
190
// if cancelling question creation/modification
191
if ($cancelQuestion) {
192
    // if we are creating a new question from the question pool
193
    if (!$exerciseId && !$questionId) {
194
        // goes back to the question pool
195
        header('Location: question_pool.php?'.api_get_cidreq());
196
        exit();
197
    } else {
198
        // goes back to the question viewing
199
        $editQuestion = $modifyQuestion;
200
        unset($newQuestion, $modifyQuestion);
201
    }
202
}
203
204
if (!empty($cloneQuestion) && !empty($objExercise->getId())) {
205
    $oldQuestionObj = Question::read($cloneQuestion);
206
    $oldQuestionObj->question = $oldQuestionObj->question.' - '.get_lang('Copy');
207
208
    $newId = $oldQuestionObj->duplicate(api_get_course_info());
209
    $newQuestionObj = Question::read($newId);
210
    $newQuestionObj->addToList($exerciseId);
211
212
    // Save category to the destination course
213
    if (!empty($oldQuestionObj->category)) {
214
        $newQuestionObj->saveCategory($oldQuestionObj->category);
215
    }
216
217
    // This should be moved to the duplicate function
218
    $newAnswerObj = new Answer($cloneQuestion);
219
    $newAnswerObj->read();
220
    $newAnswerObj->duplicate($newQuestionObj);
221
222
    // Reloading tne $objExercise obj
223
    $objExercise->read($objExercise->getId(), false);
224
225
    Display::addFlash(Display::return_message(get_lang('Item copied')));
226
227
    header('Location: admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->getId().'&page='.$page);
228
    exit;
229
}
230
231
// if cancelling answer creation/modification
232
if ($cancelAnswers) {
233
    // goes back to the question viewing
234
    $editQuestion = $modifyAnswers;
235
    unset($modifyAnswers);
236
}
237
238
$nameTools = '';
239
// modifies the query string that is used in the link of tool name
240
if ($editQuestion || $modifyQuestion || $newQuestion || $modifyAnswers) {
241
    $nameTools = get_lang('Question / Answer management');
242
}
243
244
if (api_is_in_gradebook()) {
245
    $interbreadcrumb[] = [
246
        'url' => Category::getUrl(),
247
        'name' => get_lang('Assessments'),
248
    ];
249
}
250
251
$interbreadcrumb[] = [
252
    'url' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq(),
253
    'name' => get_lang('Tests'),
254
];
255
if (isset($_GET['newQuestion']) || isset($_GET['editQuestion'])) {
256
    $interbreadcrumb[] = [
257
        'url' => api_get_path(WEB_CODE_PATH).'exercise/admin.php?exerciseId='.$objExercise->getId().'&'.api_get_cidreq(),
258
        'name' => $objExercise->selectTitle(true),
259
    ];
260
} else {
261
    $interbreadcrumb[] = [
262
        'url' => '#',
263
        'name' => $objExercise->selectTitle(true),
264
    ];
265
}
266
267
// shows a link to go back to the question pool
268
if (!$exerciseId && $nameTools != get_lang('Tests management')) {
269
    $interbreadcrumb[] = [
270
        'url' => api_get_path(WEB_CODE_PATH)."exercise/question_pool.php?fromExercise=$fromExercise&".api_get_cidreq(),
271
        'name' => get_lang('Recycle existing questions'),
272
    ];
273
}
274
275
// if the question is duplicated, disable the link of tool name
276
if ('thisExercise' === $modifyIn) {
277
    if (!empty($buttonBack)) {
278
        $modifyIn = 'allExercises';
279
    }
280
}
281
282
$htmlHeadXtra[] = api_get_build_js('legacy_exercise.js');
283
284
$template = new Template();
285
$templateName = $template->get_template('exercise/submit.js.tpl');
286
$htmlHeadXtra[] = $template->fetch($templateName);
287
$htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js');
288
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
289
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
290
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_PATH).'build/libs/select2/css/select2.min.css">';
291
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_PATH).'build/libs/select2/js/select2.min.js"></script>';
292
$htmlHeadXtra[] = '<script>$(function(){ if ($.fn.select2){ $(".ch-select2").select2({width:"100%"}); } });</script>';
293
294
if (isset($_GET['message'])) {
295
    if (in_array($_GET['message'], ['ExerciseStored', 'ItemUpdated', 'ItemAdded'])) {
296
        Display::addFlash(Display::return_message(get_lang($_GET['message']), 'confirmation'));
297
    }
298
}
299
300
Display::display_header($nameTools, 'Exercise');
301
302
// If we are in a test
303
$inATest = isset($exerciseId) && $exerciseId > 0;
304
305
if ($inATest) {
306
    $actions = '';
307
    if (isset($_GET['hotspotadmin']) || isset($_GET['newQuestion'])) {
308
        $actions .= '<a
309
        href="'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
310
            Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Go back to the questions list')).'</a>';
311
    }
312
313
    if (!isset($_GET['hotspotadmin']) && !isset($_GET['newQuestion']) && !isset($_GET['editQuestion'])) {
314
        $actions .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'">'.
315
            Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, sprintf(get_lang('Back to %s'), get_lang('Test list'))).'</a>';
316
    }
317
    $actions .= '<a
318
        href="'.api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->getId().'&preview=1">'.
319
        Display::getMdiIcon(ActionIcon::PREVIEW_CONTENT, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Preview')).'</a>';
320
321
    $actions .= Display::url(
322
        Display::getMdiIcon('chart-box', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Results and feedback')),
323
        api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->getId()
324
    );
325
326
    $actions .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->getId().'">'.
327
        Display::getMdiIcon('cog', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Edit test name and settings')).'</a>';
328
329
    $maxScoreAllQuestions = 0;
330
    if (false === $showPagination) {
331
        $questionList = $objExercise->selectQuestionList(true, $objExercise->random > 0 ? false : true);
332
        if (!empty($questionList)) {
333
            foreach ($questionList as $questionItemId) {
334
                $question = Question::read($questionItemId);
335
                if ($question) {
336
                    $maxScoreAllQuestions += $question->selectWeighting();
337
                }
338
            }
339
        }
340
    }
341
342
    echo Display::toolbarAction('toolbar', [$actions]);
343
344
    if ($objExercise->added_in_lp()) {
345
        echo Display::return_message(
346
            get_lang(
347
                'This exercise has been included in a learning path, so it cannot be accessed by students directly from here. If you want to put the same exercise available through the exercises tool, please make a copy of the current exercise using the copy icon.'
348
            ),
349
            'warning'
350
        );
351
    }
352
    if ($editQuestion && $objQuestion->existsInAnotherExercise()) {
353
        echo Display::return_message(
354
            Display::getMdiIcon('alert', 'ch-tool-icon', null, ICON_SIZE_SMALL)
355
            .get_lang('This question is used in another exercises. If you continue its edition, the changes will affect all exercises that contain this question.'),
356
            'warning',
357
            false
358
        );
359
    }
360
361
    $isHotspotEdit = is_object($objQuestion) && in_array((int)$objQuestion->selectType(), [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_DELINEATION], true);
362
    $alert = '';
363
    if (false === $showPagination && !$isHotspotEdit) {
364
        $originalSelectionType = $objExercise->questionSelectionType;
365
        $objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED;
366
367
        // Get the full list of question IDs (as configured in this exercise).
368
        /** @var int[] $allIds */
369
        $allIds = (array) $objExercise->selectQuestionList(true, true);
370
371
        // Load questions and build a children map to detect "media" containers reliably.
372
        $questionsById = [];
373
        $childrenByParent = []; // parentId => [childId, ...]
374
        foreach ($allIds as $qid) {
375
            $q = Question::read($qid);
376
            if (!$q) {
377
                continue;
378
            }
379
            $questionsById[$qid] = $q;
380
381
            // some DBs might store parent_id as string/null
382
            $pid = (int) ($q->parent_id ?? 0);
383
            if ($pid > 0) {
384
                if (!isset($childrenByParent[$pid])) {
385
                    $childrenByParent[$pid] = [];
386
                }
387
                $childrenByParent[$pid][] = $qid;
388
            }
389
        }
390
391
        // is this question a media/container?
392
        $isMediaContainer = static function ($q, $qid, $childrenByParent) {
393
            // Case 1: explicit MEDIA_QUESTION type (when constant exists)
394
            $isMediaType = (defined('MEDIA_QUESTION') && (int) $q->selectType() === MEDIA_QUESTION);
395
396
            // Case 2: it is a parent of other questions within this exercise
397
            $isParent = isset($childrenByParent[$qid]) && !empty($childrenByParent[$qid]);
398
399
            // Case 3: some forks expose a method isMedia()
400
            $hasMethod = method_exists($q, 'isMedia') && $q->isMedia();
401
402
            return $isMediaType || $isParent || $hasMethod;
403
        };
404
405
        // Build the effective set of answerable questions (exclude media containers).
406
        $effectiveQuestions = []; // id => Question
407
        foreach ($questionsById as $qid => $q) {
408
            if ($isMediaContainer($q, $qid, $childrenByParent)) {
409
                continue; // skip media/parent containers
410
            }
411
            $effectiveQuestions[$qid] = $q;
412
        }
413
414
        // Compute counts and totals using only effective questions.
415
        $effectiveNbrQuestions = count($effectiveQuestions);
416
        $effectiveTotalScore = 0.0;
417
        foreach ($effectiveQuestions as $q) {
418
            $effectiveTotalScore += (float) $q->selectWeighting();
419
        }
420
421
        // Restore original selection type.
422
        $objExercise->questionSelectionType = $originalSelectionType;
423
424
        // First line: "X questions, total score Y." (media excluded)
425
        $alert .= sprintf(
426
            get_lang('%d questions, for a total score (all questions) of %s.'),
427
            $effectiveNbrQuestions,
428
            $effectiveTotalScore
429
        );
430
431
        // If random selection is enabled, display the limit and an informative max total
432
        if ($objExercise->random > 0) {
433
            $limit = min((int) $objExercise->random, $effectiveNbrQuestions);
434
435
            // Gather weights and take top-N.
436
            $weights = [];
437
            foreach ($effectiveQuestions as $id => $q) {
438
                $weights[$id] = (float) $q->selectWeighting();
439
            }
440
            arsort($weights, SORT_NUMERIC); // highest first
441
442
            $maxScoreSelected = 0.0;
443
            $i = 0;
444
            foreach ($weights as $w) {
445
                $maxScoreSelected += $w;
446
                if (++$i >= $limit) { break; }
447
            }
448
449
            $alert .= '<br />'.sprintf(
450
                    get_lang('Only %s questions will be picked randomly following the quiz configuration.'),
451
                    $limit
452
                );
453
            $alert .= sprintf(
454
                '<br>'.get_lang('Only %d questions will be selected based on the test configuration, for a total score of %s.'),
455
                $limit,
456
                $maxScoreSelected
457
            );
458
        }
459
460
        // Category-based ordered selection: use effective counts/totals as well.
461
        if ($objExercise->questionSelectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED) {
462
            $alert .= sprintf(
463
                '<br>'.get_lang(
464
                    'Only %d questions will be selected based on the test configuration, for a total score of %s.'
465
                ),
466
                $effectiveNbrQuestions,
467
                $effectiveTotalScore
468
            );
469
        }
470
    } else {
471
        // Pagination enabled or hotspot edit: keep a minimal, safe notice for random selection.
472
        if ($objExercise->random > 0) {
473
            $limit = min((int) $objExercise->random, (int) $nbrQuestions);
474
            $alert .= '<br />'.sprintf(
475
                    get_lang('Only %s questions will be picked randomly following the quiz configuration.'),
476
                    $limit
477
                );
478
        }
479
    }
480
    if (!empty($alert)) {
481
        echo Display::return_message($alert, 'normal', false);
482
    }
483
} elseif (isset($_GET['newQuestion'])) {
484
    // we are in create a new question from question pool not in a test
485
    $actions = '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'">'.
486
        Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Go back to the questions list')).'</a>';
487
    echo Display::toolbarAction('toolbar', [$actions]);
488
} else {
489
    // If we are in question_pool but not in a test, go back to the questions created in pool
490
    $actions = '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/question_pool.php?'.api_get_cidreq().'">'.
491
        Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Go back to the questions list')).
492
        '</a>';
493
    echo Display::toolbarAction('toolbar', [$actions]);
494
}
495
496
if ($newQuestion || $editQuestion) {
497
    // Question management
498
    $type = isset($_REQUEST['answerType']) ? Security::remove_XSS($_REQUEST['answerType']) : null;
499
    echo '<input type="hidden" name="Type" value="'.$type.'" />';
500
501
    if ('yes' === $newQuestion) {
502
        $objExercise->edit_exercise_in_lp = true;
503
        require 'question_admin.inc.php';
504
    }
505
    if ($editQuestion) {
506
        // Question preview if teacher clicked the "switch to student"
507
        if ($studentViewActive && $is_allowedToEdit) {
508
            echo '<div class="main-question">';
509
            echo Display::div($objQuestion->selectTitle(), ['class' => 'question_title']);
510
            ExerciseLib::showQuestion(
511
                $objExercise,
512
                $editQuestion,
513
                false,
514
                null,
515
                null,
516
                false,
517
                true,
518
                false,
519
                true,
520
                true
521
            );
522
            echo '</div>';
523
        } else {
524
            require 'question_admin.inc.php';
525
            ExerciseLib::showTestsWhereQuestionIsUsed($objQuestion->iid, $objExercise->getId());
526
        }
527
    }
528
}
529
530
if (isset($_GET['hotspotadmin'])) {
531
    if (!is_object($objQuestion)) {
532
        $objQuestion = Question::read($_GET['hotspotadmin']);
533
    }
534
    if (!$objQuestion) {
535
        api_not_allowed();
536
    }
537
    require 'hotspot_admin.inc.php';
538
}
539
540
if (isset($_GET['mad_admin'])) {
541
    $qid = (int) $_GET['mad_admin'];
542
    $objQuestion = Question::read($qid);
543
    if (!$objQuestion) {
0 ignored issues
show
$objQuestion is of type Question, thus it always evaluated to true.
Loading history...
544
        api_not_allowed();
545
    }
546
547
    require 'multiple_answer_dropdown_admin.php';
548
    exit;
549
}
550
551
if (
552
    !$newQuestion
553
    && !$modifyQuestion
554
    && !$editQuestion
555
    && !isset($_GET['hotspotadmin'])
556
    && !isset($_GET['mad_admin'])
557
) {
558
    require 'question_list_admin.inc.php';
559
}
560
561
// if we are in question authoring, display warning to user is feedback not shown at the end of the test -ref #6619
562
// this test to display only message in the question authoring page and not in the question list page too
563
if (EXERCISE_FEEDBACK_TYPE_EXAM == $objExercise->getFeedbackType()) {
564
    echo Display::return_message(
565
        get_lang(
566
            'This test is configured not to display feedback to learners. Comments will not be seen at the end of the test, but may be useful for you, as teacher, when reviewing the question details.'
567
        ),
568
        'normal'
569
    );
570
}
571
572
Session::write('objQuestion', $objQuestion);
573
Session::write('objAnswer', $objAnswer);
574
Display::display_footer();
575