Passed
Pull Request — 1.11.x (#6306)
by Yannick
18:44
created

render_question_list()   C

Complexity

Conditions 16
Paths 110

Size

Total Lines 117
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 63
c 0
b 0
f 0
dl 0
loc 117
rs 5.4833
cc 16
nc 110
nop 13

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use ChamiloSession as Session;
6
7
/**
8
 * Exercise submission
9
 * This script allows to run an exercise. According to the exercise type, questions
10
 * can be on an unique page, or one per page with a Next button.
11
 *
12
 * One exercise may contain different types of answers (unique or multiple selection,
13
 * matching, fill in blanks, free answer, hot-spot).
14
 *
15
 * Questions are selected randomly or not.
16
 *
17
 * When the user has answered all questions and clicks on the button "Ok",
18
 * it goes to exercise_result.php
19
 *
20
 * Notice : This script is also used to show a question before modifying it by
21
 * the administrator
22
 *
23
 * @author Olivier Brouckaert
24
 * @author Julio Montoya <[email protected]>
25
 *            Fill in blank option added (2008)
26
 *            Cleaning exercises (2010),
27
 *            Adding hotspot delineation support (2011)
28
 *            Adding reminder + ajax support (2011)
29
 * Modified by hubert.borderiou (2011-10-21 question category)
30
 */
31
require_once __DIR__.'/../inc/global.inc.php';
32
$current_course_tool = TOOL_QUIZ;
33
$this_section = SECTION_COURSES;
34
$debug = false;
35
36
// Notice for unauthorized people.
37
api_protect_course_script(true);
38
39
$origin = api_get_origin();
40
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
41
$courseId = api_get_course_int_id();
42
$sessionId = api_get_session_id();
43
$glossaryExtraTools = api_get_setting('show_glossary_in_extra_tools');
44
$allowTimePerQuestion = api_get_configuration_value('allow_time_per_question');
45
if ($allowTimePerQuestion) {
46
    $htmlHeadXtra[] = api_get_asset('easytimer/easytimer.min.js');
47
}
48
49
$showPreviousButton = true;
50
$showGlossary = in_array($glossaryExtraTools, ['true', 'exercise', 'exercise_and_lp']);
51
if ('learnpath' === $origin) {
52
    $showGlossary = in_array($glossaryExtraTools, ['true', 'lp', 'exercise_and_lp']);
53
}
54
if ($showGlossary) {
55
    $htmlHeadXtra[] = '<script
56
        type="text/javascript"
57
        src="'.api_get_path(WEB_CODE_PATH).'glossary/glossary.js.php?add_ready=1&'.api_get_cidreq().'"></script>';
58
    $htmlHeadXtra[] = api_get_js('jquery.highlight.js');
59
}
60
61
$js = '<script>'.api_get_language_translate_html().'</script>';
62
$htmlHeadXtra[] = $js;
63
$htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js');
64
$htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js');
65
$htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js');
66
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
67
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
68
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
69
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
70
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
71
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
72
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
73
$htmlHeadXtra[] = api_get_jquery_libraries_js(['jquery-ui', 'jquery-upload']);
74
if (api_get_configuration_value('quiz_prevent_copy_paste')) {
75
    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.nocopypaste.js"></script>';
76
}
77
78
if ('true' === api_get_setting('enable_record_audio')) {
79
    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'rtc/RecordRTC.js"></script>';
80
    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'wami-recorder/recorder.js"></script>';
81
    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'wami-recorder/gui.js"></script>';
82
    $htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'swfobject/swfobject.js"></script>';
83
    $htmlHeadXtra[] = api_get_js('record_audio/record_audio.js');
84
}
85
86
$zoomOptions = api_get_configuration_value('quiz_image_zoom');
87
if (isset($zoomOptions['options']) && !in_array($origin, ['embeddable', 'iframe', 'mobileapp'])) {
88
    $options = $zoomOptions['options'];
89
    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.elevatezoom.js"></script>';
90
    $htmlHeadXtra[] = '<script>
91
        $(function() {
92
            $("img").each(function() {
93
                var attr = $(this).attr("data-zoom-image");
94
                // For some browsers, `attr` is undefined; for others,
95
                // `attr` is false.  Check for both.
96
                if (typeof attr !== typeof undefined && attr !== false) {
97
                    $(this).elevateZoom({
98
                        scrollZoom : true,
99
                        cursor: "crosshair",
100
                        tint:true,
101
                        tintColour:\'#CCC\',
102
                        tintOpacity:0.5,
103
                        zoomWindowWidth:'.$options['zoomWindowWidth'].',
104
                        zoomWindowHeight:'.$options['zoomWindowHeight'].',
105
                        zoomWindowPosition: 7
106
                    });
107
                }
108
            });
109
110
            $(document).contextmenu(function() {
111
                return false;
112
            });
113
        });
114
    </script>';
115
}
116
117
$template = new Template();
118
119
// General parameters passed via POST/GET
120
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
121
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
122
$learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
123
$reminder = isset($_REQUEST['reminder']) ? (int) $_REQUEST['reminder'] : 0;
124
$remind_question_id = isset($_REQUEST['remind_question_id']) ? (int) $_REQUEST['remind_question_id'] : 0;
125
$exerciseId = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : 0;
126
$formSent = isset($_REQUEST['formSent']) ? $_REQUEST['formSent'] : null;
127
$exerciseResult = isset($_REQUEST['exerciseResult']) ? $_REQUEST['exerciseResult'] : null;
128
$exerciseResultCoordinates = isset($_REQUEST['exerciseResultCoordinates']) ? $_REQUEST['exerciseResultCoordinates'] : null;
129
$choice = isset($_REQUEST['choice']) ? $_REQUEST['choice'] : null;
130
$choice = empty($choice) ? isset($_REQUEST['choice2']) ? $_REQUEST['choice2'] : null : null;
131
$current_question = $currentQuestionFromUrl = isset($_REQUEST['num']) ? (int) $_REQUEST['num'] : null;
132
$currentAnswer = isset($_REQUEST['num_answer']) ? (int) $_REQUEST['num_answer'] : null;
133
$logInfo = [
134
    'tool' => TOOL_QUIZ,
135
    'tool_id' => $exerciseId,
136
    'action' => $learnpath_id,
137
    'action_details' => $learnpath_id,
138
];
139
Event::registerLog($logInfo);
140
141
$error = '';
142
$exercise_attempt_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
143
144
/*  Teacher takes an exam and want to see a preview,
145
    we delete the objExercise from the session in order to get the latest
146
    changes in the exercise */
147
if (api_is_allowed_to_edit(null, true) &&
148
    isset($_GET['preview']) && $_GET['preview'] == 1
149
) {
150
    Session::erase('objExercise');
151
}
152
153
// 1. Loading the $objExercise variable
154
/** @var \Exercise $exerciseInSession */
155
$exerciseInSession = Session::read('objExercise');
156
if (empty($exerciseInSession) || (!empty($exerciseInSession) && $exerciseInSession->iid != $_GET['exerciseId'])) {
157
    // Construction of Exercise
158
    /** @var |Exercise $objExercise */
159
    $objExercise = new Exercise($courseId);
160
    Session::write('firstTime', true);
161
    if ($debug) {
162
        error_log('1. Setting the $objExercise variable');
163
    }
164
    Session::erase('questionList');
165
166
    // if the specified exercise doesn't exist or is disabled
167
    if (!$objExercise->read($exerciseId) ||
168
        (!$objExercise->selectStatus() && !$is_allowedToEdit && !in_array($origin, ['learnpath', 'embeddable', 'iframe']))
169
    ) {
170
        unset($objExercise);
171
        $error = get_lang('ExerciseNotFound');
172
    } else {
173
        // Saves the object into the session
174
        Session::write('objExercise', $objExercise);
175
    }
176
} else {
177
    Session::write('firstTime', false);
178
}
179
// 2. Checking if $objExercise is set.
180
/** @var |Exercise $objExercise */
181
if (!isset($objExercise) && isset($exerciseInSession)) {
182
    if ($debug) {
183
        error_log('2. Loading $objExercise from session');
184
    }
185
    $objExercise = $exerciseInSession;
186
}
187
188
$exerciseInSession = Session::read('objExercise');
189
190
// 3. $objExercise is not set, then return to the exercise list.
191
if (!is_object($objExercise)) {
192
    header('Location: exercise.php?'.api_get_cidreq());
193
    exit;
194
}
195
196
if ('true' === api_get_plugin_setting('positioning', 'tool_enable')) {
197
    $plugin = Positioning::create();
198
    if ($plugin->blockFinalExercise(api_get_user_id(), $objExercise->iid, api_get_course_int_id(), $sessionId)) {
199
        api_not_allowed(true);
200
    }
201
}
202
203
// if the user has submitted the form.
204
$exercise_title = Security::remove_XSS($objExercise->selectTitle());
205
$exercise_sound = $objExercise->selectSound();
206
207
// If reminder ends we jump to the exercise_reminder
208
if ($objExercise->review_answers) {
209
    if (-1 == $remind_question_id) {
210
        $extraParams = "&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id";
211
        $url = api_get_path(WEB_CODE_PATH).
212
            'exercise/exercise_reminder.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().$extraParams;
213
        api_location($url);
214
    }
215
}
216
217
$template->assign('shuffle_answers', $objExercise->random_answers);
218
$templateName = $template->get_template('exercise/submit.js.tpl');
219
$htmlHeadXtra[] = $template->fetch($templateName);
220
221
$current_timestamp = time();
222
$myRemindList = [];
223
$time_control = false;
224
if (0 != $objExercise->expired_time) {
225
    $time_control = true;
226
}
227
228
// Generating the time control key for the user
229
$current_expired_time_key = ExerciseLib::get_time_control_key(
230
    $objExercise->iid,
231
    $learnpath_id,
232
    $learnpath_item_id
233
);
234
235
Session::write('duration_time_previous', [$current_expired_time_key => $current_timestamp]);
236
$durationTime = Session::read('duration_time');
237
if (!empty($durationTime) && isset($durationTime[$current_expired_time_key])) {
238
    Session::write(
239
        'duration_time_previous',
240
        [$current_expired_time_key => $durationTime[$current_expired_time_key]]
241
    );
242
}
243
Session::write('duration_time', [$current_expired_time_key => $current_timestamp]);
244
245
if ($time_control) {
246
    // Get the expired time of the current exercise in track_e_exercises
247
    $total_seconds = $objExercise->expired_time * 60;
248
}
249
250
$show_clock = true;
251
$user_id = api_get_user_id();
252
if ($objExercise->selectAttempts() > 0) {
253
    $messageReachedMax = Display::return_message(
254
        sprintf(get_lang('ReachedMaxAttempts'), $exercise_title, $objExercise->selectAttempts()),
255
        'warning',
256
        false
257
    );
258
259
    $attempt_html = '';
260
    $attempt_count = Event::get_attempt_count(
261
        $user_id,
262
        $exerciseId,
263
        $learnpath_id,
264
        $learnpath_item_id,
265
        $learnpath_item_view_id
266
    );
267
268
    if ($attempt_count >= $objExercise->selectAttempts()) {
269
        $show_clock = false;
270
        if (!api_is_allowed_to_edit(null, true)) {
271
            if ($objExercise->results_disabled == 0 && !in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
272
                // Showing latest attempt according with task BT#1628
273
                $exercise_stat_info = Event::getExerciseResultsByUser(
274
                    $user_id,
275
                    $exerciseId,
276
                    $courseId,
277
                    $sessionId
278
                );
279
280
                if (!empty($exercise_stat_info)) {
281
                    $isQuestionsLimitReached = ExerciseLib::isQuestionsLimitPerDayReached(
282
                        $user_id,
283
                        count($objExercise->get_validated_question_list()),
284
                        $courseId,
285
                        $sessionId
286
                    );
287
288
                    if ($isQuestionsLimitReached) {
289
                        $maxQuestionsAnswered = (int) api_get_course_setting('quiz_question_limit_per_day');
290
                        Display::addFlash(
291
                            Display::return_message(
292
                                sprintf(get_lang('QuizQuestionsLimitPerDayXReached'), $maxQuestionsAnswered),
293
                                'warning',
294
                                false
295
                            )
296
                        );
297
298
                        if (in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
299
                            Display::display_reduced_header();
300
                            Display::display_reduced_footer();
301
                        } else {
302
                            Display::display_header(get_lang('Exercises'));
303
                            Display::display_footer();
304
                        }
305
                        exit;
306
                    }
307
308
                    $max_exe_id = max(array_keys($exercise_stat_info));
309
                    $last_attempt_info = $exercise_stat_info[$max_exe_id];
310
                    $attempt_html .= Display::div(
311
                        get_lang('Date').': '.api_get_local_time($last_attempt_info['exe_date']),
312
                        ['id' => '']
313
                    );
314
315
                    $attempt_html .= $messageReachedMax;
316
                    if (!empty($last_attempt_info['question_list'])) {
317
                        foreach ($last_attempt_info['question_list'] as $questions) {
318
                            foreach ($questions as $question_data) {
319
                                $question_id = $question_data['question_id'];
320
                                $marks = $question_data['marks'];
321
                                $question_info = Question::read($question_id);
322
                                $attempt_html .= Display::div(
323
                                    $question_info->question,
324
                                    ['class' => 'question_title']
325
                                );
326
                                $attempt_html .= Display::div(
327
                                    get_lang('Score').' '.$marks,
328
                                    ['id' => 'question_question_titlescore']
329
                                );
330
                            }
331
                        }
332
                    }
333
                    $score = ExerciseLib::show_score(
334
                        $last_attempt_info['exe_result'],
335
                        $last_attempt_info['exe_weighting']
336
                    );
337
                    $attempt_html .= Display::div(
338
                        get_lang('YourTotalScore').' '.$score,
339
                        ['id' => 'question_score']
340
                    );
341
                } else {
342
                    $attempt_html .= $messageReachedMax;
343
                }
344
            } else {
345
                $attempt_html .= $messageReachedMax;
346
            }
347
        } else {
348
            $attempt_html .= $messageReachedMax;
349
        }
350
351
        if (in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
352
            Display::display_reduced_header();
353
        } else {
354
            Display::display_header(get_lang('Exercises'));
355
        }
356
357
        echo $attempt_html;
358
        if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
359
            Display::display_footer();
360
        }
361
        exit;
362
    }
363
}
364
365
/* 5. Getting user exercise info (if the user took the exam before) generating exe_id */
366
$exercise_stat_info = $objExercise->get_stat_track_exercise_info(
367
    $learnpath_id,
368
    $learnpath_item_id,
369
    $learnpath_item_view_id
370
);
371
372
// Fix in order to get the correct question list.
373
$questionListUncompressed = $objExercise->getQuestionListWithMediasUncompressed();
374
Session::write('question_list_uncompressed', $questionListUncompressed);
375
376
$clock_expired_time = null;
377
if (empty($exercise_stat_info)) {
378
    $disable = api_get_configuration_value('exercises_disable_new_attempts');
379
    if ($disable) {
380
        api_not_allowed(true);
381
    }
382
    $total_weight = 0;
383
    $questionList = $objExercise->get_question_list(true);
384
    foreach ($questionListUncompressed as $question_id) {
385
        $objQuestionTmp = Question::read($question_id);
386
        $total_weight += (float) $objQuestionTmp->weighting;
387
    }
388
389
    if ($time_control) {
390
        $expected_time = $current_timestamp + $total_seconds;
391
        if ($debug) {
392
            error_log('5.1. $current_timestamp '.$current_timestamp);
393
            error_log('5.2. $expected_time '.$expected_time);
394
        }
395
396
        $clock_expired_time = api_get_utc_datetime($expected_time);
397
        if ($debug) {
398
            error_log('5.3. $expected_time '.$clock_expired_time);
399
        }
400
401
        //Sessions  that contain the expired time
402
        $_SESSION['expired_time'][$current_expired_time_key] = $clock_expired_time;
403
        if ($debug) {
404
            error_log(
405
                '5.4. Setting the $_SESSION[expired_time]: '.$_SESSION['expired_time'][$current_expired_time_key]
406
            );
407
        }
408
    }
409
410
    $exe_id = $objExercise->save_stat_track_exercise_info(
411
        $clock_expired_time,
412
        $learnpath_id,
413
        $learnpath_item_id,
414
        $learnpath_item_view_id,
415
        $questionList,
416
        $total_weight
417
    );
418
    $exercise_stat_info = $objExercise->get_stat_track_exercise_info(
419
        $learnpath_id,
420
        $learnpath_item_id,
421
        $learnpath_item_view_id
422
    );
423
424
    // Send notification at the start
425
    if (!api_is_allowed_to_edit(null, true) &&
426
        !api_is_excluded_user_type()
427
    ) {
428
        $objExercise->send_mail_notification_for_exam(
429
            'start',
430
            [],
431
            $origin,
432
            $exe_id
433
        );
434
    }
435
} else {
436
    $exe_id = $exercise_stat_info['exe_id'];
437
    // Remember last question id position.
438
    $isFirstTime = Session::read('firstTime');
439
    if ($isFirstTime && ONE_PER_PAGE == $objExercise->type) {
440
        $resolvedQuestions = Event::getAllExerciseEventByExeId($exe_id);
441
        if (!empty($resolvedQuestions) &&
442
            !empty($exercise_stat_info['data_tracking'])
443
        ) {
444
            // Get current question based in data_tracking question list, instead of track_e_attempt order BT#17789.
445
            $resolvedQuestionsQuestionIds = array_keys($resolvedQuestions);
446
            $count = 0;
447
            $attemptQuestionList = explode(',', $exercise_stat_info['data_tracking']);
448
            foreach ($attemptQuestionList as $index => $question) {
449
                if (in_array($question, $resolvedQuestionsQuestionIds)) {
450
                    $count = $index;
451
                    continue;
452
                }
453
            }
454
            $current_question = $count;
455
            //var_dump($current_question, $index);exit;
456
        }
457
    }
458
}
459
460
Session::write('exe_id', $exe_id);
461
$checkAnswersUrl = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=check_answers&exe_id='.$exe_id.'&'.api_get_cidreq();
462
$saveDurationUrl = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=update_duration&exe_id='.$exe_id.'&'.api_get_cidreq();
463
$questionListInSession = Session::read('questionList');
464
$selectionType = $objExercise->getQuestionSelectionType();
465
466
$allowBlockCategory = false;
467
if (api_get_configuration_value('block_category_questions')) {
468
    $extraFieldValue = new ExtraFieldValue('exercise');
469
    $extraFieldData = $extraFieldValue->get_values_by_handler_and_field_variable($objExercise->iid, 'block_category');
470
    if ($extraFieldData && isset($extraFieldData['value']) && 1 === (int) $extraFieldData['value']) {
471
        $allowBlockCategory = true;
472
    }
473
}
474
475
if (!isset($questionListInSession)) {
476
    // Selects the list of question ID
477
    $questionList = $objExercise->getQuestionList();
478
    // Media questions.
479
    $media_is_activated = $objExercise->mediaIsActivated();
480
    // Getting order from random
481
    if ($media_is_activated == false &&
482
        (
483
            $objExercise->isRandom() ||
484
            !empty($objExercise->getRandomByCategory()) ||
485
            $selectionType > EX_Q_SELECTION_RANDOM
486
        ) &&
487
        isset($exercise_stat_info) &&
488
        !empty($exercise_stat_info['data_tracking'])
489
    ) {
490
        $questionList = explode(',', $exercise_stat_info['data_tracking']);
491
        $questionList = array_combine(
492
            range(1, count($questionList)),
493
            $questionList
494
        );
495
        $categoryList = [];
496
        if ($allowBlockCategory) {
497
            foreach ($questionList as $question) {
498
                $categoryId = TestCategory::getCategoryForQuestion($question);
499
                $categoryList[$categoryId][] = $question;
500
            }
501
            Session::write('categoryList', $categoryList);
502
        }
503
    }
504
    Session::write('questionList', $questionList);
505
} else {
506
    if (isset($objExercise) && isset($exerciseInSession)) {
507
        $questionList = Session::read('questionList');
508
    }
509
}
510
// Array to check in order to block the chat
511
ExerciseLib::create_chat_exercise_session($exe_id);
512
513
if (!empty($exercise_stat_info['questions_to_check'])) {
514
    $myRemindList = $exercise_stat_info['questions_to_check'];
515
    $myRemindList = explode(',', $myRemindList);
516
    $myRemindList = array_filter($myRemindList);
517
}
518
519
$params = "exe_id=$exe_id&exerciseId=$exerciseId&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id&".api_get_cidreq().'&reminder='.$reminder;
520
// It is a lti provider
521
$ltiLaunchId = '';
522
$ltiParams = '';
523
if (isset($_REQUEST['lti_launch_id'])) {
524
    $ltiLaunchId = Security::remove_XSS($_REQUEST['lti_launch_id']);
525
    $ltiParams = '&lti_launch_id='.$ltiLaunchId;
526
    $params .= $ltiParams;
527
}
528
if (2 === $reminder && empty($myRemindList)) {
529
    if ($debug) {
530
        error_log('6.2 calling the exercise_reminder.php');
531
    }
532
    header('Location: exercise_reminder.php?'.$params);
533
    exit;
534
}
535
536
/*
537
 * 7. Loading Time control parameters
538
 * If the expired time is major that zero(0) then the expired time is compute on this time.
539
 */
540
if ($time_control) {
541
    if ($debug) {
542
        error_log('7.1. Time control is enabled');
543
        error_log('7.2. $current_expired_time_key  '.$current_expired_time_key);
544
        error_log(
545
            '7.3. $_SESSION[expired_time][$current_expired_time_key] '.
546
            $_SESSION['expired_time'][$current_expired_time_key]
547
        );
548
    }
549
550
    if (!isset($_SESSION['expired_time'][$current_expired_time_key])) {
551
        // Timer - Get expired_time for a student.
552
        if (!empty($exercise_stat_info)) {
553
            $expired_time_of_this_attempt = $exercise_stat_info['expired_time_control'];
554
            if ($debug) {
555
                error_log('7.4 Seems that the session ends and the user want to retake the exam');
556
                error_log('7.5 $expired_time_of_this_attempt: '.$expired_time_of_this_attempt);
557
            }
558
            // Get the last attempt of an exercise
559
            $last_attempt_date = Event::getLastAttemptDateOfExercise($exercise_stat_info['exe_id']);
560
561
            /* This means that the user enters the exam but do not answer the
562
               first question we get the date from the track_e_exercises not from
563
               the track_et_attempt see #2069 */
564
            if (empty($last_attempt_date)) {
565
                $diff = $current_timestamp - api_strtotime($exercise_stat_info['start_date'], 'UTC');
566
                $last_attempt_date = api_get_utc_datetime(
567
                    api_strtotime($exercise_stat_info['start_date'], 'UTC') + $diff
568
                );
569
            } else {
570
                //Recalculate the time control due #2069
571
                $diff = $current_timestamp - api_strtotime($last_attempt_date, 'UTC');
572
                $last_attempt_date = api_get_utc_datetime(api_strtotime($last_attempt_date, 'UTC') + $diff);
573
            }
574
575
            // New expired time - it is due to the possible closure of session.
576
            $new_expired_time_in_seconds = api_strtotime($expired_time_of_this_attempt, 'UTC') - api_strtotime($last_attempt_date, 'UTC');
577
            $expected_time = $current_timestamp + $new_expired_time_in_seconds;
578
            $clock_expired_time = api_get_utc_datetime($expected_time);
579
580
            // First we update the attempt to today
581
            /* How the expired time is changed into "track_e_exercises" table,
582
               then the last attempt for this student should be changed too */
583
            $sql = "UPDATE $exercise_attempt_table SET
584
                      tms = '".api_get_utc_datetime()."'
585
                    WHERE
586
                        exe_id = '".$exercise_stat_info['exe_id']."' AND
587
                        tms = '".$last_attempt_date."' ";
588
            Database::query($sql);
589
590
            // Sessions that contain the expired time
591
            $_SESSION['expired_time'][$current_expired_time_key] = $clock_expired_time;
592
593
            if ($debug) {
594
                error_log('7.6. $last_attempt_date: '.$last_attempt_date);
595
                error_log('7.7. $new_expired_time_in_seconds: '.$new_expired_time_in_seconds);
596
                error_log('7.8. $expected_time1: '.$expected_time);
597
                error_log('7.9. $clock_expired_time: '.$clock_expired_time);
598
                error_log('7.10. $sql: '.$sql);
599
                error_log('7.11. Setting the $_SESSION[expired_time]: '.$_SESSION['expired_time'][$current_expired_time_key]);
600
            }
601
        }
602
    } else {
603
        $clock_expired_time = $_SESSION['expired_time'][$current_expired_time_key];
604
    }
605
}
606
607
// Get time left for expiring time
608
$time_left = api_strtotime($clock_expired_time, 'UTC') - time();
609
610
/*
611
 * The time control feature is enable here - this feature is enable for a jquery plugin called epiclock
612
 * for more details of how it works see this link : http://eric.garside.name/docs.html?p=epiclock
613
 */
614
if ($time_control) {
615
    //Sends the exercise form when the expired time is finished.
616
    $htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left);
617
}
618
619
$media_questions = $objExercise->getMediaList();
620
$media_question_is_active = $objExercise->mediaIsActivated($media_questions);
621
622
// If the "remind question" option is enabled, in learning path
623
if (!isset($_SESSION['questionList'])) {
624
    // selects the list of question ID
625
    $questionList = $objExercise->get_question_list(false);
626
    if ($media_is_activated == false && $objExercise->isRandom() && !empty($exercise_stat_info['data_tracking'])) {
627
        $questionList = explode(',', $exercise_stat_info['data_tracking']);
628
    }
629
630
    Session::write('questionList', $questionList);
631
} else {
632
    if (isset($objExercise) && isset($_SESSION['objExercise'])) {
633
        $questionList = Session::read('questionList');
634
    }
635
}
636
637
$isLastQuestionInCategory = 0;
638
if ($allowBlockCategory &&
639
    ONE_PER_PAGE == $objExercise->type &&
640
    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $selectionType
641
) {
642
    // Check current attempt.
643
    $currentAttempt = Event::get_exercise_results_by_attempt($exe_id, 'incomplete');
644
    $answeredQuestions = [];
645
    if (!empty($currentAttempt) && isset($currentAttempt[$exe_id]) &&
646
        isset($currentAttempt[$exe_id]['question_list'])
647
    ) {
648
        $answeredQuestions = array_keys($currentAttempt[$exe_id]['question_list']);
649
    }
650
    $categoryAllResolved = [];
651
    $categoryList = Session::read('categoryList');
652
    foreach ($categoryList as $categoryId => $categoryQuestionList) {
653
        $categoryAllResolved[$categoryId] = false;
654
        $answered = 1;
655
        foreach ($categoryQuestionList as $questionInCategoryId) {
656
            if (in_array($questionInCategoryId, $answeredQuestions)) {
657
                $answered++;
658
                break;
659
            }
660
        }
661
        if ($answered === count($categoryList[$categoryId])) {
662
            $categoryAllResolved[$categoryId] = true;
663
        }
664
    }
665
666
    $blockedCategories = [];
667
    if (isset($exercise_stat_info['blocked_categories']) && !empty($exercise_stat_info['blocked_categories'])) {
668
        $blockedCategories = explode(',', $exercise_stat_info['blocked_categories']);
669
    }
670
671
    $count = 0;
672
    $questionCheck = null;
673
    foreach ($questionList as $questionId) {
674
        // if it is not the right question, goes to the next loop iteration
675
        if ((int) $current_question === $count) {
676
            $questionCheck = Question::read($questionId);
677
            break;
678
        }
679
        $count++;
680
    }
681
682
    $categoryId = 0;
683
    if (null !== $questionCheck) {
684
        $categoryId = $questionCheck->category;
685
    }
686
687
    if ($objExercise->review_answers && isset($_GET['category_id'])) {
688
        $categoryId = $_GET['category_id'] ?? 0;
689
    }
690
691
    if (!empty($categoryId)) {
692
        $categoryInfo = $categoryList[$categoryId];
693
        $count = 1;
694
        $total = count($categoryList[$categoryId]);
695
696
        foreach ($categoryList[$categoryId] as $checkQuestionId) {
697
            if ((int) $checkQuestionId === (int) $questionCheck->iid) {
698
                break;
699
            }
700
            $count++;
701
        }
702
703
        if ($count === $total) {
704
            $isLastQuestionInCategory = $categoryId;
705
            if ($isLastQuestionInCategory) {
706
                // This is the last question
707
                if ((int) $current_question + 1 === count($questionList)) {
708
                    if (false === $objExercise->review_answers) {
709
                        $isLastQuestionInCategory = 0;
710
                    }
711
                }
712
            }
713
        }
714
715
        if (0 === $isLastQuestionInCategory) {
716
            $showPreviousButton = false;
717
        }
718
        if (0 === $isLastQuestionInCategory && 2 === $reminder) {
719
            //    $isLastQuestionInCategory = $categoryId;
720
        }
721
    }
722
    //var_dump($categoryId, $blockedCategories, $isLastQuestionInCategory);
723
724
    // Blocked if category was already answered.
725
    if ($categoryId && in_array($categoryId, $blockedCategories)) {
726
        // Redirect to category intro.
727
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_question_reminder.php?'.
728
            $params.'&num='.$current_question.'&category_id='.$isLastQuestionInCategory;
729
        api_location($url);
730
    }
731
}
732
733
if ($debug) {
734
    error_log('8. Question list loaded '.print_r($questionList, 1));
735
}
736
737
// Real question count.
738
$question_count = 0;
739
if (!empty($questionList)) {
740
    $question_count = count($questionList);
741
}
742
if ($media_question_is_active) {
743
    $question_count = $objExercise->get_count_questions_when_using_medias();
744
}
745
746
747
if ($current_question > $question_count) {
748
    // If time control then don't change the current question, otherwise there will be a loop.
749
    // @todo
750
    if (false == $time_control) {
751
        $current_question = 0;
752
    }
753
}
754
755
if ($formSent && isset($_POST)) {
756
    if (!is_array($exerciseResult)) {
757
        $exerciseResult = [];
758
        $exerciseResultCoordinates = [];
759
    }
760
761
    // Only for hotspot
762
    if (!isset($choice) && isset($_REQUEST['hidden_hotspot_id'])) {
763
        $hotspot_id = (int) $_REQUEST['hidden_hotspot_id'];
764
        $choice = [$hotspot_id => ''];
765
    }
766
767
    // Only for upload answer
768
    if (!isset($choice) && isset($_REQUEST['uploadChoice'])) {
769
        $uploadAnswerFileNames = $_REQUEST['uploadChoice'];
770
        $choice = implode('|', $uploadAnswerFileNames[$questionId]);
771
    }
772
773
    // if the user has answered at least one question
774
    if (is_array($choice)) {
775
        if ($debug) {
776
            error_log('9.1. $choice is an array '.print_r($choice, 1));
777
        }
778
        // Also store hotspot spots in the session ($exerciseResultCoordinates
779
        // will be stored in the session at the end of this script)
780
        if (isset($_POST['hotspot'])) {
781
            $exerciseResultCoordinates = $_POST['hotspot'];
782
            if ($debug) {
783
                error_log('9.2. $_POST[hotspot] data '.print_r($exerciseResultCoordinates, 1));
784
            }
785
        }
786
        if (ALL_ON_ONE_PAGE == $objExercise->type) {
787
            // $exerciseResult receives the content of the form.
788
            // Each choice of the student is stored into the array $choice
789
            $exerciseResult = $choice;
790
        } else {
791
            // gets the question ID from $choice. It is the key of the array
792
            [$key] = array_keys($choice);
793
            // if the user didn't already answer this question
794
            if (!isset($exerciseResult[$key])) {
795
                // stores the user answer into the array
796
                $exerciseResult[$key] = $choice[$key];
797
                // Saving each question.
798
                if (!in_array($objExercise->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT])) {
799
                    $nro_question = $current_question; // - 1;
800
                    $questionId = $key;
801
                    // gets the student choice for this question
802
                    $choice = $exerciseResult[$questionId];
803
                    if (isset($exe_id)) {
804
                        // Manage the question and answer attempts
805
                        $objExercise->manage_answer(
806
                            $exe_id,
807
                            $questionId,
808
                            $choice,
809
                            'exercise_show',
810
                            $exerciseResultCoordinates,
811
                            true,
812
                            false,
813
                            false,
814
                            $objExercise->propagate_neg,
815
                            []
816
                        );
817
                    }
818
                }
819
            }
820
        }
821
        if ($debug) {
822
            error_log('9.3.  $choice is an array - end');
823
            error_log('9.4.  $exerciseResult '.print_r($exerciseResult, 1));
824
        }
825
    }
826
827
    // the script "exercise_result.php" will take the variable $exerciseResult from the session
828
    Session::write('exerciseResult', $exerciseResult);
829
    Session::write('exerciseResultCoordinates', $exerciseResultCoordinates);
830
831
    // if all questions on one page OR if it is the last question (only for an exercise with one question per page)
832
    if ($objExercise->type == ALL_ON_ONE_PAGE || $current_question >= $question_count) {
833
        if (api_is_allowed_to_session_edit()) {
834
            // goes to the script that will show the result of the exercise
835
            if ($objExercise->type == ALL_ON_ONE_PAGE) {
836
                if ($debug) {
837
                    error_log('10. Exercise ALL_ON_ONE_PAGE -> Redirecting to exercise_result.php');
838
                }
839
                //We check if the user attempts before sending to the exercise_result.php
840
                if ($objExercise->selectAttempts() > 0) {
841
                    $attempt_count = Event::get_attempt_count(
842
                        api_get_user_id(),
843
                        $exerciseId,
844
                        $learnpath_id,
845
                        $learnpath_item_id,
846
                        $learnpath_item_view_id
847
                    );
848
                    if ($attempt_count >= $objExercise->selectAttempts()) {
849
                        echo Display::return_message(
850
                            sprintf(get_lang('ReachedMaxAttempts'), $exercise_title, $objExercise->selectAttempts()),
851
                            'warning',
852
                            false
853
                        );
854
                        if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
855
                            //so we are not in learnpath tool
856
                            echo '</div>'; //End glossary div
857
                            Display::display_footer();
858
                        } else {
859
                            echo '</body></html>';
860
                        }
861
                    }
862
                }
863
                header("Location: exercise_result.php?".api_get_cidreq()."&exe_id=$exe_id&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id.$ltiParams");
864
                exit;
865
            } else {
866
                if ($debug) {
867
                    error_log('10. Redirecting to exercise_result.php');
868
                }
869
                header("Location: exercise_result.php?".api_get_cidreq()."&exe_id=$exe_id&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id.$ltiParams");
870
                exit;
871
            }
872
        } else {
873
            if ($debug) {
874
                error_log('10. Redirecting to exercise_submit.php');
875
            }
876
            header("Location: exercise_submit.php?".api_get_cidreq()."&exerciseId=$exerciseId");
877
            exit;
878
        }
879
    }
880
    if ($debug) {
881
        error_log('11. $formSent was set - end');
882
    }
883
}
884
885
// If questionNum comes from POST and not from GET
886
$latestQuestionId = Event::getLatestQuestionIdFromAttempt($exe_id);
887
888
if (is_null($current_question)) {
889
    $current_question = 1;
890
    if ($latestQuestionId) {
891
        $current_question = $objExercise->getPositionInCompressedQuestionList($latestQuestionId);
892
    }
893
} else {
894
    $current_question++;
895
}
896
897
if ($question_count != 0) {
898
    if ($objExercise->type == ALL_ON_ONE_PAGE || $current_question > $question_count) {
899
        if (api_is_allowed_to_session_edit()) {
900
            // goes to the script that will show the result of the exercise
901
            if ($objExercise->type == ALL_ON_ONE_PAGE) {
902
                if ($debug) {
903
                    error_log('12. Exercise ALL_ON_ONE_PAGE -> Redirecting to exercise_result.php');
904
                }
905
906
                // We check if the user attempts before sending to the exercise_result.php
907
                if ($objExercise->selectAttempts() > 0) {
908
                    $attempt_count = Event::get_attempt_count(
909
                        api_get_user_id(),
910
                        $exerciseId,
911
                        $learnpath_id,
912
                        $learnpath_item_id,
913
                        $learnpath_item_view_id
914
                    );
915
                    if ($attempt_count >= $objExercise->selectAttempts()) {
916
                        Display::return_message(
917
                            sprintf(get_lang('ReachedMaxAttempts'), $exercise_title, $objExercise->selectAttempts()),
918
                            'warning',
919
                            false
920
                        );
921
                        if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
922
                            //so we are not in learnpath tool
923
                            echo '</div>'; //End glossary div
924
                            Display::display_footer();
925
                        } else {
926
                            echo '</body></html>';
927
                        }
928
                        exit;
929
                    }
930
                }
931
            } else {
932
                if ($objExercise->review_answers) {
933
                    header('Location: exercise_reminder.php?'.$params);
934
                    exit;
935
                } else {
936
                    $certaintyQuestionPresent = false;
937
                    foreach ($questionList as $questionId) {
938
                        $question = Question::read($questionId);
939
                        if ($question->type == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
940
                            $certaintyQuestionPresent = true;
941
                            break;
942
                        }
943
                    }
944
                    if ($certaintyQuestionPresent) {
945
                        // Certainty grade question
946
                        // We send an email to the student before redirection to the result page
947
                        MultipleAnswerTrueFalseDegreeCertainty::sendQuestionCertaintyNotification(
948
                            $user_id, $objExercise, $exe_id
949
                        );
950
                    }
951
952
                    header("Location: exercise_result.php?"
953
                        .api_get_cidreq()
954
                        ."&exe_id=$exe_id&learnpath_id=$learnpath_id&learnpath_item_id="
955
                        .$learnpath_item_id
956
                        ."&learnpath_item_view_id=$learnpath_item_view_id.$ltiParams"
957
                    );
958
                    exit;
959
                }
960
            }
961
        }
962
    }
963
} else {
964
    $error = get_lang('ThereAreNoQuestionsForThisExercise');
965
    // if we are in the case where user select random by category, but didn't choose the number of random question
966
    if ($objExercise->getRandomByCategory() > 0 && $objExercise->random <= 0) {
967
        $error .= '<br/>'.get_lang('PleaseSelectSomeRandomQuestion');
968
    }
969
}
970
971
if (api_is_in_gradebook()) {
972
    $interbreadcrumb[] = [
973
        'url' => Category::getUrl(),
974
        'name' => get_lang('ToolGradebook'),
975
    ];
976
}
977
978
$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
979
$interbreadcrumb[] = ['url' => '#', 'name' => $objExercise->selectTitle(true)];
980
981
// Time per question.
982
$questionTimeCondition = '';
983
$showQuestionClock = false;
984
if ($allowTimePerQuestion && $objExercise->type == ONE_PER_PAGE) {
985
    $objQuestionTmp = null;
986
    $previousQuestion = null;
987
    if (!empty($questionList)) {
988
        $i = 0;
989
        foreach ($questionList as $questionId) {
990
            $i++;
991
            $objQuestionTmp = Question::read($questionId);
992
            // if it is not the right question, goes to the next loop iteration
993
            if ($current_question == $i) {
994
                break;
995
            }
996
            $previousQuestion = $objQuestionTmp;
997
        }
998
    }
999
    $extraFieldValue = new ExtraFieldValue('question');
1000
    $value = $extraFieldValue->get_values_by_handler_and_field_variable($objQuestionTmp->iid, 'time');
1001
    if (!empty($value) && isset($value['value']) && !empty($value['value'])) {
1002
        $showQuestionClock = true;
1003
        $seconds = (int) $value['value'];
1004
        $now = time();
1005
        $timeSpent = Event::getAttemptQuestionDuration($exe_id, $objQuestionTmp->iid);
1006
        // Redirect to next question.
1007
        if ($timeSpent > $seconds) {
1008
            $nextQuestion = (int) $currentQuestionFromUrl + 1;
1009
            $nextQuestionUrl = api_get_path(WEB_CODE_PATH).
1010
                "exercise/exercise_submit.php?$params&num=$nextQuestion&remind_question_id=$remind_question_id";
1011
            api_location($nextQuestionUrl);
1012
        }
1013
1014
        $seconds = $seconds - $timeSpent;
1015
        $questionTimeCondition = "
1016
                var timer = new easytimer.Timer();
1017
                timer.start({countdown: true, startValues: {seconds: $seconds}});
1018
                timer.addEventListener('secondsUpdated', function (e) {
1019
                    $('#question_timer').html(timer.getTimeValues().toString());
1020
                });
1021
                timer.addEventListener('targetAchieved', function (e) {
1022
                    $('.question-validate-btn').click();
1023
                });
1024
            ";
1025
    }
1026
}
1027
1028
$quizKeepAlivePingInterval = api_get_configuration_value('quiz_keep_alive_ping_interval');
1029
1030
if (false !== $quizKeepAlivePingInterval) {
1031
    $quizKeepAlivePingInterval *= 1000;
1032
1033
    $htmlHeadXtra[] = "<script>$(function () {
1034
        window.setInterval(function () {
1035
            $.post(_p.web_ajax + 'exercise.ajax.php', {a: 'ping', exe_id: '{$objExercise->iid}'});
1036
        }, $quizKeepAlivePingInterval);
1037
    })</script>";
1038
}
1039
1040
if (!in_array($origin, ['learnpath', 'embeddable', 'mobileapp', 'iframe'])) {
1041
    //so we are not in learnpath tool
1042
    SessionManager::addFlashSessionReadOnly();
1043
    Display::display_header(null, 'Exercise');
1044
} else {
1045
    Display::display_reduced_header();
1046
    echo '<div style="height:10px">&nbsp;</div>';
1047
}
1048
1049
if ($origin === 'mobileapp') {
1050
    echo '<div class="actions">';
1051
    echo '<a href="javascript:window.history.go(-1);">'.
1052
        Display::return_icon('back.png', get_lang('GoBackToQuestionList'), [], 32).'</a>';
1053
    echo '</div>';
1054
}
1055
1056
$show_quiz_edition = $objExercise->added_in_lp();
1057
// I'm in a preview mode
1058
if (api_is_course_admin() && !in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1059
    echo '<div class="actions">';
1060
    if ($show_quiz_edition == false) {
1061
        echo '<a href="exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->iid.'">'.
1062
            Display::return_icon('settings.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
1063
    } else {
1064
        echo '<a href="#">'.
1065
            Display::return_icon('settings_na.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
1066
    }
1067
    echo '</div>';
1068
}
1069
1070
$is_visible_return = $objExercise->is_visible(
1071
    $learnpath_id,
1072
    $learnpath_item_id,
1073
    $learnpath_item_view_id,
1074
    true,
1075
    $sessionId
1076
);
1077
1078
if ($is_visible_return['value'] == false) {
1079
    echo $is_visible_return['message'];
1080
    if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1081
        Display::display_footer();
1082
    }
1083
    exit;
1084
}
1085
1086
if (!api_is_allowed_to_session_edit()) {
1087
    if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1088
        Display::display_footer();
1089
    }
1090
    exit;
1091
}
1092
1093
$exercise_timeover = false;
1094
$limit_time_exists = !empty($objExercise->start_time) || !empty($objExercise->end_time) ? true : false;
1095
if ($limit_time_exists) {
1096
    $exercise_start_time = api_strtotime($objExercise->start_time, 'UTC');
1097
    $exercise_end_time = api_strtotime($objExercise->end_time, 'UTC');
1098
    $time_now = time();
1099
    $permission_to_start = true;
1100
    if (!empty($objExercise->start_time)) {
1101
        $permission_to_start = (($time_now - $exercise_start_time) > 0) ? true : false;
1102
    }
1103
    if ($_SERVER['REQUEST_METHOD'] != 'POST') {
1104
        if (!empty($objExercise->end_time)) {
1105
            $exercise_timeover = (($time_now - $exercise_end_time) > 0) ? true : false;
1106
        }
1107
    }
1108
1109
    if (!$permission_to_start || $exercise_timeover) {
1110
        if (!api_is_allowed_to_edit(null, true)) {
1111
            $message_warning = $permission_to_start ? get_lang('ReachedTimeLimit') : get_lang('ExerciseNoStartedYet');
1112
            echo Display::return_message(
1113
                sprintf(
1114
                    $message_warning,
1115
                    $exercise_title,
1116
                    $objExercise->selectAttempts()
1117
                ),
1118
                'warning'
1119
            );
1120
            if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1121
                Display::display_footer();
1122
            }
1123
            exit;
1124
        } else {
1125
            $message_warning = $permission_to_start ? get_lang('ReachedTimeLimitAdmin') : get_lang('ExerciseNoStartedAdmin');
1126
            echo Display::return_message(
1127
                sprintf(
1128
                    $message_warning,
1129
                    $exercise_title,
1130
                    $objExercise->selectAttempts()
1131
                ),
1132
                'warning'
1133
            );
1134
        }
1135
    }
1136
}
1137
1138
// Blocking empty start times see BT#2800
1139
global $_custom;
1140
if (isset($_custom['exercises_hidden_when_no_start_date']) &&
1141
    $_custom['exercises_hidden_when_no_start_date']
1142
) {
1143
    if (empty($objExercise->start_time)) {
1144
        echo Display::return_message(
1145
            sprintf(
1146
                get_lang('ExerciseNoStartedYet'),
1147
                $exercise_title,
1148
                $objExercise->selectAttempts()
1149
            ),
1150
            'warning'
1151
        );
1152
        if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1153
            Display::display_footer();
1154
            exit;
1155
        }
1156
    }
1157
}
1158
1159
if ($time_control) {
1160
    echo $objExercise->returnTimeLeftDiv();
1161
    echo '<div style="display:none" class="warning-message" id="expired-message-id">'.
1162
        get_lang('ExerciseExpiredTimeMessage').'</div>';
1163
}
1164
1165
if ($showQuestionClock) {
1166
    $icon = Display::returnFontAwesomeIcon('clock-o');
1167
    echo '<div class="well" style="text-align: center">
1168
            '.get_lang('RemainingTimeToFinishQuestion').'
1169
            <div id="question_timer" class="label label-warning"></div>
1170
          </div>';
1171
}
1172
1173
if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1174
    echo '<div id="highlight-plugin" class="glossary-content">';
1175
}
1176
if (2 === $reminder) {
1177
    $data_tracking = $exercise_stat_info['data_tracking'];
1178
    $data_tracking = explode(',', $data_tracking);
1179
    $current_question = 1; // Set by default the 1st question
1180
1181
    if (!empty($myRemindList)) {
1182
        // Checking which questions we are going to call from the remind list
1183
        for ($i = 0; $i < count($data_tracking); $i++) {
1184
            for ($j = 0; $j < count($myRemindList); $j++) {
1185
                if (!empty($remind_question_id)) {
1186
                    if ($remind_question_id == $myRemindList[$j]) {
1187
                        if ($remind_question_id == $data_tracking[$i]) {
1188
                            if (isset($myRemindList[$j + 1])) {
1189
                                $remind_question_id = $myRemindList[$j + 1];
1190
                                $current_question = $i + 1;
1191
                            } else {
1192
                                // We end the remind list we go to the exercise_reminder.php please
1193
                                $remind_question_id = -1;
1194
                                $current_question = $i + 1; // last question
1195
                            }
1196
                            break 2;
1197
                        }
1198
                    }
1199
                } else {
1200
                    if ($myRemindList[$j] == $data_tracking[$i]) {
1201
                        if (isset($myRemindList[$j + 1])) {
1202
                            $remind_question_id = $myRemindList[$j + 1];
1203
                            $current_question = $i + 1; // last question
1204
                        } else {
1205
                            // We end the remind list we go to the exercise_reminder.php please
1206
                            $remind_question_id = -1;
1207
                            $current_question = $i + 1; // last question
1208
                        }
1209
                        break 2;
1210
                    }
1211
                }
1212
            }
1213
        }
1214
    } else {
1215
        if ($objExercise->review_answers) {
1216
            if ($debug) {
1217
                error_log('. redirecting to exercise_reminder.php ');
1218
            }
1219
            header("Location: exercise_reminder.php?$params");
1220
            exit;
1221
        }
1222
    }
1223
}
1224
1225
if (!empty($error)) {
1226
    Display::addFlash(Display::return_message($error, 'error', false));
1227
    api_not_allowed();
1228
    exit;
1229
}
1230
1231
$script_php = 'exercise_result.php';
1232
if ($objExercise->review_answers) {
1233
    $script_php = 'exercise_reminder.php';
1234
}
1235
1236
if (!empty($exercise_sound)) {
1237
    echo "<a
1238
        href=\"../document/download.php?doc_url=%2Faudio%2F".Security::remove_XSS($exercise_sound)."\"
1239
        target=\"_blank\">";
1240
    echo "<img src=\"../img/sound.gif\" border=\"0\" align=\"absmiddle\" alt=", get_lang('Sound')."\" /></a>";
1241
}
1242
// Get number of hotspot questions for javascript validation
1243
$number_of_hotspot_questions = 0;
1244
$i = 0;
1245
if (!empty($questionList)) {
1246
    foreach ($questionList as $questionId) {
1247
        $i++;
1248
        $objQuestionTmp = Question::read($questionId);
1249
        $selectType = $objQuestionTmp->selectType();
1250
        // for sequential exercises.
1251
        if ($objExercise->type == ONE_PER_PAGE) {
1252
            // if it is not the right question, goes to the next loop iteration
1253
            if ($current_question != $i) {
1254
                continue;
1255
            } else {
1256
                if (in_array($selectType, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_DELINEATION])) {
1257
                    $number_of_hotspot_questions++;
1258
                }
1259
                break;
1260
            }
1261
        } else {
1262
            if (in_array($selectType, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_DELINEATION])) {
1263
                $number_of_hotspot_questions++;
1264
            }
1265
        }
1266
    }
1267
}
1268
1269
if ($allowBlockCategory &&
1270
    ONE_PER_PAGE == $objExercise->type &&
1271
    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $selectionType
1272
) {
1273
    if (0 === $isLastQuestionInCategory && 2 === $reminder) {
1274
        $endReminderValue = false;
1275
        if (!empty($myRemindList)) {
1276
            $endValue = end($myRemindList);
1277
            if ($endValue == $questionId) {
1278
                $endReminderValue = true;
1279
            }
1280
        }
1281
        if ($endReminderValue) {
1282
            $isLastQuestionInCategory = $categoryId;
1283
        }
1284
    }
1285
}
1286
1287
$saveIcon = Display::return_icon(
1288
    'save.png',
1289
    get_lang('Saved'),
1290
    [],
1291
    ICON_SIZE_SMALL,
1292
    false,
1293
    true
1294
);
1295
$loading = Display::returnFontAwesomeIcon('spinner', null, true, 'fa-spin');
1296
1297
echo '<script>
1298
    function addExerciseEvent(elm, evType, fn, useCapture) {
1299
        if (elm.addEventListener) {
1300
            elm.addEventListener(evType, fn, useCapture);
1301
            return;
1302
        } else if (elm.attachEvent) {
1303
            elm.attachEvent(\'on\' + evType, fn);
1304
        } else{
1305
            elm[\'on\'+evType] = fn;
1306
        }
1307
        return;
1308
    }
1309
1310
    var calledUpdateDuration = false;
1311
    function updateDuration() {
1312
        if (calledUpdateDuration === false) {
1313
            var saveDurationUrl = "'.$saveDurationUrl.'";
1314
            // Logout of course just in case
1315
            $.ajax({
1316
                url: saveDurationUrl,
1317
                success: function (data) {
1318
                    calledUpdateDuration = true;
1319
                    return;
1320
                },
1321
            });
1322
            return;
1323
        }
1324
    }
1325
1326
    $(function() {
1327
        '.$questionTimeCondition.'
1328
        // This pre-load the save.png icon
1329
        var saveImage = new Image();
1330
        saveImage.src = "'.$saveIcon.'";
1331
1332
        // Block form submition on enter
1333
        $(".block_on_enter").keypress(function(event) {
1334
            return event.keyCode != 13;
1335
        });
1336
1337
        $(".checkCalculatedQuestionOnEnter").keypress(function(event) {
1338
            if (event.keyCode === 13) {
1339
                event.preventDefault();
1340
                var id = $(this).attr("id");
1341
                var parts = id.split("_");
1342
                var buttonId = "button_" + parts[1];
1343
                document.getElementById(buttonId).click();
1344
            }
1345
        });
1346
1347
        $(".main_question").mouseout(function() {
1348
            $(this).removeClass("question_highlight");
1349
        });
1350
1351
        $(".no_remind_highlight").hide();
1352
        $("form#exercise_form").prepend($("#exercise-description"));
1353
1354
        $(\'button[name="previous_question_and_save"]\').on("touchstart click", function (e) {
1355
            e.preventDefault();
1356
            e.stopPropagation();
1357
            var
1358
                $this = $(this),
1359
                previousId = parseInt($this.data(\'prev\')) || 0,
1360
                questionId = parseInt($this.data(\'question\')) || 0;
1361
1362
            previous_question_and_save(previousId, questionId);
1363
        });
1364
1365
        $(\'button[name="save_question_list"]\').on(\'touchstart click\', function (e) {
1366
            e.preventDefault();
1367
            e.stopPropagation();
1368
            var $this = $(this);
1369
            var questionList = $this.data(\'list\').split(",");
1370
1371
            save_question_list(questionList);
1372
        });
1373
1374
        $(\'button[name="check_answers"]\').on(\'touchstart click\', function (e) {
1375
            e.preventDefault();
1376
            e.stopPropagation();
1377
            var $this = $(this);
1378
            var questionId = parseInt($this.data(\'question\')) || 0;
1379
1380
            save_now(questionId, "check_answers");
1381
        });
1382
1383
        $(\'button[name="save_now"]\').on(\'touchstart click\', function (e) {
1384
            e.preventDefault();
1385
            e.stopPropagation();
1386
            var
1387
                $this = $(this),
1388
                questionId = parseInt($this.data(\'question\')) || 0,
1389
                urlExtra = $this.data(\'url\') || null;
1390
1391
            save_now(questionId, urlExtra);
1392
        });
1393
1394
        $(\'button[name="validate_all"]\').on(\'touchstart click\', function (e) {
1395
            e.preventDefault();
1396
            e.stopPropagation();
1397
1398
            validate_all();
1399
        });
1400
1401
        // Save attempt duration
1402
        addExerciseEvent(window, \'unload\', updateDuration , false);
1403
        addExerciseEvent(window, \'beforeunload\', updateDuration , false);
1404
    });
1405
1406
    function previous_question(question_num) {
1407
        var url = "exercise_submit.php?'.$params.'&num="+question_num;
1408
        window.location = url;
1409
    }
1410
1411
    function previous_question_and_save(previous_question_id, question_id_to_save) {
1412
        var url = "exercise_submit.php?'.$params.'&num="+previous_question_id;
1413
        //Save the current question
1414
        save_now(question_id_to_save, url);
1415
    }
1416
1417
    function save_question_list(question_list) {
1418
        $.each(question_list, function(key, question_id) {
1419
            save_now(question_id, null);
1420
        });
1421
1422
        var url = "";
1423
        if ('.$reminder.' == 1 ) {
1424
            url = "exercise_reminder.php?'.$params.'&num='.$current_question.'";
1425
        } else if ('.$reminder.' == 2 ) {
1426
            url = "exercise_submit.php?'.$params.'&num='.$current_question.'&remind_question_id='.$remind_question_id.'&reminder=2";
1427
        } else {
1428
            url = "exercise_submit.php?'.$params.'&num='.$current_question.'&remind_question_id='.$remind_question_id.'";
1429
        }
1430
        window.location = url;
1431
    }
1432
1433
    function redirectExerciseToResult()
1434
    {
1435
        window.location = "'.$script_php.'?'.$params.'";
1436
    }
1437
1438
    function save_now(question_id, url_extra) {
1439
        // 1. Normal choice inputs
1440
        var my_choice = $(\'*[name*="choice[\'+question_id+\']"]\').serialize();
1441
1442
        // 2. Reminder checkbox
1443
        var remind_list = $(\'*[name*="remind_list"]\').serialize();
1444
1445
        // 3. Hotspots
1446
        var hotspot = $(\'*[name*="hotspot[\'+question_id+\']"]\').serialize();
1447
1448
        // 4. choice for degree of certainty
1449
        var my_choiceDc = $(\'*[name*="choiceDegreeCertainty[\'+question_id+\']"]\').serialize();
1450
1451
        // 5. upload answer files
1452
        var uploadAnswerFiles = $(\'*[name*="uploadChoice[\'+question_id+\'][]"]\').serialize();
1453
1454
        // Checking CkEditor
1455
        if (question_id) {
1456
            if (CKEDITOR.instances["choice["+question_id+"]"]) {
1457
                var ckContent = CKEDITOR.instances["choice["+question_id+"]"].getData();
1458
                my_choice = {};
1459
                my_choice["choice["+question_id+"]"] = ckContent;
1460
                my_choice = $.param(my_choice);
1461
            }
1462
        }
1463
1464
        if ($(\'input[name="remind_list[\'+question_id+\']"]\').is(\':checked\')) {
1465
            $("#question_div_"+question_id).addClass("remind_highlight");
1466
        } else {
1467
            $("#question_div_"+question_id).removeClass("remind_highlight");
1468
        }
1469
1470
        // Only for the first time
1471
        var dataparam = "'.$params.'&type=simple&question_id="+question_id;
1472
        dataparam += "&"+my_choice;
1473
        dataparam += hotspot ? ("&" + hotspot) : "";
1474
        dataparam += remind_list ? ("&" + remind_list) : "";
1475
        dataparam += my_choiceDc ? ("&" + my_choiceDc) : "";
1476
        dataparam += uploadAnswerFiles ? ("&" + uploadAnswerFiles) : "";
1477
1478
        $("#save_for_now_"+question_id).html(\''.$loading.'\');
1479
        $.ajax({
1480
            type:"post",
1481
            async: false,
1482
            url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=save_exercise_by_now",
1483
            data: dataparam,
1484
            success: function(return_value) {
1485
                if (return_value.ok) {
1486
                    $("#save_for_now_"+question_id).html(\''.
1487
                    Display::return_icon('save.png', get_lang('Saved'), [], ICON_SIZE_SMALL).'\');
1488
                } else if (return_value.error) {
1489
                    $("#save_for_now_"+question_id).html(\''.
1490
                        Display::return_icon('error.png', get_lang('Error'), [], ICON_SIZE_SMALL).'\');
1491
                } else if (return_value.type == "one_per_page") {
1492
                    var url = "";
1493
                    if ('.$reminder.' == 1 ) {
1494
                        url = "exercise_reminder.php?'.$params.'&num='.$current_question.'";
1495
                    } else if ('.$reminder.' == 2 ) {
1496
                        url = "exercise_submit.php?'.$params.'&num='.$current_question.
1497
                            '&remind_question_id='.$remind_question_id.'&reminder=2";
1498
                    } else {
1499
                        url = "exercise_submit.php?'.$params.'&num='.$current_question.
1500
                            '&remind_question_id='.$remind_question_id.'";
1501
                    }
1502
1503
                    // If last question in category send to exercise_question_reminder.php
1504
                    if ('.$isLastQuestionInCategory.' > 0 ) {
1505
                        url = "exercise_question_reminder.php?'.$params.'&num='.($current_question - 1).'&category_id='.$isLastQuestionInCategory.'";
1506
                    }
1507
1508
                    if (url_extra) {
1509
                        url = url_extra;
1510
                    }
1511
1512
                    $("#save_for_now_"+question_id).html(\''.
1513
                        Display::return_icon('save.png', get_lang('Saved'), [], ICON_SIZE_SMALL).'\' + return_value.savedAnswerMessage);
1514
1515
                    // Show popup
1516
                    if ("check_answers" === url_extra) {
1517
                        var button = $(\'button[name="check_answers"]\');
1518
                        var questionId = parseInt(button.data(\'question\')) || 0;
1519
                        var urlExtra = button.data(\'url\') || null;
1520
                        var checkUrl = "'.$checkAnswersUrl.'";
1521
1522
                        $("#global-modal").attr("data-keyboard", "false");
1523
                        $("#global-modal").attr("data-backdrop", "static");
1524
                        $("#global-modal").find(".close").hide();
1525
1526
                        $("#global-modal .modal-body").load(checkUrl, function() {
1527
                            $("#global-modal .modal-body").append("<div class=\"btn-group\"></div>");
1528
                            var continueTest = $("<a>",{
1529
                                text: "'.addslashes(get_lang('ContinueTest')).'",
1530
                                title: "'.addslashes(get_lang('ContinueTest')).'",
1531
                                href: "javascript:void(0);",
1532
                                click: function(){
1533
                                    $(this).attr("disabled", "disabled");
1534
                                    $("#global-modal").modal("hide");
1535
                                    $("#global-modal .modal-body").html("");
1536
                                }
1537
                            }).addClass("btn btn-default").appendTo("#global-modal .modal-body .btn-group");
1538
1539
                             $("<a>",{
1540
                                text: "'.addslashes(get_lang('EndTest')).'",
1541
                                title: "'.addslashes(get_lang('EndTest')).'",
1542
                                href: "javascript:void(0);",
1543
                                click: function() {
1544
                                    $(this).attr("disabled", "disabled");
1545
                                    continueTest.attr("disabled", "disabled");
1546
                                    save_now(questionId, urlExtra);
1547
                                    $("#global-modal .modal-body").html("<span style=\"text-align:center\">'.addslashes($loading).addslashes(get_lang('Loading')).'</span>");
1548
                                }
1549
                            }).addClass("btn btn-primary").appendTo("#global-modal .modal-body .btn-group");
1550
                        });
1551
                        $("#global-modal").modal("show");
1552
1553
                        return true;
1554
                    }
1555
                    // window.quizTimeEnding will be reset in exercise.class.php
1556
                    if (window.quizTimeEnding) {
1557
                        redirectExerciseToResult();
1558
                    } else {
1559
                        window.location = url;
1560
                    }
1561
                }
1562
            },
1563
            error: function() {
1564
                $("#save_for_now_"+question_id).html(\''.
1565
                    Display::return_icon('error.png', get_lang('Error'), [], ICON_SIZE_SMALL).'\');
1566
            }
1567
        });
1568
    }
1569
1570
    function save_now_all(validate) {
1571
        // 1. Input choice.
1572
        var my_choice = $(\'*[name*="choice"]\').serialize();
1573
1574
        // 2. Reminder.
1575
        var remind_list = $(\'*[name*="remind_list"]\').serialize();
1576
1577
        // 3. Hotspots.
1578
        var hotspot = $(\'*[name*="hotspot"]\').serialize();
1579
1580
        // Question list.
1581
        var question_list = ['.implode(',', $questionList).'];
1582
        var free_answers = {};
1583
        $.each(question_list, function(index, my_question_id) {
1584
            // Checking Ckeditor and upload answer
1585
            if (my_question_id) {
1586
                if (CKEDITOR.instances["choice["+my_question_id+"]"]) {
1587
                    var ckContent = CKEDITOR.instances["choice["+my_question_id+"]"].getData();
1588
                    free_answers["free_choice["+my_question_id+"]"] = ckContent;
1589
                }
1590
                if ($(\'*[name*="uploadChoice[\'+my_question_id+\']"]\').length) {
1591
                    var uploadChoice = $(\'*[name*="uploadChoice[\'+my_question_id+\']"]\').serializeArray();
1592
                    $.each(uploadChoice, function(i, obj) {
1593
                        free_answers["uploadChoice["+my_question_id+"]["+i+"]"] = uploadChoice[i].value;
1594
                    });
1595
                }
1596
            }
1597
        });
1598
1599
        free_answers = $.param(free_answers);
1600
        $("#save_all_response").html(\''.$loading.'\');
1601
1602
        var requestData = "'.$params.'&type=all";
1603
        requestData += "&" + my_choice;
1604
        requestData += hotspot ? ("&" + hotspot) : "";
1605
        requestData += free_answers ? ("&" + free_answers) : "";
1606
        requestData += remind_list ? ("&" + remind_list) : "";
1607
1608
        $.ajax({
1609
            type:"post",
1610
            async: false,
1611
            url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=save_exercise_by_now",
1612
            data: requestData,
1613
            success: function(return_value) {
1614
                if (return_value.ok) {
1615
                    if (validate == "validate") {
1616
                        $("#save_all_response").html(return_value.savedAnswerMessage);
1617
                        window.location = "'.$script_php.'?'.$params.'";
1618
                    } else {
1619
                        $("#save_all_response").html(\''.Display::return_icon('accept.png').'\');
1620
                    }
1621
                } else {
1622
                    $("#save_all_response").html(\''.Display::return_icon('wrong.gif').'\');
1623
                }
1624
            }
1625
        });
1626
        return false;
1627
    }
1628
1629
    function validate_all() {
1630
        save_now_all("validate");
1631
    }
1632
1633
    window.quizTimeEnding = false;
1634
</script>';
1635
1636
echo '<form id="exercise_form" method="post" action="'.
1637
        api_get_self().'?'.api_get_cidreq().'&reminder='.$reminder.
1638
        '&autocomplete=off&exerciseId='.$exerciseId.'" name="frm_exercise">
1639
     <input type="hidden" name="formSent" value="1" />
1640
     <input type="hidden" name="exerciseId" value="'.$exerciseId.'" />
1641
     <input type="hidden" name="num" value="'.$current_question.'" id="num_current_id" />
1642
     <input type="hidden" name="num_answer" value="'.$currentAnswer.'" id="num_current_answer_id" />
1643
     <input type="hidden" name="exe_id" value="'.$exe_id.'" />
1644
     <input type="hidden" name="origin" value="'.$origin.'" />
1645
     <input type="hidden" name="reminder" value="'.$reminder.'" />
1646
     <input type="hidden" name="learnpath_id" value="'.$learnpath_id.'" />
1647
     <input type="hidden" name="learnpath_item_id" value="'.$learnpath_item_id.'" />
1648
     <input type="hidden" name="learnpath_item_view_id" value="'.$learnpath_item_view_id.'" />';
1649
if (!empty($ltiLaunchId)) {
1650
    echo '<input type="hidden" name="lti_launch_id" value="'.$ltiLaunchId.'" />';
1651
}
1652
1653
// Show list of questions
1654
$i = 1;
1655
$attempt_list = [];
1656
if (isset($exe_id)) {
1657
    $attempt_list = Event::getAllExerciseEventByExeId($exe_id);
1658
}
1659
1660
$remind_list = [];
1661
if (isset($exercise_stat_info['questions_to_check']) &&
1662
    !empty($exercise_stat_info['questions_to_check'])
1663
) {
1664
    $remind_list = explode(',', $exercise_stat_info['questions_to_check']);
1665
}
1666
render_question_list(
1667
    $objExercise,
1668
    $questionList,
1669
    $current_question,
1670
    $exerciseResult,
1671
    $attempt_list,
1672
    $remind_list,
1673
    $media_questions
1674
);
1675
echo '</form>';
1676
1677
function render_question_list(
1678
    $objExercise,
1679
    $questionList,
1680
    $current_question,
1681
    $exerciseResult,
1682
    $attempt_list,
1683
    $remind_list,
1684
    $media_questions = [],
1685
    $exerciseInSession,
1686
    $learnpath_id = null,
1687
    $learnpath_item_id = null,
1688
    $learnpath_item_view_id = null,
1689
    $myRemindList = [],
1690
    $showPreviousButton = false
1691
) {
1692
    global $origin;
1693
1694
    $i = 1;
1695
1696
    //Normal question list render
1697
    foreach ($questionList as $questionId) {
1698
        // for sequential exercises
1699
        if (ONE_PER_PAGE == $objExercise->type) {
1700
            // if it is not the right question, goes to the next loop iteration
1701
            if ($current_question != $i) {
1702
                $i++;
1703
                continue;
1704
            } else {
1705
                if (!in_array($objExercise->getFeedbackType(),
1706
                    [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
1707
                    // if the user has already answered this question
1708
                    if (isset($exerciseResult[$questionId])) {
1709
                        // construction of the Question object
1710
                        $objQuestionTmp = Question::read($questionId);
1711
                        //$questionName = $objQuestionTmp->selectTitle();
1712
                        // destruction of the Question object
1713
                        unset($objQuestionTmp);
1714
                        echo Display::return_message(get_lang('AlreadyAnswered'));
1715
                        $i++;
1716
                        break;
1717
                    }
1718
                }
1719
1720
                if (1 === $exerciseInSession->getPreventBackwards()) {
1721
                    if (isset($attempt_list[$questionId])) {
1722
                        echo Display::return_message(get_lang('AlreadyAnswered'));
1723
                        $i++;
1724
                        break;
1725
                    }
1726
                }
1727
            }
1728
        }
1729
1730
1731
        // Media question rendering
1732
1733
        if (isset($media_questions) && !empty($media_questions)) {
1734
            $media_question_list = $media_questions[$questionId];
1735
            $objQuestionTmp = Question::read($questionId);
1736
1737
            $counter = 1;
1738
            if ($objQuestionTmp->type == MEDIA_QUESTION) {
1739
                echo $objQuestionTmp->show_media_content();
1740
                $count_of_questions_inside_media = count($media_question_list);
1741
1742
            }
1743
            //Show questions that belongs to a media
1744
            if (!empty($media_question_list)) {
1745
                $letterCounter = 97;
1746
                foreach ($media_question_list as $my_question_id) {
1747
                    if ($counter == $count_of_questions_inside_media) {
1748
                        $last_question_in_media = true;
1749
                    }
1750
                    render_question(
1751
                        $objExercise,
1752
                        $my_question_id,
1753
                        $attempt_list,
1754
                        $remind_list,
1755
                        chr($letterCounter),
1756
                        $current_question,
1757
                        true,
1758
                        $count_of_questions_inside_media,
1759
                        $last_question_in_media
1760
                    );
1761
                    $letterCounter++;
1762
                    $counter++;
1763
                }
1764
            } else {
1765
                render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question);
1766
            }
1767
        } else {
1768
            // Normal question rendering
1769
            render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question);
1770
        }
1771
1772
        $i++;
1773
        // for sequential exercises
1774
        if ($objExercise->type == ONE_PER_PAGE) {
1775
            // quits the loop
1776
            break;
1777
        }
1778
    }
1779
1780
    if ($objExercise->type == ALL_ON_ONE_PAGE) {
1781
        $exerciseActions = $objExercise->show_button(
1782
            $questionId,
1783
            $current_question,
1784
            [],
1785
            '',
1786
            [],
1787
            true,
1788
            $learnpath_id,
1789
            $learnpath_item_id,
1790
            $learnpath_item_view_id
1791
        );
1792
        echo Display::div($exerciseActions, ['class' => 'exercise_actions']);
1793
        echo '<br>';
1794
    }
1795
}
1796
1797
function render_question(
1798
    $objExercise,
1799
    $questionId,
1800
    $attempt_list,
1801
    $remind_list,
1802
    $i,
1803
    $current_question,
1804
    $question_in_media = [],
1805
    $last_question_in_media = false
1806
) {
1807
    global $origin;
1808
    $user_choice = $attempt_list[$questionId];
1809
1810
    $remind_highlight = null;
1811
1812
    //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
1813
    if ($objExercise->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
1814
        $remind_highlight = 'no_remind_highlight';
1815
    }
1816
1817
1818
    $attributes = array('id' => 'remind_list['.$questionId.']');
1819
1820
    $is_remind_on = false;
1821
    if (in_array($questionId, $remind_list)) {
1822
        $is_remind_on = true;
1823
        $attributes['checked'] = 1;
1824
        $remind_highlight = ' remind_highlight ';
1825
    }
1826
1827
    //Showing the question
1828
1829
    $exercise_actions = null;
1830
1831
    echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
1832
1833
    // Shows the question + possible answers
1834
    showQuestion($questionId, false, $origin, $i, true, false, $user_choice, false);
1835
1836
    // Button to save and continue
1837
    switch ($objExercise->type) {
1838
        case ONE_PER_PAGE:
1839
            $exercise_actions .= $objExercise->show_button($questionId, $current_question);
1840
            break;
1841
        case ALL_ON_ONE_PAGE :
1842
            $button = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\'); ">'.get_lang('SaveForNow').'</a>';
1843
            $button .= '<span id="save_for_now_'.$questionId.'"></span>&nbsp;';
1844
            $exercise_actions .= Display::div($button, array('class' => 'exercise_save_now_button'));
1845
            break;
1846
    }
1847
1848
    if (!empty($questions_in_media)) {
1849
        /*$button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\'); ">'.get_lang('SaveForNow').'</a>';
1850
        $button .= '<span id="save_for_now_'.$questionId.'"></span>&nbsp;';
1851
        $exercise_actions  = Display::div($button, array('class'=>'exercise_save_now_button'));
1852
        $exercise_actions .= $objExercise->show_button($questionId, $current_question);*/
1853
        $count_of_questions_inside_media = count($questions_in_media);
1854
        if ($count_of_questions_inside_media > 1) {
1855
            $button = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false); ">'.get_lang('SaveForNow').'</a>';
1856
            $button .= '<span id="save_for_now_'.$questionId.'"></span>&nbsp;';
1857
            $exercise_actions = Display::div($button, array('class' => 'exercise_save_now_button'));
1858
        }
1859
1860
        if ($last_question_in_media) {
1861
            $exercise_actions = $objExercise->show_button($questionId, $current_question, $questions_in_media);
1862
        }
1863
    }
1864
1865
    //Checkbox review answers
1866
    if ($objExercise->review_answers) {
1867
        $remind_question_div = Display::tag('label',
1868
            Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'),
1869
            array('class' => 'checkbox', 'for' => 'remind_list['.$questionId.']'));
1870
        $exercise_actions .= Display::div($remind_question_div, array('class' => 'exercise_save_now_button'));
1871
    }
1872
1873
    echo Display::div($exercise_actions, array('class' => 'form-actions'));
1874
    echo '</div>';
1875
}
1876
1877
if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
1878
    // So we are not in learnpath tool
1879
    echo '</div>'; //End glossary div
1880
}
1881
Display::display_footer();
1882