Completed
Push — 1.10.x ( 3eec0c...a91d05 )
by
unknown
57:18
created

FillBlanks::getFillTheBlankMenuAnswers()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 *	Class FillBlanks
6
 *
7
 *	@author Eric Marguin
8
 * 	@author Julio Montoya multiple fill in blank option added
9
 *	@package chamilo.exercise
10
 **/
11
class FillBlanks extends Question
12
{
13
    static $typePicture = 'fill_in_blanks.png';
14
    static $explanationLangVar = 'FillBlanks';
15
16
    const FILL_THE_BLANK_STANDARD = 0;
17
    const FILL_THE_BLANK_MENU = 1;
18
    const FILL_THE_BLANK_SEVERAL_ANSWER = 2;
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
     * function which redefines Question::createAnswersForm
32
     * @param FormValidator $form
33
     */
34
    public function createAnswersForm($form)
35
    {
36
        $fillBlanksAllowedSeparator = self::getAllowedSeparator();
37
        $defaults = array();
38
39
        if (!empty($this->id)) {
40
            $objectAnswer = new Answer($this->id);
41
            $answer = $objectAnswer->selectAnswer(1);
42
            $listAnswersInfo = FillBlanks::getAnswerInfo($answer);
43
44
            if ($listAnswersInfo["switchable"]) {
45
                $defaults['multiple_answer'] = 1;
46
            } else {
47
                $defaults['multiple_answer'] = 0;
48
            }
49
            //take the complete string except after the last '::'
50
            $defaults['answer'] = $listAnswersInfo["text"];
51
            $defaults['select_separator'] = $listAnswersInfo["blankseparatornumber"];
52
            $blanksepartornumber = $listAnswersInfo["blankseparatornumber"];
53
        } else {
54
            $defaults['answer'] = get_lang('DefaultTextInBlanks');
55
            $defaults['select_separator'] = 0;
56
            $blanksepartornumber = 0;
57
        }
58
59
        $blankSeparatorStart = self::getStartSeparator($blanksepartornumber);
60
        $blankSeparatorEnd = self::getEndSeparator($blanksepartornumber);
61
62
        $setValues = null;
63
64
        if (isset($a_weightings) && count($a_weightings) > 0) {
0 ignored issues
show
Bug introduced by
The variable $a_weightings seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
65
            foreach ($a_weightings as $i => $weighting) {
66
                $setValues .= 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";';
67
            }
68
        }
69
        // javascript
70
        echo '<script>
71
72
            var blankSeparatortStart = "'.$blankSeparatorStart.'";
73
            var blankSeparatortEnd = "'.$blankSeparatorEnd.'";
74
            var blankSeparatortStartRegexp = getBlankSeparatorRegexp(blankSeparatortStart);
75
            var blankSeparatortEndRegexp = getBlankSeparatorRegexp(blankSeparatortEnd);
76
77
            CKEDITOR.on("instanceCreated", function(e) {
78
                if (e.editor.name === "answer") {
79
                    e.editor.on("change", updateBlanks);
80
                }
81
            });
82
83
            var firstTime = true;
84
85
            function updateBlanks()
86
            {
87
                if (firstTime) {
88
                    var field = document.getElementById("answer");
89
                    var answer = field.value;
90
                } else {
91
                    var 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 blanksRegexp = "/"+blankSeparatortStartRegexp+"[^"+blankSeparatortStartRegexp+"]*"+blankSeparatortEndRegexp+"/g";
99
100
                var blanks = answer.match(eval(blanksRegexp));
101
                var fields = "<div class=\"form-group \">";
102
                fields += "<label class=\"col-sm-2 control-label\">'.get_lang('Weighting').'</label>";
103
                fields += "<div class=\"col-sm-8\">";
104
                fields += "<table>";
105
                fields += "<tr><th style=\"padding:0 20px\">'.get_lang("WordTofind").'</th><th style=\"padding:0 20px\">'.get_lang("QuestionWeighting").'</th><th style=\"padding:0 20px\">'.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], blankSeparatortStart, blankSeparatortEnd);
113
114
                        // if the word is empty []
115
                        if (blanks[i] == blankSeparatortStartRegexp+blankSeparatortEndRegexp) {
116
                            break;
117
                        }
118
                        // get input size
119
                        var lainputsize = 200;
120
                        if ($("#samplesize\\\["+i+"\\\]").width()) {
121
                            lainputsize = $("#samplesize\\\["+i+"\\\]").width();
122
                        }
123
124
                        if (document.getElementById("weighting["+i+"]")) {
125
                            var value = document.getElementById("weighting["+i+"]").value;
126
                        } else {
127
                            var value = "10";
128
                        }
129
                        fields += "<tr>";
130
                        fields += "<td>"+blanks[i]+"</td>";
131
                        fields += "<td><input style=\"width:35px\" value=\""+value+"\" type=\"text\" id=\"weighting["+i+"]\" name=\"weighting["+i+"]\" /></td>";
132
                        fields += "<td>";
133
                        fields += "<input type=\"button\" value=\"-\" onclick=\"changeInputSize(-1, "+i+")\">";
134
                        fields += "<input type=\"button\" value=\"+\" onclick=\"changeInputSize(1, "+i+")\">";
135
                        fields += "<input value=\""+blanks[i].substr(1, blanks[i].length - 2)+"\" style=\"width:"+lainputsize+"px\" disabled=disabled id=\"samplesize["+i+"]\"/>";
136
                        fields += "<input type=\"hidden\" id=\"sizeofinput["+i+"]\" name=\"sizeofinput["+i+"]\" value=\""+lainputsize+"\" \"/>";
137
                        fields += "</td>";
138
                        fields += "</tr>";
139
                        // enable the save button
140
                        $("button").removeAttr("disabled");
141
                        $("#defineoneblank").hide();
142
                    }
143
                }
144
                document.getElementById("blanks_weighting").innerHTML = fields + "</table></div></div>";
145
                if (firstTime) {
146
                    firstTime = false;
147
                ';
148
149
        if (isset($listAnswersInfo) && count($listAnswersInfo["tabweighting"]) > 0) {
150
151
            foreach ($listAnswersInfo["tabweighting"] as $i => $weighting) {
152
                echo 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";';
153
            }
154
            foreach ($listAnswersInfo["tabinputsize"] as $i => $sizeOfInput) {
155
                echo 'document.getElementById("sizeofinput['.$i.']").value = "'.$sizeOfInput.'";';
156
                echo '$("#samplesize\\\['.$i.'\\\]").width('.$sizeOfInput.');';
157
            }
158
        }
159
160
        echo '}
161
            }
162
            window.onload = updateBlanks;
163
164
            function getInputSize() {
165
                var outTabSize = new Array();
166
                $("input").each(function() {
167
                    if ($(this).attr("id") && $(this).attr("id").match(/samplesize/)) {
168
                        var tabidnum = $(this).attr("id").match(/\d+/);
169
                        var idnum = tabidnum[0];
170
                        var thewidth = $(this).next().attr("value");
171
                        tabInputSize[idnum] = thewidth;
172
                    }
173
                });
174
            }
175
176
            function changeInputSize(inCoef, inIdNum)
177
            {
178
                var currentWidth = $("#samplesize\\\["+inIdNum+"\\\]").width();
179
                var newWidth = currentWidth + inCoef * 20;
180
                newWidth = Math.max(20, newWidth);
181
                newWidth = Math.min(newWidth, 600);
182
                $("#samplesize\\\["+inIdNum+"\\\]").width(newWidth);
183
                $("#sizeofinput\\\["+inIdNum+"\\\]").attr("value", newWidth);
184
            }
185
186
            function removeForbiddenChars(inTxt) {
187
                outTxt = inTxt;
188
189
                outTxt = outTxt.replace(/&quot;/g, ""); // remove the   char
190
                outTxt = outTxt.replace(/\x22/g, ""); // remove the   char
191
                outTxt = outTxt.replace(/"/g, ""); // remove the   char
192
                outTxt = outTxt.replace(/\\\\/g, ""); // remove the \ char
193
                outTxt = outTxt.replace(/&nbsp;/g, " ");
194
                outTxt = outTxt.replace(/^ +/, "");
195
                outTxt = outTxt.replace(/ +$/, "");
196
                return outTxt;
197
            }
198
199
            function changeBlankSeparator()
200
            {
201
                var separatorNumber = $("#select_separator").val();
202
                var tabSeparator = getSeparatorFromNumber(separatorNumber);
203
                blankSeparatortStart = tabSeparator[0];
204
                blankSeparatortEnd = tabSeparator[1];
205
                blankSeparatortStartRegexp = getBlankSeparatorRegexp(blankSeparatortStart);
206
                blankSeparatortEndRegexp = getBlankSeparatorRegexp(blankSeparatortEnd);
207
                updateBlanks();
208
            }
209
210
            // this function is the same than the PHP one
211
            // if modify it modify the php one escapeForRegexp
212
            function getBlankSeparatorRegexp(inTxt)
213
            {
214
                var tabSpecialChar = new Array(".", "+", "*", "?", "[", "^", "]", "$", "(", ")",
215
                    "{", "}", "=", "!", "<", ">", "|", ":", "-", ")");
216
                for (var i=0; i < tabSpecialChar.length; i++) {
217
                    if (inTxt == tabSpecialChar[i]) {
218
                        return "\\\"+inTxt;
219
                    }
220
                }
221
                return inTxt;
222
            }
223
224
            // this function is the same than the PHP one
225
            // if modify it modify the php one getAllowedSeparator
226
            function getSeparatorFromNumber(innumber)
227
            {
228
                tabSeparator = new Array();
229
                tabSeparator[0] = new Array("[", "]");
230
                tabSeparator[1] = new Array("{", "}");
231
                tabSeparator[2] = new Array("(", ")");
232
                tabSeparator[3] = new Array("*", "*");
233
                tabSeparator[4] = new Array("#", "#");
234
                tabSeparator[5] = new Array("%", "%");
235
                tabSeparator[6] = new Array("$", "$");
236
                return tabSeparator[innumber];
237
            }
238
239
            function trimBlanksBetweenSeparator(inTxt, inSeparatorStart, inSeparatorEnd)
240
            {
241
                // blankSeparatortStartRegexp
242
                // blankSeparatortEndRegexp
243
                var result = inTxt
244
                result = result.replace(inSeparatorStart, "");
245
                result = result.replace(inSeparatorEnd, "");
246
                result = result.trim();
247
                return inSeparatorStart+result+inSeparatorEnd;
248
            }
249
        </script>';
250
251
        // answer
252
        $form->addElement('label', null, '<br /><br />'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank'));
253
        $form->addElement(
254
            'html_editor',
255
            'answer',
256
            Display::returnIconPath('fill_field.png'),
257
            ['id' => 'answer', 'onkeyup' => "javascript: updateBlanks(this);"],
258
            array('ToolbarSet' => 'TestQuestionDescription')
259
        );
260
        $form->addRule('answer',get_lang('GiveText'),'required');
261
262
        //added multiple answers
263
        $form->addElement('checkbox','multiple_answer','', get_lang('FillInBlankSwitchable'));
264
        $form->addElement(
265
            'select',
266
            'select_separator',
267
            get_lang("SelectFillTheBlankSeparator"),
268
            self::getAllowedSeparatorForSelect(),
269
            ' id="select_separator"   style="width:150px" onchange="changeBlankSeparator()" '
270
        );
271
        $form->addElement(
272
            'label',
273
            null,
274
            '<input type="button" onclick="updateBlanks()" value="'.get_lang('RefreshBlanks').'" class="btn btn-default" />'
275
        );
276
        $form->addElement('html','<div id="blanks_weighting"></div>');
277
278
        global $text;
279
        // setting the save button here and not in the question class.php
280
        $form->addElement('html','<div id="defineoneblank" style="color:#D04A66; margin-left:160px">'.get_lang('DefineBlanks').'</div>');
281
        $form->addButtonSave($text, 'submitQuestion');
282
283
        if (!empty($this->id)) {
284
            $form->setDefaults($defaults);
285
        } else {
286
            if ($this->isContent == 1) {
287
                $form->setDefaults($defaults);
288
            }
289
        }
290
    }
291
292
    /**
293
     * Function which creates the form to create/edit the answers of the question
294
     * @param FormValidator $form
295
     */
296
    public function processAnswersCreation($form)
297
    {
298
        $answer = $form->getSubmitValue('answer');
299
        // Due the ckeditor transform the elements to their HTML value
300
301
        //$answer = api_html_entity_decode($answer, ENT_QUOTES, $charset);
302
        //$answer = htmlentities(api_utf8_encode($answer));
303
304
        // remove the :: eventually written by the user
305
        $answer = str_replace('::', '', $answer);
306
307
        // remove starting and ending space and &nbsp;
308
        $answer = api_preg_replace("/\xc2\xa0/", " ", $answer);
309
310
        // start and end separator
311
312
        $blankStartSeparator = self::getStartSeparator($form->getSubmitValue('select_separator'));
313
        $blankEndSeparator = self::getEndSeparator($form->getSubmitValue('select_separator'));
314
        $blankStartSeparatorRegexp = self::escapeForRegexp($blankStartSeparator);
315
        $blankEndSeparatorRegexp = self::escapeForRegexp($blankEndSeparator);
316
317
        // remove spaces at the beginning and the end of text in square brackets
318
        $answer = preg_replace_callback(
319
            "/".$blankStartSeparatorRegexp."[^]]+".$blankEndSeparatorRegexp."/",
320
            function ($matches) use ($blankStartSeparator, $blankEndSeparator) {
321
                $matchingResult = $matches[0];
322
                $matchingResult = trim($matchingResult, $blankStartSeparator);
323
                $matchingResult = trim($matchingResult, $blankEndSeparator);
324
                $matchingResult = trim($matchingResult);
325
                // remove forbidden chars
326
                $matchingResult = str_replace("/\\/", "", $matchingResult);
327
                $matchingResult = str_replace('/"/', "", $matchingResult);
328
329
                return $blankStartSeparator.$matchingResult.$blankEndSeparator;
330
            },
331
            $answer
332
        );
333
334
        // get the blanks weightings
335
        $nb = preg_match_all(
336
            '/'.$blankStartSeparatorRegexp.'[^'.$blankStartSeparatorRegexp.']*'.$blankEndSeparatorRegexp.'/',
337
            $answer,
338
            $blanks
339
        );
340
341
        if (isset($_GET['editQuestion'])) {
342
            $this->weighting = 0;
343
        }
344
345
        /* if we have some [tobefound] in the text
346
        build the string to save the following in the answers table
347
        <p>I use a [computer] and a [pen].</p>
348
        becomes
349
        <p>I use a [computer] and a [pen].</p>::100,50:100,50@1
350
            ++++++++-------**
351
            --- -- --- -- -
352
            A B  (C) (D)(E)
353
        +++++++ : required, weighting of each words
354
        ------- : optional, input width to display, 200 if not present
355
        ** : equal @1 if "Allow answers order switches" has been checked, @ otherwise
356
        A : weighting for the word [computer]
357
        B : weighting for the word [pen]
358
        C : input width for the word [computer]
359
        D : input width for the word [pen]
360
        E : equal @1 if "Allow answers order switches" has been checked, @ otherwise
361
        */
362
        if ($nb > 0) {
363
            $answer .= '::';
364
            // weighting
365
            for ($i=0; $i < $nb; ++$i) {
366
                // enter the weighting of word $i
367
                $answer .= $form->getSubmitValue('weighting['.$i.']');
368
                // not the last word, add ","
369
                if ($i != $nb - 1) {
370
                    $answer .= ",";
371
                }
372
                // calculate the global weighting for the question
373
                $this -> weighting += $form->getSubmitValue('weighting['.$i.']');
374
            }
375
376
            // input width
377
            $answer .= ":";
378
            for ($i=0; $i < $nb; ++$i) {
379
                // enter the width of input for word $i
380
                $answer .= $form->getSubmitValue('sizeofinput['.$i.']');
381
                // not the last word, add ","
382
                if ($i != $nb - 1) {
383
                    $answer .= ",";
384
                }
385
            }
386
        }
387
388
        // write the blank separator code number
389
        // see function getAllowedSeparator
390
        /*
391
            0 [...]
392
            1 {...}
393
            2 (...)
394
            3 *...*
395
            4 #...#
396
            5 %...%
397
            6 $...$
398
         */
399
        $answer .= ":".$form->getSubmitValue('select_separator');
400
401
        // Allow answers order switches
402
        $is_multiple = $form -> getSubmitValue('multiple_answer');
403
        $answer.= '@'.$is_multiple;
404
405
        $this->save();
406
        $objAnswer = new Answer($this->id);
407
        $objAnswer->createAnswer($answer, 0, '', 0, 1);
408
        $objAnswer->save();
409
    }
410
411
    /**
412
     * @param null $feedback_type
413
     * @param null $counter
414
     * @param null $score
415
     * @return null|string
416
     */
417 View Code Duplication
    public function return_header($feedback_type = null, $counter = null, $score = null)
418
    {
419
        $header = parent::return_header($feedback_type, $counter, $score);
420
        $header .= '<table class="'.$this->question_table_class .'">
421
            <tr>
422
                <th>'.get_lang("Answer").'</th>
423
            </tr>';
424
425
        return $header;
426
    }
427
428
    /**
429
     * @param $separatorStartRegexp
430
     * @param $separatorEndRegexp
431
     * @param $correctItemRegexp
432
     * @param $questionId
433
     * @param $correctItem
434
     * @param $attributes
435
     * @param $answer
436
     * @param $listAnswersInfo
437
     * @param $displayForStudent
438
     * @return string
439
     */
440
    public static function getFillTheBlankHtml(
441
        $separatorStartRegexp,
442
        $separatorEndRegexp,
443
        $correctItemRegexp,
444
        $questionId,
445
        $correctItem,
446
        $attributes,
447
        $answer,
448
        $listAnswersInfo,
449
        $displayForStudent,
450
        $inBlankNumber
451
    ) {
452
        $result = "";
453
        $inTabTeacherSolution = $listAnswersInfo['tabwords'];
454
        $inTeacherSolution = $inTabTeacherSolution[$inBlankNumber];
455
        switch (self::getFillTheBlankAnswerType($inTeacherSolution)) {
456
            case self::FILL_THE_BLANK_MENU:
457
                $selected = "";
458
                // the blank menu
459
                $optionMenu = "";
460
                // display a menu from answer separated with |
461
                // if display for student, shuffle the correct answer menu
462
                $listMenu = self::getFillTheBlankMenuAnswers($inTeacherSolution, $displayForStudent);
463
                $result .= '<select name="choice['.$questionId.'][]">';
464
                for ($k=0; $k < count($listMenu); $k++) {
465
                    $selected = "";
466
                    if ($correctItem == $listMenu[$k]) {
467
                        $selected = " selected=selected ";
468
                    }
469
                    // if in teacher view, display the first item by default, which is the right answer
470
                    if ($k==0 && !$displayForStudent) {
471
                        $selected = " selected=selected ";
472
                    }
473
                    $optionMenu .= '<option '.$selected.' value="'.$listMenu[$k].'">'.$listMenu[$k].'</option>';
474
                }
475
                if ($selected == "") {
476
                    // no good answer have been found...
477
                    $selected = " selected=selected ";
478
                }
479
                $result .= "<option $selected value=''>--</option>";
480
                $result .= $optionMenu;
481
                $result .= '</select>';
482
                break;
483
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
484
                //no break
485
            case self::FILL_THE_BLANK_STANDARD:
486
            default:
487
                $result = Display::input('text', "choice[$questionId][]", $correctItem, $attributes);
488
                break;
489
        }
490
491
        return $result;
492
    }
493
494
    /**
495
     * Return an array with the different choices available
496
     * when the answers between bracket show as a menu
497
     * @param string $correctAnswer
498
     * @param bool $displayForStudent true if we want to shuffle the choices of the menu for students
499
     *
500
     * @return array
501
     */
502
    public static function getFillTheBlankMenuAnswers($correctAnswer, $displayForStudent)
503
    {
504
        // if $inDisplayForStudent, then shuffle the result array
505
        $listChoises = api_preg_split("/\|/", $correctAnswer);
506
        if ($displayForStudent) {
507
            shuffle($listChoises);
508
        }
509
510
        return $listChoises;
511
    }
512
513
    /**
514
     * Return the array index of the student answer
515
     * @param string $correctAnswer the menu Choice1|Choice2|Choice3
516
     * @param string $studentAnswer the student answer must be Choice1 or Choice2 or Choice3
517
     *
518
     * @return int  in the example 0 1 or 2 depending of the choice of the student
519
     */
520
    public static function getFillTheBlankMenuAnswerNum($correctAnswer, $studentAnswer)
521
    {
522
        $listChoices = self::getFillTheBlankMenuAnswers($correctAnswer, false);
523
        foreach ($listChoices as $num => $value) {
524
            if ($value == $studentAnswer) {
525
                return $num;
526
            }
527
        }
528
529
        // should not happened, because student choose the answer in a menu of possible answers
530
        return -1;
531
    }
532
533
534
    /**
535
     * Return the possible answer if the answer between brackets is a multiple choice menu
536
     * @param string $correctAnswer
537
     *
538
     * @return array
539
     */
540
    public static function getFillTheBlankSeveralAnswers($correctAnswer)
541
    {
542
        // is answer||Answer||response||Response , mean answer or Answer ...
543
        $listSeveral = api_preg_split("/\|\|/", $correctAnswer);
544
545
        return $listSeveral;
546
    }
547
548
    /**
549
     * Return true if student answer is right according to the correctAnswer
550
     * it is not as simple as equality, because of the type of Fill The Blank question
551
     * eg : studentAnswer = 'Un' and correctAnswer = 'Un||1||un'
552
     * @param string $studentAnswer [studentanswer] of the info array of the answer field
553
     * @param string $correctAnswer [tabwords] of the info array of the answer field
554
     *
555
     * @return bool
556
     */
557
    public static function isGoodStudentAnswer($studentAnswer, $correctAnswer)
558
    {
559
        switch (self::getFillTheBlankAnswerType($correctAnswer)) {
560
            case self::FILL_THE_BLANK_MENU:
561
                $listMenu = self::getFillTheBlankMenuAnswers($correctAnswer, false);
562
                $result = $listMenu[0] == $studentAnswer;
563
                break;
564
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
565
                // the answer must be one of the choice made
566
                $listSeveral = self::getFillTheBlankSeveralAnswers($correctAnswer);
567
                $result = in_array($studentAnswer, $listSeveral);
568
                break;
569
            case self::FILL_THE_BLANK_STANDARD:
570
            default:
571
                $result = $studentAnswer == $correctAnswer;
572
                break;
573
        }
574
575
        return $result;
576
    }
577
578
    /**
579
     * @param string $correctAnswer
580
     *
581
     * @return int
582
     */
583
    public static function getFillTheBlankAnswerType($correctAnswer)
584
    {
585
        if (api_strpos($correctAnswer, "|") && !api_strpos($correctAnswer, "||")) {
586
            return self::FILL_THE_BLANK_MENU;
587
        } elseif (api_strpos($correctAnswer, "||")) {
588
            return self::FILL_THE_BLANK_SEVERAL_ANSWER;
589
        } else {
590
            return self::FILL_THE_BLANK_STANDARD;
591
        }
592
    }
593
594
    /**
595
     * Return information about the answer
596
     * @param string $userAnswer the text of the answer of the question
597
     * @param bool   $isStudentAnswer true if it's a student answer false the empty question model
598
     *
599
     * @return array of information about the answer
600
     */
601
    public static function getAnswerInfo($userAnswer = "", $isStudentAnswer = false)
602
    {
603
        $listAnswerResults = array();
604
        $listAnswerResults['text'] = "";
605
        $listAnswerResults['wordsCount'] = 0;
606
        $listAnswerResults['tabwordsbracket'] = array();
607
        $listAnswerResults['tabwords'] = array();
608
        $listAnswerResults['tabweighting'] = array();
609
        $listAnswerResults['tabinputsize'] = array();
610
        $listAnswerResults['switchable'] = "";
611
        $listAnswerResults['studentanswer'] = array();
612
        $listAnswerResults['studentscore'] = array();
613
        $listAnswerResults['blankseparatornumber'] = 0;
614
        $listDoubleColon = array();
615
616
        api_preg_match("/(.*)::(.*)$/s", $userAnswer, $listResult);
617
618
        if (count($listResult) < 2) {
619
            $listDoubleColon[] = '';
620
            $listDoubleColon[] = '';
621
        } else {
622
            $listDoubleColon[] = $listResult[1];
623
            $listDoubleColon[] = $listResult[2];
624
        }
625
626
        $listAnswerResults['systemstring'] = $listDoubleColon[1];
627
628
        // make sure we only take the last bit to find special marks
629
        $listArobaseSplit = explode('@', $listDoubleColon[1]);
630
631
        if (count($listArobaseSplit) < 2) {
632
            $listArobaseSplit[1] = "";
633
        }
634
635
        // take the complete string except after the last '::'
636
        $listDetails = explode(":", $listArobaseSplit[0]);
637
638
        // < number of item after the ::[score]:[size]:[separator_id]@ , here there are 3
639
        if (count($listDetails) < 3) {
640
            $listWeightings = explode(',', $listDetails[0]);
641
            $listSizeOfInput = array();
642
            for ($i=0; $i < count($listWeightings); $i++) {
643
                $listSizeOfInput[] = 200;
644
            }
645
            $blankSeparatorNumber = 0;    // 0 is [...]
646
        } else {
647
            $listWeightings = explode(',', $listDetails[0]);
648
            $listSizeOfInput = explode(',', $listDetails[1]);
649
            $blankSeparatorNumber = $listDetails[2];
650
        }
651
652
        $listAnswerResults['text'] = $listDoubleColon[0];
653
        $listAnswerResults['tabweighting'] = $listWeightings;
654
        $listAnswerResults['tabinputsize'] = $listSizeOfInput;
655
        $listAnswerResults['switchable'] = $listArobaseSplit[1];
656
        $listAnswerResults['blankseparatorstart'] = self::getStartSeparator($blankSeparatorNumber);
657
        $listAnswerResults['blankseparatorend'] = self::getEndSeparator($blankSeparatorNumber);
658
        $listAnswerResults['blankseparatornumber'] = $blankSeparatorNumber;
659
660
        $blankCharStart = self::getStartSeparator($blankSeparatorNumber);
661
        $blankCharEnd = self::getEndSeparator($blankSeparatorNumber);
662
        $blankCharStartForRegexp = self::escapeForRegexp($blankCharStart);
663
        $blankCharEndForRegexp = self::escapeForRegexp($blankCharEnd);
664
665
        // get all blanks words
666
        $listAnswerResults['wordsCount'] = api_preg_match_all(
667
            '/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/',
668
            $listDoubleColon[0],
669
            $listWords
670
        );
671
672
        if ($listAnswerResults['wordsCount'] > 0) {
673
            $listAnswerResults['tabwordsbracket'] = $listWords[0];
674
            // remove [ and ] in string
675
            array_walk(
676
                $listWords[0],
677
                function (&$value, $key, $tabBlankChar) {
678
                    $trimChars = "";
679
                    for ($i=0; $i < count($tabBlankChar); $i++) {
680
                        $trimChars .= $tabBlankChar[$i];
681
                    }
682
                    $value = trim($value, $trimChars);
683
                },
684
                array($blankCharStart, $blankCharEnd)
685
            );
686
            $listAnswerResults['tabwords'] = $listWords[0];
687
        }
688
689
        // get all common words
690
        $commonWords = api_preg_replace(
691
            '/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/',
692
            "::",
693
            $listDoubleColon[0]
694
        );
695
696
        // if student answer, the second [] is the student answer,
697
        // the third is if student scored or not
698
        $listBrackets = array();
699
        $listWords =  array();
700
701
        if ($isStudentAnswer) {
702
            for ($i=0; $i < count($listAnswerResults['tabwords']); $i++) {
703
                $listBrackets[] = $listAnswerResults['tabwordsbracket'][$i];
704
                $listWords[] = $listAnswerResults['tabwords'][$i];
705
                if ($i+1 < count($listAnswerResults['tabwords'])) {
706
                    // should always be
707
                    $i++;
708
                }
709
                $listAnswerResults['studentanswer'][] = $listAnswerResults['tabwords'][$i];
710
                if ($i+1 < count($listAnswerResults['tabwords'])) {
711
                    // should always be
712
                    $i++;
713
                }
714
                $listAnswerResults['studentscore'][] = $listAnswerResults['tabwords'][$i];
715
            }
716
            $listAnswerResults['tabwords'] = $listWords;
717
            $listAnswerResults['tabwordsbracket'] = $listBrackets;
718
719
            // if we are in student view, we've got 3 times :::::: for common words
720
            $commonWords = api_preg_replace("/::::::/", "::", $commonWords);
721
        }
722
723
        $listAnswerResults['commonwords'] = explode("::", $commonWords);
724
725
        return $listAnswerResults;
726
    }
727
    
728
    /**
729
    * Return an array of student state answers for fill the blank questions
730
    * for each students that answered the question
731
    * -2  : didn't answer
732
    * -1  : student answer is wrong
733
    *  0  : student answer is correct
734
    * >0  : for fill the blank question with choice menu, is the index of the student answer (right answer indice is 0)
735
    *
736
    * @param $testId
737
    * @param $questionId
738
    * @param $studentsIdList
739
    * @param $startDate
740
    * @param $endDate
741
    * @param bool $useLastAnswerredAttempt
742
    * @return array
743
    * (
744
    *     [student_id] => Array
745
    *         (
746
    *             [first fill the blank for question] => -1
747
    *             [second fill the blank for question] => 2
748
    *             [third fill the blank for question] => -1
749
    *         )
750
    * )
751
    */
752
    public static function getFillTheBlankTabResult($testId, $questionId, $studentsIdList, $startDate, $endDate, $useLastAnswerredAttempt = true) {
753
754
       $tblTrackEAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
755
       $tblTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
756
       $courseId = api_get_course_int_id();
757
758
       require_once api_get_path(SYS_PATH).'main/exercice/fill_blanks.class.php';
759
760
       // request to have all the answers of student for this question
761
       // student may have doing it several time
762
       // student may have not answered the bracket id, in this case, is result of the answer is empty
763
764
       // we got the less recent attempt first
765
       $sql = '
766
           SELECT * FROM '.$tblTrackEAttempt.' tea
767
768
           LEFT JOIN '.$tblTrackEExercise.' tee
769
           ON tee.exe_id = tea.exe_id
770
           AND tea.c_id = '.$courseId.'
771
           AND exe_exo_id = '.$testId.'
772
773
           WHERE tee.c_id = '.$courseId.'
774
           AND question_id = '.$questionId.'
775
           AND tea.user_id IN ('.implode(',', $studentsIdList).')
776
           AND tea.tms >= "'.$startDate.'"
777
           AND tea.tms <= "'.$endDate.'"
778
           ORDER BY user_id, tea.exe_id;
779
       ';
780
781
       $res = Database::query($sql);
782
       $tabUserResult = array();
783
       $bracketNumber = 0;
784
       // foreach attempts for all students starting with his older attempt
785
       while ($data = Database::fetch_array($res)) {
786
           $tabAnswer = FillBlanks::getAnswerInfo($data['answer'], true);
787
788
           // for each bracket to find in this question
789
           foreach ($tabAnswer['studentanswer'] as $bracketNumber => $studentAnswer) {
790
791
               if ($tabAnswer['studentanswer'][$bracketNumber] != '') {
792
                   // student has answered this bracket, cool
793
                   switch (FillBlanks::getFillTheBlankAnswerType($tabAnswer['tabwords'][$bracketNumber])) {
794
                       case self::FILL_THE_BLANK_MENU :
795
                           // get the indice of the choosen answer in the menu
796
                           // we know that the right answer is the first entry of the menu, ie 0
797
                           // (remember, menu entries are shuffled when taking the test)
798
                           $tabUserResult[$data['user_id']][$bracketNumber] = FillBlanks::getFillTheBlankMenuAnswerNum($tabAnswer['tabwords'][$bracketNumber], $tabAnswer['studentanswer'][$bracketNumber]);
799
                           break;
800
                       default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
801
                           if (FillBlanks::isGoodStudentAnswer($tabAnswer['studentanswer'][$bracketNumber], $tabAnswer['tabwords'][$bracketNumber])) {
802
                               $tabUserResult[$data['user_id']][$bracketNumber] = 0;   //  right answer
803
                           } else {
804
                               $tabUserResult[$data['user_id']][$bracketNumber] = -1;  // wrong answer
805
                           }
806
                   }
807
               } else {
808
                   // student didn't answer this bracket
809
                   if ($useLastAnswerredAttempt) {
810
                       // if we take into account the last answered attempt
811
                       if (!isset($tabUserResult[$data['user_id']][$bracketNumber])) {
812
                           $tabUserResult[$data['user_id']][$bracketNumber] = -2;      // not answered
813
                       }
814
                   } else {
815
                       // we take the last attempt, even if the student answer the question before
816
                       $tabUserResult[$data['user_id']][$bracketNumber] = -2;      // not answered
817
                   }
818
               }
819
           }
820
821
822
       }
823
       return $tabUserResult;
824
    }
825
826
827
828
    /**
829
     * Return the number of student that give at leat an answer in the fill the blank test
830
     * @param $resultList
831
     * @return int
832
     */
833
    public static function getNbResultFillBlankAll($resultList)
834
    {
835
        $outRes = 0;
836
        // for each student in group
837
        foreach($resultList as $userId => $tabValue) {
838
            $trouve = false;
839
            // for each bracket, if student has at leat one answer ( choice > -2) then he pass the question
840
            foreach($tabValue as $i => $choice) {
841
                if ($choice > -2 && !$trouve) {
842
                    $outRes++;
843
                    $trouve = true;
844
                }
845
            }
846
        }
847
        return $outRes;
848
    }
849
850
    /**
851
     * Replace the occurrence of blank word with [correct answer][student answer][answer is correct]
852
     * @param array $listWithStudentAnswer
853
     *
854
     * @return string
855
     */
856
    public static function getAnswerInStudentAttempt($listWithStudentAnswer)
857
    {
858
        $separatorStart = $listWithStudentAnswer['blankseparatorstart'];
859
        $separatorEnd = $listWithStudentAnswer['blankseparatorend'];
860
        // lets rebuild the sentence with [correct answer][student answer][answer is correct]
861
        $result = "";
862
        for ($i=0; $i < count($listWithStudentAnswer['commonwords']) - 1; $i++) {
863
            $result .= $listWithStudentAnswer['commonwords'][$i];
864
            $result .= $listWithStudentAnswer['tabwordsbracket'][$i];
865
            $result .= $separatorStart.$listWithStudentAnswer['studentanswer'][$i].$separatorEnd;
866
            $result .= $separatorStart.$listWithStudentAnswer['studentscore'][$i].$separatorEnd;
867
        }
868
        $result .= $listWithStudentAnswer['commonwords'][$i];
869
        $result .= "::";
870
        // add the system string
871
        $result .= $listWithStudentAnswer['systemstring'];
872
873
        return $result;
874
    }
875
876
    /**
877
     * This function is the same than the js one above getBlankSeparatorRegexp
878
     * @param string $inChar
879
     *
880
     * @return string
881
     */
882
    public static function escapeForRegexp($inChar)
883
    {
884
        $listChars = [
885
            ".",
886
            "+",
887
            "*",
888
            "?",
889
            "[",
890
            "^",
891
            "]",
892
            "$",
893
            "(",
894
            ")",
895
            "{",
896
            "}",
897
            "=",
898
            "!",
899
            ">",
900
            "|",
901
            ":",
902
            "-",
903
            ")",
904
        ];
905
906
        if (in_array($inChar, $listChars)) {
907
            return "\\".$inChar;
908
        } else {
909
            return $inChar;
910
        }
911
    }
912
913
    /**
914
     * return $text protected for use in regexp
915
     * @param string $text
916
     *
917
     * @return mixed
918
     */
919
    public static function getRegexpProtected($text)
920
    {
921
        $listRegexpCharacters = [
922
            "/",
923
            ".",
924
            "+",
925
            "*",
926
            "?",
927
            "[",
928
            "^",
929
            "]",
930
            "$",
931
            "(",
932
            ")",
933
            "{",
934
            "}",
935
            "=",
936
            "!",
937
            ">",
938
            "|",
939
            ":",
940
            "-",
941
            ")",
942
        ];
943
        $result = $text;
944
        for ($i=0; $i < count($listRegexpCharacters); $i++) {
945
            $result = str_replace($listRegexpCharacters[$i], "\\".$listRegexpCharacters[$i], $result);
946
        }
947
948
        return $result;
949
    }
950
951
952
    /**
953
     * This function must be the same than the js one getSeparatorFromNumber above
954
     * @return array
955
     */
956
    public static function getAllowedSeparator()
957
    {
958
        $fillBlanksAllowedSeparator = array(
959
            array('[', ']'),
960
            array('{', '}'),
961
            array('(', ')'),
962
            array('*', '*'),
963
            array('#', '#'),
964
            array('%', '%'),
965
            array('$', '$'),
966
        );
967
968
        return $fillBlanksAllowedSeparator;
969
    }
970
971
    /**
972
     * return the start separator for answer
973
     * @param string $number
974
     *
975
     * @return mixed
976
     */
977
    public static function getStartSeparator($number)
978
    {
979
        $listSeparators = self::getAllowedSeparator();
980
981
        return $listSeparators[$number][0];
982
    }
983
984
    /**
985
     * return the end separator for answer
986
     * @param string $number
987
     *
988
     * @return mixed
989
     */
990
    public static function getEndSeparator($number)
991
    {
992
        $listSeparators = self::getAllowedSeparator();
993
994
        return $listSeparators[$number][1];
995
    }
996
997
    /**
998
     * Return as a description text, array of allowed separators for question
999
     * eg: array("[...]", "(...)")
1000
     * @return array
1001
     */
1002
    public static function getAllowedSeparatorForSelect()
1003
    {
1004
        $listResults = array();
1005
        $fillBlanksAllowedSeparator = self::getAllowedSeparator();
1006
        for ($i=0; $i < count($fillBlanksAllowedSeparator); $i++) {
1007
            $listResults[] = $fillBlanksAllowedSeparator[$i][0]."...".$fillBlanksAllowedSeparator[$i][1];
1008
        }
1009
1010
        return $listResults;
1011
    }
1012
1013
    /**
1014
     * return the code number of the separator for the question
1015
     * @param string $startSeparator
1016
     * @param string $endSeparator
1017
     *
1018
     * @return int
1019
     */
1020
    public function getDefaultSeparatorNumber($startSeparator, $endSeparator)
1021
    {
1022
        $listSeparators = self::getAllowedSeparator();
1023
        $result = 0;
1024
        for ($i=0; $i < count($listSeparators); $i++) {
1025
            if ($listSeparators[$i][0] == $startSeparator &&
1026
                $listSeparators[$i][1] == $endSeparator
1027
            ) {
1028
                $result = $i;
1029
            }
1030
        }
1031
1032
        return $result;
1033
    }
1034
1035
    /**
1036
     * return the HTML display of the answer
1037
     * @param string $answer
1038
     * @param bool   $resultsDisabled
1039
     *
1040
     * @return string
1041
     */
1042
    public static function getHtmlDisplayForAnswer($answer, $resultsDisabled = false)
1043
    {
1044
        $result = "";
1045
        $listStudentAnswerInfo = self::getAnswerInfo($answer, true);
1046
1047
        // rebuild the answer with good HTML style
1048
        // this is the student answer, right or wrong
1049
        for ($i=0; $i < count($listStudentAnswerInfo['studentanswer']); $i++) {
1050
            if ($listStudentAnswerInfo['studentscore'][$i] == 1) {
1051
                $listStudentAnswerInfo['studentanswer'][$i] = self::getHtmlRightAnswer(
1052
                    $listStudentAnswerInfo['studentanswer'][$i],
1053
                    $listStudentAnswerInfo['tabwords'][$i],
1054
                    $resultsDisabled
1055
                );
1056
            } else {
1057
                $listStudentAnswerInfo['studentanswer'][$i] = self::getHtmlWrongAnswer(
1058
                    $listStudentAnswerInfo['studentanswer'][$i],
1059
                    $listStudentAnswerInfo['tabwords'][$i],
1060
                    $resultsDisabled
1061
                );
1062
            }
1063
        }
1064
1065
1066
        // rebuild the sentence with student answer inserted
1067
        for ($i=0; $i < count($listStudentAnswerInfo['commonwords']); $i++) {
1068
            $result .= isset($listStudentAnswerInfo['commonwords'][$i]) ? $listStudentAnswerInfo['commonwords'][$i] : '';
1069
            $result .= isset($listStudentAnswerInfo['studentanswer'][$i]) ? $listStudentAnswerInfo['studentanswer'][$i] : '';
1070
        }
1071
1072
        // the last common word (should be </p>)
1073
        $result .= isset($listStudentAnswerInfo['commonwords'][$i]) ? $listStudentAnswerInfo['commonwords'][$i] : '';
1074
1075
        return $result;
1076
    }
1077
1078
    /**
1079
     * return the HTML code of answer for correct and wrong answer
1080
     * @param string $answer
1081
     * @param string $correct
1082
     * @param string $right
1083
     * @param bool   $resultsDisabled
1084
     *
1085
     * @return string
1086
     */
1087
    public static function getHtmlAnswer($answer, $correct, $right, $resultsDisabled = false)
1088
    {
1089
        $style = "color: green";
1090
        if (!$right) {
1091
            $style = "color: red; text-decoration: line-through;";
1092
        }
1093
        $type = FillBlanks::getFillTheBlankAnswerType($correct);
1094
        switch ($type) {
1095
            case self::FILL_THE_BLANK_MENU:
1096
                $correctAnswerHtml = "";
1097
                $listPossibleAnswers = FillBlanks::getFillTheBlankMenuAnswers($correct, false);
1098
                $correctAnswerHtml .= "<span style='color: green'>".$listPossibleAnswers[0]."</span>";
1099
                $correctAnswerHtml .= " <span style='font-weight:normal'>(";
1100
                for ($i=1; $i < count($listPossibleAnswers); $i++) {
1101
                    $correctAnswerHtml .= $listPossibleAnswers[$i];
1102
                    if ($i != count($listPossibleAnswers) - 1) {
1103
                        $correctAnswerHtml .= " | ";
1104
                    }
1105
                }
1106
                $correctAnswerHtml .= ")</span>";
1107
                break;
1108
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
1109
                $listCorrects = explode("||", $correct);
1110
                $firstCorrect = $correct;
1111
                if (count($listCorrects) > 0) {
1112
                    $firstCorrect = $listCorrects[0];
1113
                }
1114
                $correctAnswerHtml = "<span style='color: green'>".$firstCorrect."</span>";
1115
                break;
1116
            case self::FILL_THE_BLANK_STANDARD:
1117
            default:
1118
                $correctAnswerHtml = "<span style='color: green'>".$correct."</span>";
1119
        }
1120
1121
        if ($resultsDisabled) {
1122
            $correctAnswerHtml = "<span title='".get_lang("ExerciseWithFeedbackWithoutCorrectionComment")."'> - </span>";
1123
        }
1124
1125
        $result = "<span style='border:1px solid black; border-radius:5px; padding:2px; font-weight:bold;'>";
1126
        $result .= "<span style='$style'>".$answer."</span>";
1127
        $result .= "&nbsp;<span style='font-size:120%;'>/</span>&nbsp;";
1128
        $result .= $correctAnswerHtml;
1129
        $result .= "</span>";
1130
1131
        return $result;
1132
    }
1133
1134
    /**
1135
     * return HTML code for correct answer
1136
     * @param string $answer
1137
     * @param string $correct
1138
     * @param bool   $resultsDisabled
1139
     *
1140
     * @return string
1141
     */
1142
    public static function getHtmlRightAnswer($answer, $correct, $resultsDisabled = false)
1143
    {
1144
        return self::getHtmlAnswer($answer, $correct, true, $resultsDisabled);
1145
    }
1146
1147
    /**
1148
     * return HTML code for wrong answer
1149
     * @param string $answer
1150
     * @param string $correct
1151
     * @param bool   $resultsDisabled
1152
     *
1153
     * @return string
1154
     */
1155
    public static function getHtmlWrongAnswer($answer, $correct, $resultsDisabled = false)
1156
    {
1157
        return self::getHtmlAnswer($answer, $correct, false, $resultsDisabled);
1158
    }
1159
}
1160