Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

main/exercise/upload_exercise.php (1 issue)

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'), 'Exercises');
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
        FILL_IN_BLANKS => get_lang('FillBlanks'),
89
        MATCHING => get_lang('Matching'),
90
        FREE_ANSWER => get_lang('FreeAnswer'),
91
        GLOBAL_MULTIPLE_ANSWER => get_lang('GlobalMultipleAnswer'),
92
    ];
93
94
    $table->setHeaderContents(0, 0, get_lang('QuestionType'));
95
    $table->setHeaderContents(0, 1, '#');
96
97
    $row = 1;
98
    foreach ($tableList as $key => $label) {
99
        $table->setCellContents($row, 0, $label);
100
        $table->setCellContents($row, 1, $key);
101
        $row++;
102
    }
103
    $table = $table->toHtml();
104
105
    $form->addElement('label', get_lang('QuestionType'), $table);
106
    $form->addElement(
107
        'checkbox',
108
        'user_custom_score',
109
        null,
110
        get_lang('UseCustomScoreForAllQuestions'),
111
        ['id' => 'user_custom_score']
112
    );
113
    $form->addElement('html', '<div id="options" class="hidden">');
114
    $form->addElement('text', 'correct_score', get_lang('CorrectScore'));
115
    $form->addElement('text', 'incorrect_score', get_lang('IncorrectScore'));
116
    $form->addElement('html', '</div>');
117
118
    $form->addRule('user_upload_quiz', get_lang('ThisFieldIsRequired'), 'required');
119
120
    $form->addProgress();
121
    $form->addButtonUpload(get_lang('Upload'), 'submit_upload_quiz');
122
    $form->display();
123
}
124
125
/**
126
 * Handles a given Excel spreadsheets as in the template provided.
127
 */
128
function lp_upload_quiz_action_handling()
129
{
130
    if (!isset($_POST['submit_upload_quiz'])) {
131
        return;
132
    }
133
134
    $_course = api_get_course_info();
135
136
    if (empty($_course)) {
137
        return false;
138
    }
139
140
    $courseId = $_course['real_id'];
141
    // Get the extension of the document.
142
    $path_info = pathinfo($_FILES['user_upload_quiz']['name']);
143
144
    // Check if the document is an Excel document
145
    if (!in_array($path_info['extension'], ['xls', 'xlsx'])) {
146
        return;
147
    }
148
149
    // Variables
150
    $numberQuestions = 0;
151
    $question = [];
152
    $scoreList = [];
153
    $feedbackTrueList = [];
154
    $feedbackFalseList = [];
155
    $questionDescriptionList = [];
156
    $noNegativeScoreList = [];
157
    $questionTypeList = [];
158
    $answerList = [];
159
    $quizTitle = '';
160
    $objPHPExcel = PHPExcel_IOFactory::load($_FILES['user_upload_quiz']['tmp_name']);
161
    $objPHPExcel->setActiveSheetIndex(0);
162
    $worksheet = $objPHPExcel->getActiveSheet();
163
    $highestRow = $worksheet->getHighestRow(); // e.g. 10
164
    //$highestColumn = $worksheet->getHighestColumn(); // e.g 'F'
165
166
    $correctScore = isset($_POST['correct_score']) ? $_POST['correct_score'] : null;
167
    $incorrectScore = isset($_POST['incorrect_score']) ? $_POST['incorrect_score'] : null;
168
    $useCustomScore = isset($_POST['user_custom_score']) ? true : false;
169
170
    for ($row = 1; $row <= $highestRow; $row++) {
171
        $cellTitleInfo = $worksheet->getCellByColumnAndRow(0, $row);
172
        $cellDataInfo = $worksheet->getCellByColumnAndRow(1, $row);
173
        $cellScoreInfo = $worksheet->getCellByColumnAndRow(2, $row);
174
        $title = $cellTitleInfo->getValue();
175
176
        switch ($title) {
177
            case 'Quiz':
178
                $quizTitle = $cellDataInfo->getValue();
179
                break;
180
            case 'Question':
181
                $question[] = $cellDataInfo->getValue();
182
                // Search cell with Answer title
183
                $answerRow = $row;
184
                $continue = true;
185
                $answerIndex = 0;
186
                while ($continue) {
187
                    $answerRow++;
188
                    $answerInfoTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
189
                    $answerInfoData = $worksheet->getCellByColumnAndRow(1, $answerRow);
190
                    $answerInfoExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
191
                    $answerInfoTitle = $answerInfoTitle->getValue();
192
                    if (strpos($answerInfoTitle, 'Answer') !== false) {
193
                        $answerList[$numberQuestions][$answerIndex]['data'] = $answerInfoData->getValue();
194
                        $answerList[$numberQuestions][$answerIndex]['extra'] = $answerInfoExtra->getValue();
195
                    } else {
196
                        $continue = false;
197
                    }
198
                    $answerIndex++;
199
200
                    // To avoid loops
201
                    if ($answerIndex > 60) {
202
                        $continue = false;
203
                    }
204
                }
205
206
                // Search cell with question type
207
                $answerRow = $row;
208
                $continue = true;
209
                $questionTypeIndex = 0;
210
                while ($continue) {
211
                    $answerRow++;
212
                    $questionTypeTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
213
                    $questionTypeExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
214
                    $title = $questionTypeTitle->getValue();
215
                    if ($title === 'QuestionType') {
216
                        $questionTypeList[$numberQuestions] = $questionTypeExtra->getValue();
217
                        $continue = false;
218
                    }
219
                    if ($title === 'Question') {
220
                        $continue = false;
221
                    }
222
                    // To avoid loops
223
                    if ($questionTypeIndex > 60) {
224
                        $continue = false;
225
                    }
226
                    $questionTypeIndex++;
227
                }
228
229
                // Detect answers
230
                $numberQuestions++;
231
                break;
232
            case 'Score':
233
                $scoreList[] = $cellScoreInfo->getValue();
234
                break;
235
            case 'NoNegativeScore':
236
                $noNegativeScoreList[] = $cellScoreInfo->getValue();
237
                break;
238
            case 'Category':
239
                $categoryList[] = $cellDataInfo->getValue();
240
                break;
241
            case 'FeedbackTrue':
242
                $feedbackTrueList[] = $cellDataInfo->getValue();
243
                break;
244
            case 'FeedbackFalse':
245
                $feedbackFalseList[] = $cellDataInfo->getValue();
246
                break;
247
            case 'EnrichQuestion':
248
                $questionDescriptionList[] = $cellDataInfo->getValue();
249
                break;
250
        }
251
    }
252
253
    $propagateNegative = 0;
254
    if ($useCustomScore && !empty($incorrectScore)) {
255
        if ($incorrectScore < 0) {
256
            $propagateNegative = 1;
257
        }
258
    }
259
260
    $url = api_get_path(WEB_CODE_PATH).'exercise/upload_exercise.php?'.api_get_cidreq();
261
262
    if (empty($quizTitle)) {
263
        Display::addFlash(Display::return_message('ErrorImportingFile'), 'warning');
264
        api_location($url);
265
    }
266
267
    // Variables
268
    $type = 2;
269
    $random = $active = $results = $max_attempt = $expired_time = 0;
270
    // Make sure feedback is enabled (3 to disable), otherwise the fields
271
    // added to the XLS are not shown, which is confusing
272
    $feedback = 0;
273
274
    // Quiz object
275
    $exercise = new Exercise();
276
    $exercise->updateTitle($quizTitle);
277
    $exercise->updateExpiredTime($expired_time);
278
    $exercise->updateType($type);
279
    $exercise->setRandom($random);
280
    $exercise->active = $active;
281
    $exercise->updateResultsDisabled($results);
282
    $exercise->updateAttempts($max_attempt);
283
    $exercise->updateFeedbackType($feedback);
284
    $exercise->updatePropagateNegative($propagateNegative);
285
    $quiz_id = $exercise->save();
286
287
    if ($quiz_id) {
288
        // Import questions.
289
        for ($i = 0; $i < $numberQuestions; $i++) {
290
            // Question name
291
            $questionTitle = $question[$i];
292
            $myAnswerList = isset($answerList[$i]) ? $answerList[$i] : [];
293
            $description = isset($questionDescriptionList[$i]) ? $questionDescriptionList[$i] : '';
294
            $categoryId = null;
295
            if (isset($categoryList[$i]) && !empty($categoryList[$i])) {
296
                $categoryName = $categoryList[$i];
297
                $categoryId = TestCategory::get_category_id_for_title($categoryName, $courseId);
298
                if (empty($categoryId)) {
299
                    $category = new TestCategory();
300
                    $category->name = $categoryName;
301
                    $categoryId = $category->save();
302
                }
303
            }
304
305
            $question_description_text = '<p></p>';
306
            if (!empty($description)) {
307
                // Question description.
308
                $question_description_text = "<p>$description</p>";
309
            }
310
311
            // Unique answers are the only question types available for now
312
            // through xls-format import
313
            $question_id = null;
314
            if (isset($questionTypeList[$i]) && $questionTypeList[$i] != '') {
315
                $detectQuestionType = (int) $questionTypeList[$i];
316
            } else {
317
                $detectQuestionType = detectQuestionType($myAnswerList);
318
            }
319
320
            /** @var Question $answer */
321
            switch ($detectQuestionType) {
322
                case FREE_ANSWER:
323
                    $answer = new FreeAnswer();
324
                    break;
325
                case GLOBAL_MULTIPLE_ANSWER:
326
                    $answer = new GlobalMultipleAnswer();
327
                    break;
328
                case MULTIPLE_ANSWER:
329
                    $answer = new MultipleAnswer();
330
                    break;
331
                case FILL_IN_BLANKS:
332
                    $answer = new FillBlanks();
333
                    $question_description_text = '';
334
                    break;
335
                case MATCHING:
336
                    $answer = new Matching();
337
                    break;
338
                case UNIQUE_ANSWER:
339
                default:
340
                    $answer = new UniqueAnswer();
341
                    break;
342
            }
343
344
            if ($questionTitle != '') {
345
                $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

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