lp_upload_quiz_actions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use ChamiloSession as Session;
6
7
/**
8
 * Upload quiz: This script shows the upload quiz feature.
9
 */
10
11
// setting the help
12
$help_content = 'exercise_upload';
13
require_once __DIR__.'/../inc/global.inc.php';
14
15
api_protect_course_script(true);
16
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
17
$debug = false;
18
$origin = api_get_origin();
19
if (!$is_allowed_to_edit) {
20
    api_not_allowed(true);
21
}
22
23
$this_section = SECTION_COURSES;
24
$htmlHeadXtra[] = "<script>
25
$(function () {
26
    $('#user_custom_score').on('change', function () {
27
        if ($('#user_custom_score[type=\"checkbox\"]').prop('checked')) {
28
            $('#options').removeClass('hidden')
29
        } else {
30
            $('#options').addClass('hidden')
31
        }
32
    });
33
});
34
</script>";
35
36
// Action handling
37
lp_upload_quiz_action_handling();
38
39
$interbreadcrumb[] = [
40
    'url' => 'exercise.php?'.api_get_cidreq(),
41
    'name' => get_lang('Exercises'),
42
];
43
44
Display::display_header(get_lang('ImportExcelQuiz'), 'Exercise');
45
46
echo '<div class="actions">';
47
echo lp_upload_quiz_actions();
48
echo '</div>';
49
50
lp_upload_quiz_main();
51
52
function lp_upload_quiz_actions()
53
{
54
    $return = '<a href="exercise.php?'.api_get_cidreq().'">'.
55
        Display::return_icon(
56
            'back.png',
57
            get_lang('BackToExercisesList'),
58
            '',
59
            ICON_SIZE_MEDIUM
60
        ).'</a>';
61
62
    return $return;
63
}
64
65
function lp_upload_quiz_main()
66
{
67
    $lp_id = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : null;
68
69
    $form = new FormValidator(
70
        'upload',
71
        'POST',
72
        api_get_self().'?'.api_get_cidreq().'&lp_id='.$lp_id,
73
        '',
74
        ['enctype' => 'multipart/form-data']
75
    );
76
    $form->addElement('header', get_lang('ImportExcelQuiz'));
77
    $form->addElement('file', 'user_upload_quiz', get_lang('FileUpload'));
78
79
    $link = '<a href="../exercise/quiz_template.xls">'.
80
        Display::return_icon('export_excel.png', get_lang('DownloadExcelTemplate')).get_lang('DownloadExcelTemplate').'</a>';
81
    $form->addElement('label', '', $link);
82
83
    $table = new HTML_Table(['class' => 'table']);
84
85
    $tableList = [
86
        UNIQUE_ANSWER => get_lang('UniqueSelect'),
87
        MULTIPLE_ANSWER => get_lang('MultipleSelect'),
88
        MULTIPLE_ANSWER_DROPDOWN => get_lang('MultipleAnswerDropdown'),
89
        MULTIPLE_ANSWER_DROPDOWN_COMBINATION => get_lang('MultipleAnswerDropdownCombination'),
90
        FILL_IN_BLANKS => get_lang('FillBlanks'),
91
        FILL_IN_BLANKS_COMBINATION => get_lang('FillBlanksCombination'),
92
        MATCHING => get_lang('Matching'),
93
        FREE_ANSWER => get_lang('FreeAnswer'),
94
        GLOBAL_MULTIPLE_ANSWER => get_lang('GlobalMultipleAnswer'),
95
    ];
96
97
    $table->setHeaderContents(0, 0, get_lang('QuestionType'));
98
    $table->setHeaderContents(0, 1, '#');
99
100
    $row = 1;
101
    foreach ($tableList as $key => $label) {
102
        $table->setCellContents($row, 0, $label);
103
        $table->setCellContents($row, 1, $key);
104
        $row++;
105
    }
106
    $table = $table->toHtml();
107
108
    $form->addElement('label', get_lang('QuestionType'), $table);
109
    $form->addElement(
110
        'checkbox',
111
        'user_custom_score',
112
        null,
113
        get_lang('UseCustomScoreForAllQuestions'),
114
        ['id' => 'user_custom_score']
115
    );
116
    $form->addElement('html', '<div id="options" class="hidden">');
117
    $form->addElement('text', 'correct_score', get_lang('CorrectScore'));
118
    $form->addElement('text', 'incorrect_score', get_lang('IncorrectScore'));
119
    $form->addElement('html', '</div>');
120
121
    $form->addRule('user_upload_quiz', get_lang('ThisFieldIsRequired'), 'required');
122
123
    $form->addProgress();
124
    $form->addButtonUpload(get_lang('Upload'), 'submit_upload_quiz');
125
    $form->display();
126
}
127
128
/**
129
 * Handles a given Excel spreadsheets as in the template provided.
130
 */
131
function lp_upload_quiz_action_handling()
132
{
133
    if (!isset($_POST['submit_upload_quiz'])) {
134
        return;
135
    }
136
137
    $_course = api_get_course_info();
138
139
    if (empty($_course)) {
140
        return false;
141
    }
142
143
    $courseId = $_course['real_id'];
144
    // Get the extension of the document.
145
    $path_info = pathinfo($_FILES['user_upload_quiz']['name']);
146
147
    // Check if the document is an Excel document
148
    if (!in_array($path_info['extension'], ['xls', 'xlsx'])) {
149
        return;
150
    }
151
152
    // Variables
153
    $numberQuestions = 0;
154
    $question = [];
155
    $scoreList = [];
156
    $feedbackTrueList = [];
157
    $feedbackFalseList = [];
158
    $questionDescriptionList = [];
159
    $noNegativeScoreList = [];
160
    $questionTypeList = [];
161
    $answerList = [];
162
    $quizTitle = '';
163
    $objPHPExcel = PHPExcel_IOFactory::load($_FILES['user_upload_quiz']['tmp_name']);
164
    $objPHPExcel->setActiveSheetIndex(0);
165
    $worksheet = $objPHPExcel->getActiveSheet();
166
    $highestRow = $worksheet->getHighestRow(); // e.g. 10
167
    //$highestColumn = $worksheet->getHighestColumn(); // e.g 'F'
168
169
    $correctScore = isset($_POST['correct_score']) ? $_POST['correct_score'] : null;
170
    $incorrectScore = isset($_POST['incorrect_score']) ? $_POST['incorrect_score'] : null;
171
    $useCustomScore = isset($_POST['user_custom_score']) ? true : false;
172
173
    for ($row = 1; $row <= $highestRow; $row++) {
174
        $cellTitleInfo = $worksheet->getCellByColumnAndRow(0, $row);
175
        $cellDataInfo = $worksheet->getCellByColumnAndRow(1, $row);
176
        $cellScoreInfo = $worksheet->getCellByColumnAndRow(2, $row);
177
        $title = $cellTitleInfo->getValue();
178
179
        switch ($title) {
180
            case 'Quiz':
181
                $quizTitle = $cellDataInfo->getValue();
182
                break;
183
            case 'Question':
184
                $question[] = $cellDataInfo->getValue();
185
                // Search cell with Answer title
186
                $answerRow = $row;
187
                $continue = true;
188
                $answerIndex = 0;
189
                while ($continue) {
190
                    $answerRow++;
191
                    $answerInfoTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
192
                    $answerInfoData = $worksheet->getCellByColumnAndRow(1, $answerRow);
193
                    $answerInfoExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
194
                    $answerInfoTitle = $answerInfoTitle->getValue();
195
                    if (strpos($answerInfoTitle, 'Answer') !== false) {
196
                        $answerList[$numberQuestions][$answerIndex]['data'] = $answerInfoData->getValue();
197
                        $answerList[$numberQuestions][$answerIndex]['extra'] = $answerInfoExtra->getValue();
198
                    } else {
199
                        $continue = false;
200
                    }
201
                    $answerIndex++;
202
203
                    // To avoid loops
204
                    if ($answerIndex > 60) {
205
                        $continue = false;
206
                    }
207
                }
208
209
                // Search cell with question type
210
                $answerRow = $row;
211
                $continue = true;
212
                $questionTypeIndex = 0;
213
                while ($continue) {
214
                    $answerRow++;
215
                    $questionTypeTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
216
                    $questionTypeExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
217
                    $title = $questionTypeTitle->getValue();
218
                    if ($title === 'QuestionType') {
219
                        $questionTypeList[$numberQuestions] = $questionTypeExtra->getValue();
220
                        $continue = false;
221
                    }
222
                    if ($title === 'Question') {
223
                        $continue = false;
224
                    }
225
                    // To avoid loops
226
                    if ($questionTypeIndex > 60) {
227
                        $continue = false;
228
                    }
229
                    $questionTypeIndex++;
230
                }
231
232
                // Detect answers
233
                $numberQuestions++;
234
                break;
235
            case 'Score':
236
                $scoreList[] = $cellScoreInfo->getValue();
237
                break;
238
            case 'NoNegativeScore':
239
                $noNegativeScoreList[] = $cellScoreInfo->getValue();
240
                break;
241
            case 'Category':
242
                $categoryList[] = $cellDataInfo->getValue();
243
                break;
244
            case 'FeedbackTrue':
245
                $feedbackTrueList[] = $cellDataInfo->getValue();
246
                break;
247
            case 'FeedbackFalse':
248
                $feedbackFalseList[] = $cellDataInfo->getValue();
249
                break;
250
            case 'EnrichQuestion':
251
                $questionDescriptionList[] = $cellDataInfo->getValue();
252
                break;
253
        }
254
    }
255
256
    $propagateNegative = 0;
257
    if ($useCustomScore && !empty($incorrectScore)) {
258
        if ($incorrectScore < 0) {
259
            $propagateNegative = 1;
260
        }
261
    }
262
263
    $url = api_get_path(WEB_CODE_PATH).'exercise/upload_exercise.php?'.api_get_cidreq();
264
265
    if (empty($quizTitle)) {
266
        Display::addFlash(Display::return_message('ErrorImportingFile'), 'warning');
267
        api_location($url);
268
    }
269
270
    // Variables
271
    $type = 2;
272
    $random = $active = $results = $max_attempt = $expired_time = 0;
273
    // Make sure feedback is enabled (3 to disable), otherwise the fields
274
    // added to the XLS are not shown, which is confusing
275
    $feedback = 0;
276
277
    // Quiz object
278
    $exercise = new Exercise();
279
    $exercise->updateTitle($quizTitle);
280
    $exercise->updateExpiredTime($expired_time);
281
    $exercise->updateType($type);
282
    $exercise->setRandom($random);
283
    $exercise->active = $active;
284
    $exercise->updateResultsDisabled($results);
285
    $exercise->updateAttempts($max_attempt);
286
    $exercise->updateFeedbackType($feedback);
287
    $exercise->updatePropagateNegative($propagateNegative);
288
    $quiz_id = $exercise->save();
289
290
    if ($quiz_id) {
291
        // Import questions.
292
        for ($i = 0; $i < $numberQuestions; $i++) {
293
            // Question name
294
            $questionTitle = $question[$i];
295
            $myAnswerList = isset($answerList[$i]) ? $answerList[$i] : [];
296
            $description = isset($questionDescriptionList[$i]) ? $questionDescriptionList[$i] : '';
297
            $categoryId = null;
298
            if (isset($categoryList[$i]) && !empty($categoryList[$i])) {
299
                $categoryName = $categoryList[$i];
300
                $categoryId = TestCategory::get_category_id_for_title($categoryName, $courseId);
301
                if (empty($categoryId)) {
302
                    $category = new TestCategory();
303
                    $category->name = $categoryName;
304
                    $categoryId = $category->save();
305
                }
306
            }
307
308
            $question_description_text = '<p></p>';
309
            if (!empty($description)) {
310
                // Question description.
311
                $question_description_text = "<p>$description</p>";
312
            }
313
314
            // Unique answers are the only question types available for now
315
            // through xls-format import
316
            $question_id = null;
317
            if (isset($questionTypeList[$i]) && $questionTypeList[$i] != '') {
318
                $detectQuestionType = (int) $questionTypeList[$i];
319
            } else {
320
                $detectQuestionType = detectQuestionType($myAnswerList);
321
            }
322
323
            /** @var Question $answer */
324
            switch ($detectQuestionType) {
325
                case FREE_ANSWER:
326
                    $answer = new FreeAnswer();
327
                    break;
328
                case GLOBAL_MULTIPLE_ANSWER:
329
                    $answer = new GlobalMultipleAnswer();
330
                    break;
331
                case MULTIPLE_ANSWER:
332
                    $answer = new MultipleAnswer();
333
                    break;
334
                case MULTIPLE_ANSWER_DROPDOWN:
335
                    $answer = new MultipleAnswerDropdown();
336
                    break;
337
                case MULTIPLE_ANSWER_DROPDOWN_COMBINATION:
338
                    $answer = new MultipleAnswerDropdownCombination();
339
                    break;
340
                case FILL_IN_BLANKS:
341
                case FILL_IN_BLANKS_COMBINATION:
342
                    $answer = new FillBlanks();
343
                    $question_description_text = '';
344
                    break;
345
                case MATCHING:
346
                    $answer = new Matching();
347
                    break;
348
                case UNIQUE_ANSWER:
349
                default:
350
                    $answer = new UniqueAnswer();
351
                    break;
352
            }
353
354
            if ($questionTitle != '') {
355
                $question_id = $answer->create_question(
0 ignored issues
show
Deprecated Code introduced by
The function Question::create_question() has been deprecated. ( Ignorable by Annotation )

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

355
                $question_id = /** @scrutinizer ignore-deprecated */ $answer->create_question(
Loading history...
356
                    $quiz_id,
357
                    $questionTitle,
358
                    $question_description_text,
359
                    0, // max score
360
                    $answer->type
361
                );
362
363
                if (!empty($categoryId)) {
364
                    TestCategory::addCategoryToQuestion(
365
                        $categoryId,
366
                        $question_id,
367
                        $courseId
368
                    );
369
                }
370
            }
371
            switch ($detectQuestionType) {
372
                case GLOBAL_MULTIPLE_ANSWER:
373
                case MULTIPLE_ANSWER_DROPDOWN:
374
                case MULTIPLE_ANSWER_DROPDOWN_COMBINATION:
375
                case MULTIPLE_ANSWER:
376
                case UNIQUE_ANSWER:
377
                    $total = 0;
378
                    if (is_array($myAnswerList) && !empty($myAnswerList) && !empty($question_id)) {
379
                        $id = 1;
380
                        $objAnswer = new Answer($question_id, $courseId);
381
                        $globalScore = isset($scoreList[$i]) ? $scoreList[$i] : null;
382
383
                        // Calculate the number of correct answers to divide the
384
                        // score between them when importing from CSV
385
                        $numberRightAnswers = 0;
386
                        foreach ($myAnswerList as $answer_data) {
387
                            if (strtolower($answer_data['extra']) == 'x') {
388
                                $numberRightAnswers++;
389
                            }
390
                        }
391
392
                        foreach ($myAnswerList as $answer_data) {
393
                            $answerValue = $answer_data['data'];
394
                            $correct = 0;
395
                            $score = 0;
396
                            if (strtolower($answer_data['extra']) == 'x') {
397
                                $correct = 1;
398
                                $score = isset($scoreList[$i]) ? $scoreList[$i] : 0;
399
                                $comment = isset($feedbackTrueList[$i]) ? $feedbackTrueList[$i] : '';
400
                            } else {
401
                                $comment = isset($feedbackFalseList[$i]) ? $feedbackFalseList[$i] : '';
402
                                $floatVal = (float) $answer_data['extra'];
403
                                if (is_numeric($floatVal)) {
404
                                    $score = $answer_data['extra'];
405
                                }
406
                            }
407
408
                            if ($useCustomScore) {
409
                                if ($correct) {
410
                                    $score = $correctScore;
411
                                } else {
412
                                    $score = $incorrectScore;
413
                                }
414
                            }
415
416
                            // Fixing scores:
417
                            switch ($detectQuestionType) {
418
                                case GLOBAL_MULTIPLE_ANSWER:
419
                                    if ($correct) {
420
                                        $score = abs($scoreList[$i]);
421
                                    } else {
422
                                        if (isset($noNegativeScoreList[$i]) && $noNegativeScoreList[$i] == 'x') {
423
                                            $score = 0;
424
                                        } else {
425
                                            $score = -abs($scoreList[$i]);
426
                                        }
427
                                    }
428
                                    $score /= $numberRightAnswers;
429
                                    break;
430
                                case UNIQUE_ANSWER:
431
                                    break;
432
                                case MULTIPLE_ANSWER:
433
                                    if (!$correct) {
434
                                        //$total = $total - $score;
435
                                    }
436
                                    break;
437
                                case MULTIPLE_ANSWER_DROPDOWN_COMBINATION:
438
                                    $score = 0;
439
                                    break;
440
                            }
441
442
                            $objAnswer->createAnswer(
443
                                $answerValue,
444
                                $correct,
445
                                $comment,
446
                                $score,
447
                                $id
448
                            );
449
                            if ($correct) {
450
                                //only add the item marked as correct ( x )
451
                                $total += (float) $score;
452
                            }
453
                            $id++;
454
                        }
455
456
                        $objAnswer->save();
457
458
                        $questionObj = Question::read(
459
                            $question_id,
460
                            $_course
461
                        );
462
463
                        if ($questionObj) {
464
                            switch ($detectQuestionType) {
465
                                case GLOBAL_MULTIPLE_ANSWER:
466
                                case MULTIPLE_ANSWER_DROPDOWN_COMBINATION:
467
                                    $questionObj->updateWeighting($globalScore);
468
                                    break;
469
                                case UNIQUE_ANSWER:
470
                                case MULTIPLE_ANSWER:
471
                                default:
472
                                    $questionObj->updateWeighting($total);
473
                                    break;
474
                            }
475
                            $questionObj->save($exercise);
476
                        }
477
                    }
478
                    break;
479
                case FREE_ANSWER:
480
                    $globalScore = isset($scoreList[$i]) ? $scoreList[$i] : null;
481
                    $questionObj = Question::read($question_id, $_course);
482
                    if ($questionObj) {
483
                        $questionObj->updateWeighting($globalScore);
484
                        $questionObj->save($exercise);
485
                    }
486
                    break;
487
                case FILL_IN_BLANKS:
488
                case FILL_IN_BLANKS_COMBINATION:
489
                    $fillInScoreList = [];
490
                    $size = [];
491
                    $globalScore = 0;
492
                    foreach ($myAnswerList as $data) {
493
                        $score = isset($data['extra']) ? $data['extra'] : 0;
494
                        $globalScore += $score;
495
                        $fillInScoreList[] = $score;
496
                        $size[] = 200;
497
                    }
498
499
                    $scoreToString = implode(',', $fillInScoreList);
500
                    $sizeToString = implode(',', $size);
501
502
                    //<p>Texte long avec les [mots] à [remplir] mis entre [crochets]</p>::10,10,10:200.36363999999998,200,200:0@'
503
                    $answerValue = $description.'::'.$scoreToString.':'.$sizeToString.':0@';
504
                    $objAnswer = new Answer($question_id, $courseId);
505
                    $objAnswer->createAnswer(
506
                        $answerValue,
507
                        '', //$correct,
508
                        '', //$comment,
509
                        $globalScore,
510
                        1
511
                    );
512
513
                    $objAnswer->save();
514
515
                    $questionObj = Question::read($question_id, $_course);
516
                    if ($questionObj) {
517
                        $questionObj->updateWeighting($globalScore);
518
                        $questionObj->save($exercise);
519
                    }
520
                    break;
521
                case MATCHING:
522
                    $globalScore = isset($scoreList[$i]) ? $scoreList[$i] : null;
523
                    $position = 1;
524
525
                    $objAnswer = new Answer($question_id, $courseId);
526
                    foreach ($myAnswerList as $data) {
527
                        $option = isset($data['extra']) ? $data['extra'] : '';
528
                        $objAnswer->createAnswer($option, 0, '', 0, $position);
529
                        $position++;
530
                    }
531
532
                    $counter = 1;
533
                    foreach ($myAnswerList as $data) {
534
                        $value = isset($data['data']) ? $data['data'] : '';
535
                        $position++;
536
                        $objAnswer->createAnswer(
537
                            $value,
538
                            $counter,
539
                            ' ',
540
                            $globalScore,
541
                            $position
542
                        );
543
                        $counter++;
544
                    }
545
                    $objAnswer->save();
546
                    $questionObj = Question::read($question_id, $_course);
547
                    if ($questionObj) {
548
                        $questionObj->updateWeighting($globalScore);
549
                        $questionObj->save($exercise);
550
                    }
551
                    break;
552
            }
553
        }
554
    }
555
556
    $lpObject = Session::read('lpobject');
557
558
    if (!empty($lpObject)) {
559
        /** @var learnpath $oLP */
560
        $oLP = UnserializeApi::unserialize('lp', $lpObject);
561
        if (is_object($oLP)) {
562
            if ((empty($oLP->cc)) || $oLP->cc != api_get_course_id()) {
563
                $oLP = null;
564
                Session::erase('oLP');
565
                Session::erase('lpobject');
566
            } else {
567
                Session::write('oLP', $oLP);
568
            }
569
        }
570
    }
571
    Display::addFlash(Display::return_message(get_lang('FileImported')));
572
573
    if (isset($_SESSION['oLP']) && isset($_GET['lp_id'])) {
574
        $previous = $_SESSION['oLP']->select_previous_item_id();
575
        $parent = 0;
576
        // Add a Quiz as Lp Item
577
        $_SESSION['oLP']->add_item(
578
            $parent,
579
            $previous,
580
            TOOL_QUIZ,
581
            $quiz_id,
582
            $quizTitle,
583
            ''
584
        );
585
        // Redirect to home page for add more content
586
        header('Location: ../lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.intval($_GET['lp_id']));
587
        exit;
588
    } else {
589
        $exerciseUrl = api_get_path(WEB_CODE_PATH).
590
            'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$quiz_id.'&session_id='.api_get_session_id();
591
        api_location($exerciseUrl);
592
    }
593
}
594
595
/**
596
 * @param array $answers_data
597
 *
598
 * @return int
599
 */
600
function detectQuestionType($answers_data)
601
{
602
    $correct = 0;
603
    $isNumeric = false;
604
605
    if (empty($answers_data)) {
606
        return FREE_ANSWER;
607
    }
608
609
    foreach ($answers_data as $answer_data) {
610
        if (strtolower($answer_data['extra']) == 'x') {
611
            $correct++;
612
        } else {
613
            if (is_numeric($answer_data['extra'])) {
614
                $isNumeric = true;
615
            }
616
        }
617
    }
618
619
    if ($correct == 1) {
620
        $type = UNIQUE_ANSWER;
621
    } else {
622
        if ($correct > 1) {
623
            $type = MULTIPLE_ANSWER;
624
        } else {
625
            $type = FREE_ANSWER;
626
        }
627
    }
628
629
    if ($type == MULTIPLE_ANSWER) {
630
        if ($isNumeric) {
631
            $type = MULTIPLE_ANSWER;
632
        } else {
633
            $type = GLOBAL_MULTIPLE_ANSWER;
634
        }
635
    }
636
637
    return $type;
638
}
639
640
if ($origin != 'learnpath') {
641
    //so we are not in learnpath tool
642
    Display::display_footer();
643
}
644