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

main/exercise/fill_blanks.class.php (2 issues)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 *  Class FillBlanks.
7
 *
8
 * @author Eric Marguin
9
 * @author Julio Montoya multiple fill in blank option added.
10
 */
11
class FillBlanks extends Question
12
{
13
    const FILL_THE_BLANK_STANDARD = 0;
14
    const FILL_THE_BLANK_MENU = 1;
15
    const FILL_THE_BLANK_SEVERAL_ANSWER = 2;
16
17
    public $typePicture = 'fill_in_blanks.png';
18
    public $explanationLangVar = 'FillBlanks';
19
20
    /**
21
     * Constructor.
22
     */
23
    public function __construct()
24
    {
25
        parent::__construct();
26
        $this->type = FILL_IN_BLANKS;
27
        $this->isContent = $this->getIsContent();
28
    }
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function createAnswersForm($form)
34
    {
35
        $defaults = [];
36
        $defaults['answer'] = get_lang('DefaultTextInBlanks');
37
        $defaults['select_separator'] = 0;
38
        $blankSeparatorNumber = 0;
39
        if (!empty($this->iid)) {
40
            $objectAnswer = new Answer($this->iid);
41
            $answer = $objectAnswer->selectAnswer(1);
42
            $listAnswersInfo = self::getAnswerInfo($answer);
43
            $defaults['multiple_answer'] = 0;
44
            if ($listAnswersInfo['switchable']) {
45
                $defaults['multiple_answer'] = 1;
46
            }
47
            // Take the complete string except after the last '::'
48
            $defaults['answer'] = $listAnswersInfo['text'];
49
            $defaults['select_separator'] = $listAnswersInfo['blank_separator_number'];
50
            $blankSeparatorNumber = $listAnswersInfo['blank_separator_number'];
51
        }
52
53
        $blankSeparatorStart = self::getStartSeparator($blankSeparatorNumber);
54
        $blankSeparatorEnd = self::getEndSeparator($blankSeparatorNumber);
55
        $setWeightAndSize = '';
56
        if (isset($listAnswersInfo) && count($listAnswersInfo['weighting']) > 0) {
57
            foreach ($listAnswersInfo['weighting'] as $i => $weighting) {
58
                $setWeightAndSize .= 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";';
59
            }
60
            foreach ($listAnswersInfo['input_size'] as $i => $sizeOfInput) {
61
                $setWeightAndSize .= 'document.getElementById("sizeofinput['.$i.']").value = "'.$sizeOfInput.'";';
62
                $setWeightAndSize .= 'document.getElementById("samplesize['.$i.']").style.width = "'.$sizeOfInput.'px";';
63
            }
64
        }
65
66
        echo '<script>
67
            var firstTime = true;
68
            var originalOrder = new Array();
69
            var blankSeparatorStart = "'.$blankSeparatorStart.'";
70
            var blankSeparatorEnd = "'.$blankSeparatorEnd.'";
71
            var blankSeparatorStartRegexp = getBlankSeparatorRegexp(blankSeparatorStart);
72
            var blankSeparatorEndRegexp = getBlankSeparatorRegexp(blankSeparatorEnd);
73
            var blanksRegexp = "/"+blankSeparatorStartRegexp+"[^"+blankSeparatorStartRegexp+"]*"+blankSeparatorEndRegexp+"/g";
74
75
            CKEDITOR.on("instanceCreated", function(e) {
76
                if (e.editor.name === "answer") {
77
                    //e.editor.on("change", updateBlanks);
78
                    e.editor.on("change", function(){
79
                        updateBlanks();
80
                    });
81
                }
82
            });
83
84
            function updateBlanks()
85
            {
86
                var answer;
87
                if (firstTime) {
88
                    var field = document.getElementById("answer");
89
                    answer = field.value;
90
                } else {
91
                    answer = CKEDITOR.instances["answer"].getData();
92
                }
93
94
                // disable the save button, if not blanks have been created
95
                $("button").attr("disabled", "disabled");
96
                $("#defineoneblank").show();
97
98
                var blanks = answer.match(eval(blanksRegexp));
99
                var fields = "<div class=\"form-group \">";
100
                fields += "<label class=\"col-sm-2 control-label\"></label>";
101
                fields += "<div class=\"col-sm-8\">";
102
                fields += "<table class=\"data_table\">";
103
                fields += "<tr><th style=\"width:220px\">'.get_lang('WordTofind').'</th>";
104
                fields += "<th style=\"width:50px\">'.get_lang('QuestionWeighting').'</th>";
105
                fields += "<th>'.get_lang('BlankInputSize').'</th></tr>";
106
107
                if (blanks != null) {
108
                    for (var i=0; i < blanks.length; i++) {
109
                        // remove forbidden characters that causes bugs
110
                        blanks[i] = removeForbiddenChars(blanks[i]);
111
                        // trim blanks between brackets
112
                        blanks[i] = trimBlanksBetweenSeparator(blanks[i], blankSeparatorStart, blankSeparatorEnd);
113
114
                        // if the word is empty []
115
                        if (blanks[i] == blankSeparatorStartRegexp+blankSeparatorEndRegexp) {
116
                            break;
117
                        }
118
119
                        // get input size
120
                        var inputSize = 100;
121
                        var textValue = blanks[i].substr(1, blanks[i].length - 2);
122
                        var btoaValue = textValue.hashCode();
123
124
                        if (firstTime == false) {
125
                            var element = document.getElementById("samplesize["+i+"]");
126
                            if (element) {
127
                                inputSize = document.getElementById("sizeofinput["+i+"]").value;
128
                            }
129
                        }
130
131
                        if (document.getElementById("weighting["+i+"]")) {
132
                            var value = document.getElementById("weighting["+i+"]").value;
133
                        } else {
134
                            var value = "1";
135
                        }
136
                        var blanksWithColor = trimBlanksBetweenSeparator(blanks[i], blankSeparatorStart, blankSeparatorEnd, 1);
137
138
                        fields += "<tr>";
139
                        fields += "<td>"+blanksWithColor+"</td>";
140
                        fields += "<td><input class=\"form-control\" style=\"width:60px\" value=\""+value+"\" type=\"text\" id=\"weighting["+i+"]\" name=\"weighting["+i+"]\" /></td>";
141
                        fields += "<td>";
142
                        fields += "<input class=\"btn btn-default\" type=\"button\" value=\"-\" onclick=\"changeInputSize(-1, "+i+")\">&nbsp;";
143
                        fields += "<input class=\"btn btn-default\" type=\"button\" value=\"+\" onclick=\"changeInputSize(1, "+i+")\">&nbsp;";
144
                        fields += "&nbsp;&nbsp;<input class=\"sample\" id=\"samplesize["+i+"]\" data-btoa=\""+btoaValue+"\"   type=\"text\" value=\""+textValue+"\" style=\"width:"+inputSize+"px\" disabled=disabled />";
145
                        fields += "<input id=\"sizeofinput["+i+"]\" type=\"hidden\" value=\""+inputSize+"\" name=\"sizeofinput["+i+"]\"  />";
146
                        fields += "</td>";
147
                        fields += "</tr>";
148
149
                        // enable the save button
150
                        $("button").removeAttr("disabled");
151
                        $("#defineoneblank").hide();
152
                    }
153
                }
154
155
                document.getElementById("blanks_weighting").innerHTML = fields + "</table></div></div>";
156
157
                $(originalOrder).each(function(i, data) {
158
                     if (firstTime == false) {
159
                        value = data.value;
160
                        var d = $("input.sample[data-btoa=\'"+value+"\']");
161
                        var id = d.attr("id");
162
                        if (id) {
163
                            var sizeInputId = id.replace("samplesize", "sizeofinput");
164
                            var sizeInputId = sizeInputId.replace("[", "\\\[");
165
                            var sizeInputId = sizeInputId.replace("]", "\\\]");
166
                            $("#"+sizeInputId).val(data.width);
167
                            d.outerWidth(data.width+"px");
168
                        }
169
                    }
170
                });
171
172
                updateOrder(blanks);
173
174
                if (firstTime) {
175
                    firstTime = false;
176
                    '.$setWeightAndSize.'
177
                }
178
            }
179
180
            window.onload = updateBlanks;
181
            String.prototype.hashCode = function() {
182
                var hash = 0, i, chr, len;
183
                if (this.length === 0) return hash;
184
                for (i = 0, len = this.length; i < len; i++) {
185
                    chr   = this.charCodeAt(i);
186
                    hash  = ((hash << 5) - hash) + chr;
187
                    hash |= 0; // Convert to 32bit integer
188
                }
189
                return hash;
190
            };
191
192
            function updateOrder(blanks)
193
            {
194
                originalOrder = new Array();
195
                 if (blanks != null) {
196
                    for (var i=0; i < blanks.length; i++) {
197
                        // remove forbidden characters that causes bugs
198
                        blanks[i] = removeForbiddenChars(blanks[i]);
199
                        // trim blanks between brackets
200
                        blanks[i] = trimBlanksBetweenSeparator(blanks[i], blankSeparatorStart, blankSeparatorEnd);
201
202
                        // if the word is empty []
203
                        if (blanks[i] == blankSeparatorStartRegexp+blankSeparatorEndRegexp) {
204
                            break;
205
                        }
206
                        var textValue = blanks[i].substr(1, blanks[i].length - 2);
207
                        var btoaValue = textValue.hashCode();
208
209
                        if (firstTime == false) {
210
                            var element = document.getElementById("samplesize["+i+"]");
211
                            if (element) {
212
                                inputSize = document.getElementById("sizeofinput["+i+"]").value;
213
                                originalOrder.push({ "width" : inputSize, "value": btoaValue });
214
                            }
215
                        }
216
                    }
217
                }
218
            }
219
220
            function changeInputSize(coef, inIdNum)
221
            {
222
                if (firstTime) {
223
                    var field = document.getElementById("answer");
224
                    answer = field.value;
225
                } else {
226
                    answer = CKEDITOR.instances["answer"].getData();
227
                }
228
229
                var blanks = answer.match(eval(blanksRegexp));
230
                var currentWidth = $("#samplesize\\\["+inIdNum+"\\\]").width();
231
                var newWidth = currentWidth + coef * 20;
232
                newWidth = Math.max(20, newWidth);
233
                newWidth = Math.min(newWidth, 600);
234
                $("#samplesize\\\["+inIdNum+"\\\]").outerWidth(newWidth);
235
                $("#sizeofinput\\\["+inIdNum+"\\\]").attr("value", newWidth);
236
237
                updateOrder(blanks);
238
            }
239
240
            function removeForbiddenChars(inTxt)
241
            {
242
                outTxt = inTxt;
243
                outTxt = outTxt.replace(/&quot;/g, ""); // remove the   char
244
                outTxt = outTxt.replace(/\x22/g, ""); // remove the   char
245
                outTxt = outTxt.replace(/"/g, ""); // remove the   char
246
                outTxt = outTxt.replace(/\\\\/g, ""); // remove the \ char
247
                outTxt = outTxt.replace(/&nbsp;/g, " ");
248
                outTxt = outTxt.replace(/^ +/, "");
249
                outTxt = outTxt.replace(/ +$/, "");
250
251
                return outTxt;
252
            }
253
254
            function changeBlankSeparator()
255
            {
256
                /* get current select blank type and replaced into #defineoneblank */
257
                var definedSeparator = $("[name=select_separator] option:selected").text();
258
                $("[name=select_separator] option").each(function (index, value) {
259
                    $("#defineoneblank").html($("#defineoneblank").html().replace($(value).html(), definedSeparator))
260
                });
261
                var separatorNumber = $("#select_separator").val();
262
                var tabSeparator = getSeparatorFromNumber(separatorNumber);
263
                blankSeparatorStart = tabSeparator[0];
264
                blankSeparatorEnd = tabSeparator[1];
265
                blankSeparatorStartRegexp = getBlankSeparatorRegexp(blankSeparatorStart);
266
                blankSeparatorEndRegexp = getBlankSeparatorRegexp(blankSeparatorEnd);
267
                blanksRegexp = "/"+blankSeparatorStartRegexp+"[^"+blankSeparatorStartRegexp+"]*"+blankSeparatorEndRegexp+"/g";
268
                updateBlanks();
269
            }
270
271
            // this function is the same than the PHP one
272
            // if modify it modify the php one escapeForRegexp
273
            function getBlankSeparatorRegexp(inTxt)
274
            {
275
                var tabSpecialChar = new Array(".", "+", "*", "?", "[", "^", "]", "$", "(", ")",
276
                    "{", "}", "=", "!", "<", ">", "|", ":", "-", ")");
277
                for (var i=0; i < tabSpecialChar.length; i++) {
278
                    if (inTxt == tabSpecialChar[i]) {
279
                        return "\\\"+inTxt;
280
                    }
281
                }
282
                return inTxt;
283
            }
284
285
            // this function is the same than the PHP one
286
            // if modify it modify the php one getAllowedSeparator
287
            function getSeparatorFromNumber(number)
288
            {
289
                var separator = new Array();
290
                separator[0] = new Array("[", "]");
291
                separator[1] = new Array("{", "}");
292
                separator[2] = new Array("(", ")");
293
                separator[3] = new Array("*", "*");
294
                separator[4] = new Array("#", "#");
295
                separator[5] = new Array("%", "%");
296
                separator[6] = new Array("$", "$");
297
                return separator[number];
298
            }
299
300
            function trimBlanksBetweenSeparator(inTxt, inSeparatorStart, inSeparatorEnd, addColor)
301
            {
302
                var result = inTxt
303
                result = result.replace(inSeparatorStart, "");
304
                result = result.replace(inSeparatorEnd, "");
305
                result = result.trim();
306
307
                if (addColor == 1) {
308
                    var resultParts = result.split("|");
309
                    var partsToString = "";
310
                    resultParts.forEach(function(item, index) {
311
                        if (index == 0) {
312
                            item = "<b><font style=\"color:green\"> " + item +"</font></b>";
313
                        }
314
                        if (index < resultParts.length - 1) {
315
                            item  = item + " | ";
316
                        }
317
                        partsToString += item;
318
                    });
319
                    result = partsToString;
320
                }
321
322
                return inSeparatorStart+result+inSeparatorEnd;
323
            }
324
325
        </script>';
326
327
        // answer
328
        $form->addLabel(
329
            null,
330
            get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank')
331
        );
332
        $form->addElement(
333
            'html_editor',
334
            'answer',
335
            Display::return_icon('fill_field.png'),
336
            ['id' => 'answer'],
337
            ['ToolbarSet' => 'TestQuestionDescription']
338
        );
339
        $form->addRule('answer', get_lang('GiveText'), 'required');
340
341
        //added multiple answers
342
        $form->addElement('checkbox', 'multiple_answer', '', get_lang('FillInBlankSwitchable'));
343
        $form->addElement(
344
            'select',
345
            'select_separator',
346
            get_lang('SelectFillTheBlankSeparator'),
347
            self::getAllowedSeparatorForSelect(),
348
            ' id="select_separator" style="width:150px" class="selectpicker" onchange="changeBlankSeparator()" '
349
        );
350
        $form->addLabel(
351
            null,
352
            '<input type="button" onclick="updateBlanks()" value="'.get_lang('RefreshBlanks').'" class="btn btn-default" />'
353
        );
354
355
        $form->addHtml('<div id="blanks_weighting"></div>');
356
357
        global $text;
358
        // setting the save button here and not in the question class.php
359
        $form->addHtml('<div id="defineoneblank" style="color:#D04A66; margin-left:160px">'.get_lang('DefineBlanks').'</div>');
360
        $form->addButtonSave($text, 'submitQuestion');
361
362
        if (!empty($this->iid)) {
363
            $form->setDefaults($defaults);
364
        } else {
365
            if ($this->isContent == 1) {
366
                $form->setDefaults($defaults);
367
            }
368
        }
369
    }
370
371
    /**
372
     * {@inheritdoc}
373
     */
374
    public function processAnswersCreation($form, $exercise)
375
    {
376
        $answer = $form->getSubmitValue('answer');
377
        // Due the ckeditor transform the elements to their HTML value
378
379
        //$answer = api_html_entity_decode($answer, ENT_QUOTES, $charset);
380
        //$answer = htmlentities(api_utf8_encode($answer));
381
382
        // remove the "::" eventually written by the user
383
        $answer = str_replace('::', '', $answer);
384
385
        // remove starting and ending space and &nbsp;
386
        $answer = api_preg_replace("/\xc2\xa0/", " ", $answer);
387
388
        // start and end separator
389
        $blankStartSeparator = self::getStartSeparator($form->getSubmitValue('select_separator'));
390
        $blankEndSeparator = self::getEndSeparator($form->getSubmitValue('select_separator'));
391
        $blankStartSeparatorRegexp = self::escapeForRegexp($blankStartSeparator);
392
        $blankEndSeparatorRegexp = self::escapeForRegexp($blankEndSeparator);
393
394
        // remove spaces at the beginning and the end of text in square brackets
395
        $answer = preg_replace_callback(
396
            "/".$blankStartSeparatorRegexp."[^]]+".$blankEndSeparatorRegexp."/",
397
            function ($matches) use ($blankStartSeparator, $blankEndSeparator) {
398
                $matchingResult = $matches[0];
399
                $matchingResult = trim($matchingResult, $blankStartSeparator);
400
                $matchingResult = trim($matchingResult, $blankEndSeparator);
401
                $matchingResult = trim($matchingResult);
402
                // remove forbidden chars
403
                $matchingResult = str_replace("/\\/", "", $matchingResult);
404
                $matchingResult = str_replace('/"/', "", $matchingResult);
405
406
                return $blankStartSeparator.$matchingResult.$blankEndSeparator;
407
            },
408
            $answer
409
        );
410
411
        // get the blanks weightings
412
        $nb = preg_match_all(
413
            '/'.$blankStartSeparatorRegexp.'[^'.$blankStartSeparatorRegexp.']*'.$blankEndSeparatorRegexp.'/',
414
            $answer,
415
            $blanks
416
        );
417
418
        if (isset($_GET['editQuestion'])) {
419
            $this->weighting = 0;
420
        }
421
422
        /* if we have some [tobefound] in the text
423
        build the string to save the following in the answers table
424
        <p>I use a [computer] and a [pen].</p>
425
        becomes
426
        <p>I use a [computer] and a [pen].</p>::100,50:100,50@1
427
            ++++++++-------**
428
            --- -- --- -- -
429
            A B  (C) (D)(E)
430
        +++++++ : required, weighting of each words
431
        ------- : optional, input width to display, 200 if not present
432
        ** : equal @1 if "Allow answers order switches" has been checked, @ otherwise
433
        A : weighting for the word [computer]
434
        B : weighting for the word [pen]
435
        C : input width for the word [computer]
436
        D : input width for the word [pen]
437
        E : equal @1 if "Allow answers order switches" has been checked, @ otherwise
438
        */
439
        if ($nb > 0) {
440
            $answer .= '::';
441
            // weighting
442
            for ($i = 0; $i < $nb; $i++) {
443
                // enter the weighting of word $i
444
                $answer .= $form->getSubmitValue('weighting['.$i.']');
445
                // not the last word, add ","
446
                if ($i != $nb - 1) {
447
                    $answer .= ',';
448
                }
449
                // calculate the global weighting for the question
450
                $this->weighting += (float) $form->getSubmitValue('weighting['.$i.']');
451
            }
452
453
            // input width
454
            $answer .= ':';
455
            for ($i = 0; $i < $nb; $i++) {
456
                // enter the width of input for word $i
457
                $answer .= $form->getSubmitValue('sizeofinput['.$i.']');
458
                // not the last word, add ","
459
                if ($i != $nb - 1) {
460
                    $answer .= ',';
461
                }
462
            }
463
        }
464
465
        // write the blank separator code number
466
        // see function getAllowedSeparator
467
        /*
468
            0 [...]
469
            1 {...}
470
            2 (...)
471
            3 *...*
472
            4 #...#
473
            5 %...%
474
            6 $...$
475
         */
476
        $answer .= ':'.$form->getSubmitValue('select_separator');
477
478
        // Allow answers order switches
479
        $is_multiple = $form->getSubmitValue('multiple_answer');
480
        $answer .= '@'.$is_multiple;
481
482
        $this->save($exercise);
483
        $objAnswer = new Answer($this->iid);
484
        $objAnswer->createAnswer($answer, 0, '', 0, 1);
485
        $objAnswer->save();
486
    }
487
488
    /**
489
     * {@inheritdoc}
490
     */
491
    public function return_header(Exercise $exercise, $counter = null, $score = [])
492
    {
493
        $header = parent::return_header($exercise, $counter, $score);
494
        $header .= '<table class="'.$this->question_table_class.'">
495
            <tr>
496
                <th>'.get_lang('Answer').'</th>
497
            </tr>';
498
499
        return $header;
500
    }
501
502
    /**
503
     * @param int    $currentQuestion
504
     * @param int    $questionId
505
     * @param string $correctItem
506
     * @param array  $attributes
507
     * @param string $answer
508
     * @param array  $listAnswersInfo
509
     * @param bool   $displayForStudent
510
     * @param int    $inBlankNumber
511
     * @param string $labelId
512
     *
513
     * @return string
514
     */
515
    public static function getFillTheBlankHtml(
516
        $currentQuestion,
517
        $questionId,
518
        $correctItem,
519
        $attributes,
520
        $answer,
521
        $listAnswersInfo,
522
        $displayForStudent,
523
        $inBlankNumber,
524
        $labelId = ''
525
    ) {
526
        $inTabTeacherSolution = $listAnswersInfo['words'];
527
        $inTeacherSolution = $inTabTeacherSolution[$inBlankNumber];
528
529
        if (empty($labelId)) {
530
            $labelId = 'choice_id_'.$currentQuestion.'_'.$inBlankNumber;
531
        }
532
533
        switch (self::getFillTheBlankAnswerType($inTeacherSolution)) {
534
            case self::FILL_THE_BLANK_MENU:
535
                $selected = '';
536
                // the blank menu
537
                // display a menu from answer separated with |
538
                // if display for student, shuffle the correct answer menu
539
                $listMenu = self::getFillTheBlankMenuAnswers(
540
                    $inTeacherSolution,
541
                    $displayForStudent
542
                );
543
544
                $resultOptions = ['' => '--'];
545
                foreach ($listMenu as $item) {
546
                    $resultOptions[sha1($item)] = $item;
547
                }
548
549
                foreach ($resultOptions as $key => $value) {
550
                    if ($correctItem == $value) {
551
                        $selected = $key;
552
553
                        break;
554
                    }
555
                }
556
                $width = '';
557
                if (!empty($attributes['style'])) {
558
                    $width = str_replace('width:', '', $attributes['style']);
559
                }
560
561
                $result = Display::select(
562
                    "choice[$questionId][]",
563
                    $resultOptions,
564
                    $selected,
565
                    [
566
                        'class' => 'selectpicker',
567
                        'data-width' => $width,
568
                        'id' => $labelId,
569
                    ],
570
                    false
571
                );
572
                break;
573
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
574
            case self::FILL_THE_BLANK_STANDARD:
575
            default:
576
                $attributes['id'] = $labelId;
577
                $result = Display::input(
578
                    'text',
579
                    "choice[$questionId][]",
580
                    $correctItem,
581
                    $attributes
582
                );
583
                break;
584
        }
585
586
        return $result;
587
    }
588
589
    /**
590
     * Return an array with the different choices available
591
     * when the answers between bracket show as a menu.
592
     *
593
     * @param string $correctAnswer
594
     * @param bool   $displayForStudent true if we want to shuffle the choices of the menu for students
595
     *
596
     * @return array
597
     */
598
    public static function getFillTheBlankMenuAnswers($correctAnswer, $displayForStudent)
599
    {
600
        $list = api_preg_split("/\|/", $correctAnswer);
601
        foreach ($list as &$item) {
602
            $item = self::trimOption($item);
603
            $item = api_html_entity_decode($item);
604
        }
605
        // The list is always in the same order, there's no option to allow or disable shuffle options.
606
        if ($displayForStudent) {
607
            shuffle_assoc($list);
608
        }
609
610
        return $list;
611
    }
612
613
    /**
614
     * Return the array index of the student answer.
615
     *
616
     * @param string $correctAnswer the menu Choice1|Choice2|Choice3
617
     * @param string $studentAnswer the student answer must be Choice1 or Choice2 or Choice3
618
     *
619
     * @return int in the example 0 1 or 2 depending of the choice of the student
620
     */
621
    public static function getFillTheBlankMenuAnswerNum($correctAnswer, $studentAnswer)
622
    {
623
        $listChoices = self::getFillTheBlankMenuAnswers($correctAnswer, false);
624
        foreach ($listChoices as $num => $value) {
625
            if ($value == $studentAnswer) {
626
                return $num;
627
            }
628
        }
629
630
        // should not happened, because student choose the answer in a menu of possible answers
631
        return -1;
632
    }
633
634
    /**
635
     * Return the possible answer if the answer between brackets is a multiple choice menu.
636
     *
637
     * @param string $correctAnswer
638
     *
639
     * @return array
640
     */
641
    public static function getFillTheBlankSeveralAnswers($correctAnswer)
642
    {
643
        // is answer||Answer||response||Response , mean answer or Answer ...
644
        return api_preg_split("/\|\|/", $correctAnswer);
645
    }
646
647
    /**
648
     * Return true if student answer is right according to the correctAnswer
649
     * it is not as simple as equality, because of the type of Fill The Blank question
650
     * eg : studentAnswer = 'Un' and correctAnswer = 'Un||1||un'.
651
     *
652
     * @param string $studentAnswer [student_answer] of the info array of the answer field
653
     * @param string $correctAnswer [words] of the info array of the answer field
654
     * @param bool   $fromDatabase
655
     *
656
     * @return bool
657
     */
658
    public static function isStudentAnswerGood($studentAnswer, $correctAnswer, $fromDatabase = false)
659
    {
660
        $result = false;
661
        switch (self::getFillTheBlankAnswerType($correctAnswer)) {
662
            case self::FILL_THE_BLANK_MENU:
663
                $listMenu = self::getFillTheBlankMenuAnswers($correctAnswer, false);
664
                if ($studentAnswer != '' && isset($listMenu[0])) {
665
                    // First item is always the correct one.
666
                    $item = $listMenu[0];
667
                    if (!$fromDatabase) {
668
                        $item = sha1($item);
669
                        $studentAnswer = sha1($studentAnswer);
670
                    }
671
                    if ($item === $studentAnswer) {
672
                        $result = true;
673
                    }
674
                }
675
                break;
676
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
677
                // the answer must be one of the choice made
678
                $listSeveral = self::getFillTheBlankSeveralAnswers($correctAnswer);
679
                $listSeveral = array_map(
680
                    function ($item) {
681
                        return self::trimOption(api_html_entity_decode($item));
682
                    },
683
                    $listSeveral
684
                );
685
                //$studentAnswer = htmlspecialchars($studentAnswer);
686
                $result = in_array($studentAnswer, $listSeveral);
687
                break;
688
            case self::FILL_THE_BLANK_STANDARD:
689
            default:
690
                $correctAnswer = api_html_entity_decode($correctAnswer);
691
                //$studentAnswer = htmlspecialchars($studentAnswer);
692
                $result = $studentAnswer == self::trimOption($correctAnswer);
693
                break;
694
        }
695
696
        return $result;
697
    }
698
699
    /**
700
     * @param string $correctAnswer
701
     *
702
     * @return int
703
     */
704
    public static function getFillTheBlankAnswerType($correctAnswer)
705
    {
706
        $type = self::FILL_THE_BLANK_STANDARD;
707
        if (api_strpos($correctAnswer, '|') && !api_strpos($correctAnswer, '||')) {
708
            $type = self::FILL_THE_BLANK_MENU;
709
        } elseif (api_strpos($correctAnswer, '||')) {
710
            $type = self::FILL_THE_BLANK_SEVERAL_ANSWER;
711
        }
712
713
        return $type;
714
    }
715
716
    /**
717
     * Return information about the answer.
718
     *
719
     * @param string $userAnswer      the text of the answer of the question
720
     * @param bool   $isStudentAnswer true if it's a student answer false the empty question model
721
     *
722
     * @return array of information about the answer
723
     */
724
    public static function getAnswerInfo($userAnswer = '', $isStudentAnswer = false)
725
    {
726
        $listAnswerResults = [];
727
        $listAnswerResults['text'] = '';
728
        $listAnswerResults['words_count'] = 0;
729
        $listAnswerResults['words_with_bracket'] = [];
730
        $listAnswerResults['words'] = [];
731
        $listAnswerResults['weighting'] = [];
732
        $listAnswerResults['input_size'] = [];
733
        $listAnswerResults['switchable'] = '';
734
        $listAnswerResults['student_answer'] = [];
735
        $listAnswerResults['student_score'] = [];
736
        $listAnswerResults['blank_separator_number'] = 0;
737
        $listDoubleColon = [];
738
739
        api_preg_match("/(.*)::(.*)$/s", $userAnswer, $listResult);
740
741
        if (count($listResult) < 2) {
742
            $listDoubleColon[] = '';
743
            $listDoubleColon[] = '';
744
        } else {
745
            $listDoubleColon[] = $listResult[1];
746
            $listDoubleColon[] = $listResult[2];
747
        }
748
749
        $listAnswerResults['system_string'] = $listDoubleColon[1];
750
751
        // Make sure we only take the last bit to find special marks
752
        $listArobaseSplit = explode('@', $listDoubleColon[1]);
753
754
        if (count($listArobaseSplit) < 2) {
755
            $listArobaseSplit[1] = '';
756
        }
757
758
        // Take the complete string except after the last '::'
759
        $listDetails = explode(':', $listArobaseSplit[0]);
760
761
        // < number of item after the ::[score]:[size]:[separator_id]@ , here there are 3
762
        if (count($listDetails) < 3) {
763
            $listWeightings = explode(',', $listDetails[0]);
764
            $listSizeOfInput = [];
765
            for ($i = 0; $i < count($listWeightings); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
766
                $listSizeOfInput[] = 200;
767
            }
768
            $blankSeparatorNumber = 0; // 0 is [...]
769
        } else {
770
            $listWeightings = explode(',', $listDetails[0]);
771
            $listSizeOfInput = explode(',', $listDetails[1]);
772
            $blankSeparatorNumber = $listDetails[2];
773
        }
774
775
        $listAnswerResults['text'] = $listDoubleColon[0];
776
        $listAnswerResults['weighting'] = $listWeightings;
777
        $listAnswerResults['input_size'] = $listSizeOfInput;
778
        $listAnswerResults['switchable'] = $listArobaseSplit[1];
779
        $listAnswerResults['blank_separator_start'] = self::getStartSeparator($blankSeparatorNumber);
780
        $listAnswerResults['blank_separator_end'] = self::getEndSeparator($blankSeparatorNumber);
781
        $listAnswerResults['blank_separator_number'] = $blankSeparatorNumber;
782
783
        $blankCharStart = self::getStartSeparator($blankSeparatorNumber);
784
        $blankCharEnd = self::getEndSeparator($blankSeparatorNumber);
785
        $blankCharStartForRegexp = self::escapeForRegexp($blankCharStart);
786
        $blankCharEndForRegexp = self::escapeForRegexp($blankCharEnd);
787
788
        // Get all blanks words
789
        $listAnswerResults['words_count'] = api_preg_match_all(
790
            '/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/',
791
            $listDoubleColon[0],
792
            $listWords
793
        );
794
795
        if ($listAnswerResults['words_count'] > 0) {
796
            $listAnswerResults['words_with_bracket'] = $listWords[0];
797
            // remove [ and ] in string
798
            array_walk(
799
                $listWords[0],
800
                function (&$value, $key, $tabBlankChar) {
801
                    $trimChars = '';
802
                    for ($i = 0; $i < count($tabBlankChar); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
803
                        $trimChars .= $tabBlankChar[$i];
804
                    }
805
                    $value = trim($value, $trimChars);
806
                },
807
                [$blankCharStart, $blankCharEnd]
808
            );
809
            $listAnswerResults['words'] = $listWords[0];
810
        }
811
812
        // Get all common words
813
        $commonWords = api_preg_replace(
814
            '/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/',
815
            '::',
816
            $listDoubleColon[0]
817
        );
818
819
        // if student answer, the second [] is the student answer,
820
        // the third is if student scored or not
821
        $listBrackets = [];
822
        $listWords = [];
823
        if ($isStudentAnswer) {
824
            for ($i = 0; $i < count($listAnswerResults['words']); $i++) {
825
                $listBrackets[] = $listAnswerResults['words_with_bracket'][$i];
826
                $listWords[] = $listAnswerResults['words'][$i];
827
                if ($i + 1 < count($listAnswerResults['words'])) {
828
                    // should always be
829
                    $i++;
830
                }
831
                $listAnswerResults['student_answer'][] = $listAnswerResults['words'][$i];
832
                if ($i + 1 < count($listAnswerResults['words'])) {
833
                    // should always be
834
                    $i++;
835
                }
836
                $listAnswerResults['student_score'][] = $listAnswerResults['words'][$i];
837
            }
838
            $listAnswerResults['words'] = $listWords;
839
            $listAnswerResults['words_with_bracket'] = $listBrackets;
840
841
            // if we are in student view, we've got 3 times :::::: for common words
842
            $commonWords = api_preg_replace("/::::::/", '::', $commonWords);
843
        }
844
        $listAnswerResults['common_words'] = explode('::', $commonWords);
845
846
        return $listAnswerResults;
847
    }
848
849
    /**
850
     * Return an array of student state answers for fill the blank questions
851
     * for each students that answered the question
852
     * -2  : didn't answer
853
     * -1  : student answer is wrong
854
     *  0  : student answer is correct
855
     * >0  : fill the blank question with choice menu, is the index of the student answer (right answer index is 0).
856
     *
857
     * @param int $testId
858
     * @param int $questionId
859
     * @param $studentsIdList
860
     * @param string $startDate
861
     * @param string $endDate
862
     * @param bool   $useLastAnsweredAttempt
863
     *
864
     * @return array
865
     *               (
866
     *               [student_id] => Array
867
     *               (
868
     *               [first fill the blank for question] => -1
869
     *               [second fill the blank for question] => 2
870
     *               [third fill the blank for question] => -1
871
     *               )
872
     *               )
873
     */
874
    public static function getFillTheBlankResult(
875
        $testId,
876
        $questionId,
877
        $studentsIdList,
878
        $startDate,
879
        $endDate,
880
        $useLastAnsweredAttempt = true
881
    ) {
882
        $tblTrackEAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
883
        $tblTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
884
        $courseId = api_get_course_int_id();
885
        // If no user has answered questions, no need to go further. Return empty array.
886
        if (empty($studentsIdList)) {
887
            return [];
888
        }
889
        // request to have all the answers of student for this question
890
        // student may have doing it several time
891
        // student may have not answered the bracket id, in this case, is result of the answer is empty
892
        // we got the less recent attempt first
893
        $sql = 'SELECT * FROM '.$tblTrackEAttempt.' tea
894
                LEFT JOIN '.$tblTrackEExercise.' tee
895
                ON
896
                    tee.exe_id = tea.exe_id AND
897
                    tea.c_id = '.$courseId.' AND
898
                    exe_exo_id = '.$testId.'
899
               WHERE
900
                    tee.c_id = '.$courseId.' AND
901
                    question_id = '.$questionId.' AND
902
                    tea.user_id IN ('.implode(',', $studentsIdList).')  AND
903
                    tea.tms >= "'.$startDate.'" AND
904
                    tea.tms <= "'.$endDate.'"
905
               ORDER BY user_id, tea.exe_id;
906
        ';
907
908
        $res = Database::query($sql);
909
        $userResult = [];
910
        // foreach attempts for all students starting with his older attempt
911
        while ($data = Database::fetch_array($res)) {
912
            $answer = self::getAnswerInfo($data['answer'], true);
913
914
            // for each bracket to find in this question
915
            foreach ($answer['student_answer'] as $bracketNumber => $studentAnswer) {
916
                if ($answer['student_answer'][$bracketNumber] != '') {
917
                    // student has answered this bracket, cool
918
                    switch (self::getFillTheBlankAnswerType($answer['words'][$bracketNumber])) {
919
                        case self::FILL_THE_BLANK_MENU:
920
                            // get the indice of the choosen answer in the menu
921
                            // we know that the right answer is the first entry of the menu, ie 0
922
                            // (remember, menu entries are shuffled when taking the test)
923
                            $userResult[$data['user_id']][$bracketNumber] = self::getFillTheBlankMenuAnswerNum(
924
                                $answer['words'][$bracketNumber],
925
                                $answer['student_answer'][$bracketNumber]
926
                            );
927
                            break;
928
                        default:
929
                            if (self::isStudentAnswerGood(
930
                                $answer['student_answer'][$bracketNumber],
931
                                $answer['words'][$bracketNumber]
932
                            )
933
                            ) {
934
                                $userResult[$data['user_id']][$bracketNumber] = 0; //  right answer
935
                            } else {
936
                                $userResult[$data['user_id']][$bracketNumber] = -1; // wrong answer
937
                            }
938
                    }
939
                } else {
940
                    // student didn't answer this bracket
941
                    if ($useLastAnsweredAttempt) {
942
                        // if we take into account the last answered attempt
943
                        if (!isset($userResult[$data['user_id']][$bracketNumber])) {
944
                            $userResult[$data['user_id']][$bracketNumber] = -2; // not answered
945
                        }
946
                    } else {
947
                        // we take the last attempt, even if the student answer the question before
948
                        $userResult[$data['user_id']][$bracketNumber] = -2; // not answered
949
                    }
950
                }
951
            }
952
        }
953
954
        return $userResult;
955
    }
956
957
    /**
958
     * Return the number of student that give at leat an answer in the fill the blank test.
959
     *
960
     * @param array $resultList
961
     *
962
     * @return int
963
     */
964
    public static function getNbResultFillBlankAll($resultList)
965
    {
966
        $outRes = 0;
967
        // for each student in group
968
        foreach ($resultList as $list) {
969
            $found = false;
970
            // for each bracket, if student has at least one answer ( choice > -2) then he pass the question
971
            foreach ($list as $choice) {
972
                if ($choice > -2 && !$found) {
973
                    $outRes++;
974
                    $found = true;
975
                }
976
            }
977
        }
978
979
        return $outRes;
980
    }
981
982
    /**
983
     * Replace the occurrence of blank word with [correct answer][student answer][answer is correct].
984
     *
985
     * @param array $listWithStudentAnswer
986
     *
987
     * @return string
988
     */
989
    public static function getAnswerInStudentAttempt($listWithStudentAnswer)
990
    {
991
        $separatorStart = $listWithStudentAnswer['blank_separator_start'];
992
        $separatorEnd = $listWithStudentAnswer['blank_separator_end'];
993
        // lets rebuild the sentence with [correct answer][student answer][answer is correct]
994
        $result = '';
995
        for ($i = 0; $i < count($listWithStudentAnswer['common_words']) - 1; $i++) {
996
            $answerValue = null;
997
            if (isset($listWithStudentAnswer['student_answer'][$i])) {
998
                $answerValue = $listWithStudentAnswer['student_answer'][$i];
999
            }
1000
            $scoreValue = null;
1001
            if (isset($listWithStudentAnswer['student_score'][$i])) {
1002
                $scoreValue = $listWithStudentAnswer['student_score'][$i];
1003
            }
1004
1005
            $result .= $listWithStudentAnswer['common_words'][$i];
1006
            $result .= $listWithStudentAnswer['words_with_bracket'][$i];
1007
            $result .= $separatorStart.$answerValue.$separatorEnd;
1008
            $result .= $separatorStart.$scoreValue.$separatorEnd;
1009
        }
1010
        $result .= $listWithStudentAnswer['common_words'][$i];
1011
        $result .= '::';
1012
        // add the system string
1013
        $result .= $listWithStudentAnswer['system_string'];
1014
1015
        return $result;
1016
    }
1017
1018
    /**
1019
     * This function is the same than the js one above getBlankSeparatorRegexp.
1020
     *
1021
     * @param string $inChar
1022
     *
1023
     * @return string
1024
     */
1025
    public static function escapeForRegexp($inChar)
1026
    {
1027
        $listChars = [
1028
            ".",
1029
            "+",
1030
            "*",
1031
            "?",
1032
            "[",
1033
            "^",
1034
            "]",
1035
            "$",
1036
            "(",
1037
            ")",
1038
            "{",
1039
            "}",
1040
            "=",
1041
            "!",
1042
            ">",
1043
            "|",
1044
            ":",
1045
            "-",
1046
            ")",
1047
        ];
1048
1049
        if (in_array($inChar, $listChars)) {
1050
            return "\\".$inChar;
1051
        } else {
1052
            return $inChar;
1053
        }
1054
    }
1055
1056
    /**
1057
     * This function must be the same than the js one getSeparatorFromNumber above.
1058
     *
1059
     * @return array
1060
     */
1061
    public static function getAllowedSeparator()
1062
    {
1063
        return [
1064
            ['[', ']'],
1065
            ['{', '}'],
1066
            ['(', ')'],
1067
            ['*', '*'],
1068
            ['#', '#'],
1069
            ['%', '%'],
1070
            ['$', '$'],
1071
        ];
1072
    }
1073
1074
    /**
1075
     * return the start separator for answer.
1076
     *
1077
     * @param string $number
1078
     *
1079
     * @return string
1080
     */
1081
    public static function getStartSeparator($number)
1082
    {
1083
        $listSeparators = self::getAllowedSeparator();
1084
1085
        return $listSeparators[$number][0];
1086
    }
1087
1088
    /**
1089
     * return the end separator for answer.
1090
     *
1091
     * @param string $number
1092
     *
1093
     * @return string
1094
     */
1095
    public static function getEndSeparator($number)
1096
    {
1097
        $listSeparators = self::getAllowedSeparator();
1098
1099
        return $listSeparators[$number][1];
1100
    }
1101
1102
    /**
1103
     * Return as a description text, array of allowed separators for question
1104
     * eg: array("[...]", "(...)").
1105
     *
1106
     * @return array
1107
     */
1108
    public static function getAllowedSeparatorForSelect()
1109
    {
1110
        $listResults = [];
1111
        $allowedSeparator = self::getAllowedSeparator();
1112
        foreach ($allowedSeparator as $part) {
1113
            $listResults[] = $part[0].'...'.$part[1];
1114
        }
1115
1116
        return $listResults;
1117
    }
1118
1119
    /**
1120
     * return the HTML display of the answer.
1121
     *
1122
     * @param string $answer
1123
     * @param int    $feedbackType
1124
     * @param bool   $resultsDisabled
1125
     * @param bool   $showTotalScoreAndUserChoices
1126
     *
1127
     * @return string
1128
     */
1129
    public static function getHtmlDisplayForAnswer(
1130
        $answer,
1131
        $feedbackType,
1132
        $resultsDisabled = false,
1133
        $showTotalScoreAndUserChoices = false
1134
    ) {
1135
        $result = '';
1136
        $listStudentAnswerInfo = self::getAnswerInfo($answer, true);
1137
1138
        // rebuild the answer with good HTML style
1139
        // this is the student answer, right or wrong
1140
        for ($i = 0; $i < count($listStudentAnswerInfo['student_answer']); $i++) {
1141
            if ($listStudentAnswerInfo['student_score'][$i] == 1) {
1142
                $listStudentAnswerInfo['student_answer'][$i] = self::getHtmlRightAnswer(
1143
                    $listStudentAnswerInfo['student_answer'][$i],
1144
                    $listStudentAnswerInfo['words'][$i],
1145
                    $feedbackType,
1146
                    $resultsDisabled,
1147
                    $showTotalScoreAndUserChoices
1148
                );
1149
            } else {
1150
                $listStudentAnswerInfo['student_answer'][$i] = self::getHtmlWrongAnswer(
1151
                    $listStudentAnswerInfo['student_answer'][$i],
1152
                    $listStudentAnswerInfo['words'][$i],
1153
                    $feedbackType,
1154
                    $resultsDisabled,
1155
                    $showTotalScoreAndUserChoices
1156
                );
1157
            }
1158
        }
1159
1160
        // rebuild the sentence with student answer inserted
1161
        for ($i = 0; $i < count($listStudentAnswerInfo['common_words']); $i++) {
1162
            if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK) {
1163
                if (empty($listStudentAnswerInfo['student_answer'][$i])) {
1164
                    continue;
1165
                }
1166
            }
1167
            $result .= isset($listStudentAnswerInfo['common_words'][$i]) ? $listStudentAnswerInfo['common_words'][$i] : '';
1168
            $studentLabel = isset($listStudentAnswerInfo['student_answer'][$i]) ? $listStudentAnswerInfo['student_answer'][$i] : '';
1169
            $result .= $studentLabel;
1170
        }
1171
1172
        // the last common word (should be </p>)
1173
        $result .= isset($listStudentAnswerInfo['common_words'][$i]) ? $listStudentAnswerInfo['common_words'][$i] : '';
1174
1175
        return $result;
1176
    }
1177
1178
    /**
1179
     * return the HTML code of answer for correct and wrong answer.
1180
     *
1181
     * @param string $answer
1182
     * @param string $correct
1183
     * @param string $right
1184
     * @param int    $feedbackType
1185
     * @param bool   $resultsDisabled
1186
     * @param bool   $showTotalScoreAndUserChoices
1187
     *
1188
     * @return string
1189
     */
1190
    public static function getHtmlAnswer(
1191
        $answer,
1192
        $correct,
1193
        $right,
1194
        $feedbackType,
1195
        $resultsDisabled = false,
1196
        $showTotalScoreAndUserChoices = false
1197
    ) {
1198
        $hideExpectedAnswer = false;
1199
        $hideUserSelection = false;
1200
        switch ($resultsDisabled) {
1201
            case RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING:
1202
            case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
1203
                $hideUserSelection = true;
1204
                break;
1205
            case RESULT_DISABLE_SHOW_SCORE_ONLY:
1206
                if (0 == $feedbackType) {
1207
                    $hideExpectedAnswer = true;
1208
                }
1209
                break;
1210
            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
1211
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
1212
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
1213
                $hideExpectedAnswer = true;
1214
                if ($showTotalScoreAndUserChoices) {
1215
                    $hideExpectedAnswer = false;
1216
                }
1217
                break;
1218
        }
1219
1220
        $style = 'feedback-green';
1221
        $iconAnswer = Display::return_icon('attempt-check.png', get_lang('Correct'), null, ICON_SIZE_SMALL);
1222
        if (!$right) {
1223
            $style = 'feedback-red';
1224
            $iconAnswer = Display::return_icon('attempt-nocheck.png', get_lang('Incorrect'), null, ICON_SIZE_SMALL);
1225
        }
1226
1227
        $correctAnswerHtml = '';
1228
        $type = self::getFillTheBlankAnswerType($correct);
1229
        switch ($type) {
1230
            case self::FILL_THE_BLANK_MENU:
1231
                $listPossibleAnswers = self::getFillTheBlankMenuAnswers($correct, false);
1232
                $correctAnswerHtml .= "<span class='correct-answer'><strong>".$listPossibleAnswers[0]."</strong>";
1233
                $correctAnswerHtml .= ' (';
1234
                for ($i = 1; $i < count($listPossibleAnswers); $i++) {
1235
                    $correctAnswerHtml .= $listPossibleAnswers[$i];
1236
                    if ($i != count($listPossibleAnswers) - 1) {
1237
                        $correctAnswerHtml .= ' | ';
1238
                    }
1239
                }
1240
                $correctAnswerHtml .= ")</span>";
1241
                break;
1242
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
1243
                $listCorrects = explode('||', $correct);
1244
                $firstCorrect = $correct;
1245
                if (count($listCorrects) > 0) {
1246
                    $firstCorrect = $listCorrects[0];
1247
                }
1248
                $correctAnswerHtml = "<span class='correct-answer'>".$firstCorrect."</span>";
1249
                break;
1250
            case self::FILL_THE_BLANK_STANDARD:
1251
            default:
1252
                $correctAnswerHtml = "<span class='correct-answer'>".$correct."</span>";
1253
        }
1254
1255
        if ($hideExpectedAnswer) {
1256
            $correctAnswerHtml = "<span
1257
                class='feedback-green'
1258
                title='".get_lang('ExerciseWithFeedbackWithoutCorrectionComment')."'> &#8212; </span>";
1259
        }
1260
1261
        $result = "<span class='feedback-question'>";
1262
        if ($hideUserSelection === false) {
1263
            $result .= $iconAnswer."<span class='$style'>".$answer."</span>";
1264
        }
1265
        $result .= "<span class='feedback-separator'>|</span>";
1266
        $result .= $correctAnswerHtml;
1267
        $result .= '</span>';
1268
1269
        return $result;
1270
    }
1271
1272
    /**
1273
     * return HTML code for correct answer.
1274
     *
1275
     * @param string $answer
1276
     * @param string $correct
1277
     * @param string $feedbackType
1278
     * @param bool   $resultsDisabled
1279
     * @param bool   $showTotalScoreAndUserChoices
1280
     *
1281
     * @return string
1282
     */
1283
    public static function getHtmlRightAnswer(
1284
        $answer,
1285
        $correct,
1286
        $feedbackType,
1287
        $resultsDisabled = false,
1288
        $showTotalScoreAndUserChoices = false
1289
    ) {
1290
        return self::getHtmlAnswer(
1291
            $answer,
1292
            $correct,
1293
            true,
1294
            $feedbackType,
1295
            $resultsDisabled,
1296
            $showTotalScoreAndUserChoices
1297
        );
1298
    }
1299
1300
    /**
1301
     * return HTML code for wrong answer.
1302
     *
1303
     * @param string $answer
1304
     * @param string $correct
1305
     * @param string $feedbackType
1306
     * @param bool   $resultsDisabled
1307
     * @param bool   $showTotalScoreAndUserChoices
1308
     *
1309
     * @return string
1310
     */
1311
    public static function getHtmlWrongAnswer(
1312
        $answer,
1313
        $correct,
1314
        $feedbackType,
1315
        $resultsDisabled = false,
1316
        $showTotalScoreAndUserChoices = false
1317
    ) {
1318
        if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK) {
1319
            return '';
1320
        }
1321
1322
        return self::getHtmlAnswer(
1323
            $answer,
1324
            $correct,
1325
            false,
1326
            $feedbackType,
1327
            $resultsDisabled,
1328
            $showTotalScoreAndUserChoices
1329
        );
1330
    }
1331
1332
    /**
1333
     * Check if a answer is correct by its text.
1334
     *
1335
     * @param string $answerText
1336
     *
1337
     * @return bool
1338
     */
1339
    public static function isCorrect($answerText)
1340
    {
1341
        $answerInfo = self::getAnswerInfo($answerText, true);
1342
        $correctAnswerList = $answerInfo['words'];
1343
        $studentAnswer = $answerInfo['student_answer'];
1344
        $isCorrect = true;
1345
1346
        foreach ($correctAnswerList as $i => $correctAnswer) {
1347
            $value = self::isStudentAnswerGood($studentAnswer[$i], $correctAnswer);
1348
            $isCorrect = $isCorrect && $value;
1349
        }
1350
1351
        return $isCorrect;
1352
    }
1353
1354
    /**
1355
     * Clear the answer entered by student.
1356
     *
1357
     * @param string $answer
1358
     *
1359
     * @return string
1360
     */
1361
    public static function clearStudentAnswer($answer)
1362
    {
1363
        $answer = htmlentities(api_utf8_encode($answer), ENT_QUOTES);
1364
        $answer = str_replace('&#039;', '&#39;', $answer); // fix apostrophe
1365
        $answer = api_preg_replace('/\s\s+/', ' ', $answer); // replace excess white spaces
1366
        $answer = strtr($answer, array_flip(get_html_translation_table(HTML_ENTITIES, ENT_QUOTES)));
1367
1368
        return trim($answer);
1369
    }
1370
1371
    /**
1372
     * Removes double spaces between words.
1373
     *
1374
     * @param string $text
1375
     *
1376
     * @return string
1377
     */
1378
    private static function trimOption($text)
1379
    {
1380
        $text = trim($text);
1381
1382
        return preg_replace("/\s+/", ' ', $text);
1383
    }
1384
}
1385