Completed
Push — 1.10.x ( 748658...a788c0 )
by
unknown
63:31
created

FillBlanks::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
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
                        // this is a weird patch to avoid to reduce the size of input blank when you are writing in the ckeditor.
122
                            lainputsize = $("#samplesize\\\["+i+"\\\]").width() + 9;
123
                        }
124
125
                        if (document.getElementById("weighting["+i+"]")) {
126
                            var value = document.getElementById("weighting["+i+"]").value;
127
                        } else {
128
                            var value = "10";
129
                        }
130
                        fields += "<tr>";
131
                        fields += "<td>"+blanks[i]+"</td>";
132
                        fields += "<td><input style=\"width:35px\" value=\""+value+"\" type=\"text\" id=\"weighting["+i+"]\" name=\"weighting["+i+"]\" /></td>";
133
                        fields += "<td>";
134
                        fields += "<input type=\"button\" value=\"-\" onclick=\"changeInputSize(-1, "+i+")\">";
135
                        fields += "<input type=\"button\" value=\"+\" onclick=\"changeInputSize(1, "+i+")\">";
136
                        fields += "<input value=\""+blanks[i].substr(1, blanks[i].length - 2)+"\" style=\"width:"+lainputsize+"px\" disabled=disabled id=\"samplesize["+i+"]\"/>";
137
                        fields += "<input type=\"hidden\" id=\"sizeofinput["+i+"]\" name=\"sizeofinput["+i+"]\" value=\""+lainputsize+"\" \"/>";
138
                        fields += "</td>";
139
                        fields += "</tr>";
140
                        // enable the save button
141
                        $("button").removeAttr("disabled");
142
                        $("#defineoneblank").hide();
143
                    }
144
                }
145
                document.getElementById("blanks_weighting").innerHTML = fields + "</table></div></div>";
146
                if (firstTime) {
147
                    firstTime = false;
148
                ';
149
150
        if (isset($listAnswersInfo) && count($listAnswersInfo["tabweighting"]) > 0) {
151
152
            foreach ($listAnswersInfo["tabweighting"] as $i => $weighting) {
153
                echo 'document.getElementById("weighting['.$i.']").value = "'.$weighting.'";';
154
            }
155
            foreach ($listAnswersInfo["tabinputsize"] as $i => $sizeOfInput) {
156
                echo 'document.getElementById("sizeofinput['.$i.']").value = "'.$sizeOfInput.'";';
157
                echo '$("#samplesize\\\['.$i.'\\\]").width('.$sizeOfInput.');';
158
            }
159
        }
160
161
        echo '}
162
            }
163
            window.onload = updateBlanks;
164
165
            function getInputSize() {
166
                var outTabSize = new Array();
167
                $("input").each(function() {
168
                    if ($(this).attr("id") && $(this).attr("id").match(/samplesize/)) {
169
                        var tabidnum = $(this).attr("id").match(/\d+/);
170
                        var idnum = tabidnum[0];
171
                        var thewidth = $(this).next().attr("value");
172
                        tabInputSize[idnum] = thewidth;
173
                    }
174
                });
175
            }
176
177
            function changeInputSize(inCoef, inIdNum)
178
            {
179
                var currentWidth = $("#samplesize\\\["+inIdNum+"\\\]").width();
180
                var newWidth = currentWidth + inCoef * 20;
181
                newWidth = Math.max(20, newWidth);
182
                newWidth = Math.min(newWidth, 600);
183
                $("#samplesize\\\["+inIdNum+"\\\]").width(newWidth);
184
                $("#sizeofinput\\\["+inIdNum+"\\\]").attr("value", newWidth);
185
            }
186
187
            function removeForbiddenChars(inTxt) {
188
                outTxt = inTxt;
189
190
                outTxt = outTxt.replace(/&quot;/g, ""); // remove the   char
191
                outTxt = outTxt.replace(/\x22/g, ""); // remove the   char
192
                outTxt = outTxt.replace(/"/g, ""); // remove the   char
193
                outTxt = outTxt.replace(/\\\\/g, ""); // remove the \ char
194
                outTxt = outTxt.replace(/&nbsp;/g, " ");
195
                outTxt = outTxt.replace(/^ +/, "");
196
                outTxt = outTxt.replace(/ +$/, "");
197
                return outTxt;
198
            }
199
200
            function changeBlankSeparator()
201
            {
202
                var separatorNumber = $("#select_separator").val();
203
                var tabSeparator = getSeparatorFromNumber(separatorNumber);
204
                blankSeparatortStart = tabSeparator[0];
205
                blankSeparatortEnd = tabSeparator[1];
206
                blankSeparatortStartRegexp = getBlankSeparatorRegexp(blankSeparatortStart);
207
                blankSeparatortEndRegexp = getBlankSeparatorRegexp(blankSeparatortEnd);
208
                updateBlanks();
209
            }
210
211
            // this function is the same than the PHP one
212
            // if modify it modify the php one escapeForRegexp
213
            function getBlankSeparatorRegexp(inTxt)
214
            {
215
                var tabSpecialChar = new Array(".", "+", "*", "?", "[", "^", "]", "$", "(", ")",
216
                    "{", "}", "=", "!", "<", ">", "|", ":", "-", ")");
217
                for (var i=0; i < tabSpecialChar.length; i++) {
218
                    if (inTxt == tabSpecialChar[i]) {
219
                        return "\\\"+inTxt;
220
                    }
221
                }
222
                return inTxt;
223
            }
224
225
            // this function is the same than the PHP one
226
            // if modify it modify the php one getAllowedSeparator
227
            function getSeparatorFromNumber(innumber)
228
            {
229
                tabSeparator = new Array();
230
                tabSeparator[0] = new Array("[", "]");
231
                tabSeparator[1] = new Array("{", "}");
232
                tabSeparator[2] = new Array("(", ")");
233
                tabSeparator[3] = new Array("*", "*");
234
                tabSeparator[4] = new Array("#", "#");
235
                tabSeparator[5] = new Array("%", "%");
236
                tabSeparator[6] = new Array("$", "$");
237
                return tabSeparator[innumber];
238
            }
239
240
            function trimBlanksBetweenSeparator(inTxt, inSeparatorStart, inSeparatorEnd)
241
            {
242
                // blankSeparatortStartRegexp
243
                // blankSeparatortEndRegexp
244
                var result = inTxt
245
                result = result.replace(inSeparatorStart, "");
246
                result = result.replace(inSeparatorEnd, "");
247
                result = result.trim();
248
                return inSeparatorStart+result+inSeparatorEnd;
249
            }
250
        </script>';
251
252
        // answer
253
        $form->addElement('label', null, '<br /><br />'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank'));
254
        $form->addElement(
255
            'html_editor',
256
            'answer',
257
            Display::return_icon('fill_field.png'),
258
            ['id' => 'answer', 'onkeyup' => "javascript: updateBlanks(this);"],
259
            array('ToolbarSet' => 'TestQuestionDescription')
260
        );
261
        $form->addRule('answer',get_lang('GiveText'),'required');
262
263
        //added multiple answers
264
        $form->addElement('checkbox','multiple_answer','', get_lang('FillInBlankSwitchable'));
265
        $form->addElement(
266
            'select',
267
            'select_separator',
268
            get_lang("SelectFillTheBlankSeparator"),
269
            self::getAllowedSeparatorForSelect(),
270
            ' id="select_separator"   style="width:150px" onchange="changeBlankSeparator()" '
271
        );
272
        $form->addElement(
273
            'label',
274
            null,
275
            '<input type="button" onclick="updateBlanks()" value="'.get_lang('RefreshBlanks').'" class="btn btn-default" />'
276
        );
277
        $form->addElement('html','<div id="blanks_weighting"></div>');
278
279
        global $text;
280
        // setting the save button here and not in the question class.php
281
        $form->addElement('html','<div id="defineoneblank" style="color:#D04A66; margin-left:160px">'.get_lang('DefineBlanks').'</div>');
282
        $form->addButtonSave($text, 'submitQuestion');
283
284
        if (!empty($this->id)) {
285
            $form->setDefaults($defaults);
286
        } else {
287
            if ($this->isContent == 1) {
288
                $form->setDefaults($defaults);
289
            }
290
        }
291
    }
292
293
    /**
294
     * Function which creates the form to create/edit the answers of the question
295
     * @param FormValidator $form
296
     */
297
    public function processAnswersCreation($form)
298
    {
299
        $answer = $form->getSubmitValue('answer');
300
        // Due the ckeditor transform the elements to their HTML value
301
302
        //$answer = api_html_entity_decode($answer, ENT_QUOTES, $charset);
303
        //$answer = htmlentities(api_utf8_encode($answer));
304
305
        // remove the :: eventually written by the user
306
        $answer = str_replace('::', '', $answer);
307
308
        // remove starting and ending space and &nbsp;
309
        $answer = api_preg_replace("/\xc2\xa0/", " ", $answer);
310
311
        // start and end separator
312
313
        $blankStartSeparator = self::getStartSeparator($form->getSubmitValue('select_separator'));
314
        $blankEndSeparator = self::getEndSeparator($form->getSubmitValue('select_separator'));
315
        $blankStartSeparatorRegexp = self::escapeForRegexp($blankStartSeparator);
316
        $blankEndSeparatorRegexp = self::escapeForRegexp($blankEndSeparator);
317
318
        // remove spaces at the beginning and the end of text in square brackets
319
        $answer = preg_replace_callback(
320
            "/".$blankStartSeparatorRegexp."[^]]+".$blankEndSeparatorRegexp."/",
321
            function ($matches) use ($blankStartSeparator, $blankEndSeparator) {
322
                $matchingResult = $matches[0];
323
                $matchingResult = trim($matchingResult, $blankStartSeparator);
324
                $matchingResult = trim($matchingResult, $blankEndSeparator);
325
                $matchingResult = trim($matchingResult);
326
                // remove forbidden chars
327
                $matchingResult = str_replace("/\\/", "", $matchingResult);
328
                $matchingResult = str_replace('/"/', "", $matchingResult);
329
330
                return $blankStartSeparator.$matchingResult.$blankEndSeparator;
331
            },
332
            $answer
333
        );
334
335
        // get the blanks weightings
336
        $nb = preg_match_all(
337
            '/'.$blankStartSeparatorRegexp.'[^'.$blankStartSeparatorRegexp.']*'.$blankEndSeparatorRegexp.'/',
338
            $answer,
339
            $blanks
340
        );
341
342
        if (isset($_GET['editQuestion'])) {
343
            $this->weighting = 0;
344
        }
345
346
        /* if we have some [tobefound] in the text
347
        build the string to save the following in the answers table
348
        <p>I use a [computer] and a [pen].</p>
349
        becomes
350
        <p>I use a [computer] and a [pen].</p>::100,50:100,50@1
351
            ++++++++-------**
352
            --- -- --- -- -
353
            A B  (C) (D)(E)
354
        +++++++ : required, weighting of each words
355
        ------- : optional, input width to display, 200 if not present
356
        ** : equal @1 if "Allow answers order switches" has been checked, @ otherwise
357
        A : weighting for the word [computer]
358
        B : weighting for the word [pen]
359
        C : input width for the word [computer]
360
        D : input width for the word [pen]
361
        E : equal @1 if "Allow answers order switches" has been checked, @ otherwise
362
        */
363
        if ($nb > 0) {
364
            $answer .= '::';
365
            // weighting
366
            for ($i=0; $i < $nb; ++$i) {
367
                // enter the weighting of word $i
368
                $answer .= $form->getSubmitValue('weighting['.$i.']');
369
                // not the last word, add ","
370
                if ($i != $nb - 1) {
371
                    $answer .= ",";
372
                }
373
                // calculate the global weighting for the question
374
                $this -> weighting += $form->getSubmitValue('weighting['.$i.']');
375
            }
376
377
            // input width
378
            $answer .= ":";
379
            for ($i=0; $i < $nb; ++$i) {
380
                // enter the width of input for word $i
381
                $answer .= $form->getSubmitValue('sizeofinput['.$i.']');
382
                // not the last word, add ","
383
                if ($i != $nb - 1) {
384
                    $answer .= ",";
385
                }
386
            }
387
        }
388
389
        // write the blank separator code number
390
        // see function getAllowedSeparator
391
        /*
392
            0 [...]
393
            1 {...}
394
            2 (...)
395
            3 *...*
396
            4 #...#
397
            5 %...%
398
            6 $...$
399
         */
400
        $answer .= ":".$form->getSubmitValue('select_separator');
401
402
        // Allow answers order switches
403
        $is_multiple = $form -> getSubmitValue('multiple_answer');
404
        $answer.= '@'.$is_multiple;
405
406
        $this->save();
407
        $objAnswer = new Answer($this->id);
408
        $objAnswer->createAnswer($answer, 0, '', 0, 1);
409
        $objAnswer->save();
410
    }
411
412
    /**
413
     * @param null $feedback_type
414
     * @param null $counter
415
     * @param null $score
416
     * @return null|string
417
     */
418 View Code Duplication
    public function return_header($feedback_type = null, $counter = null, $score = null)
419
    {
420
        $header = parent::return_header($feedback_type, $counter, $score);
421
        $header .= '<table class="'.$this->question_table_class .'">
422
            <tr>
423
                <th>'.get_lang("Answer").'</th>
424
            </tr>';
425
426
        return $header;
427
    }
428
429
    /**
430
     * @param $separatorStartRegexp
431
     * @param $separatorEndRegexp
432
     * @param $correctItemRegexp
433
     * @param $questionId
434
     * @param $correctItem
435
     * @param $attributes
436
     * @param $answer
437
     * @param $listAnswersInfo
438
     * @param $displayForStudent
439
     * @return string
440
     */
441
    public static function getFillTheBlankHtml(
442
        $separatorStartRegexp,
443
        $separatorEndRegexp,
444
        $correctItemRegexp,
445
        $questionId,
446
        $correctItem,
447
        $attributes,
448
        $answer,
449
        $listAnswersInfo,
450
        $displayForStudent,
451
        $inBlankNumber
452
    ) {
453
        $result = "";
454
        $inTabTeacherSolution = $listAnswersInfo['tabwords'];
455
        $inTeacherSolution = $inTabTeacherSolution[$inBlankNumber];
456
        switch (self::getFillTheBlankAnswerType($inTeacherSolution)) {
457
            case self::FILL_THE_BLANK_MENU:
458
                $selected = "";
459
                // the blank menu
460
                $optionMenu = "";
461
                // display a menu from answer separated with |
462
                // if display for student, shuffle the correct answer menu
463
                $listMenu = self::getFillTheBlankMenuAnswers($inTeacherSolution, $displayForStudent);
464
                $result .= '<select name="choice['.$questionId.'][]">';
465
                for ($k=0; $k < count($listMenu); $k++) {
466
                    $selected = "";
467
                    if ($correctItem == $listMenu[$k]) {
468
                        $selected = " selected=selected ";
469
                    }
470
                    // if in teacher view, display the first item by default, which is the right answer
471
                    if ($k==0 && !$displayForStudent) {
472
                        $selected = " selected=selected ";
473
                    }
474
                    $optionMenu .= '<option '.$selected.' value="'.$listMenu[$k].'">'.$listMenu[$k].'</option>';
475
                }
476
                if ($selected == "") {
477
                    // no good answer have been found...
478
                    $selected = " selected=selected ";
479
                }
480
                $result .= "<option $selected value=''>--</option>";
481
                $result .= $optionMenu;
482
                $result .= '</select>';
483
                break;
484
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
485
                //no break
486
            case self::FILL_THE_BLANK_STANDARD:
487
            default:
488
                $result = Display::input('text', "choice[$questionId][]", $correctItem, $attributes);
489
                break;
490
        }
491
492
        return $result;
493
    }
494
495
    /**
496
     * Return an array with the different choices available
497
     * when the answers between bracket show as a menu
498
     * @param string $correctAnswer
499
     * @param bool $displayForStudent true if we want to shuffle the choices of the menu for students
500
     *
501
     * @return array
502
     */
503
    public static function getFillTheBlankMenuAnswers($correctAnswer, $displayForStudent)
504
    {
505
        // if $inDisplayForStudent, then shuffle the result array
506
        $listChoises = api_preg_split("/\|/", $correctAnswer);
507
        if ($displayForStudent) {
508
            shuffle($listChoises);
509
        }
510
511
        return $listChoises;
512
    }
513
514
    /**
515
     * Return the array index of the student answer
516
     * @param string $correctAnswer the menu Choice1|Choice2|Choice3
517
     * @param string $studentAnswer the student answer must be Choice1 or Choice2 or Choice3
518
     *
519
     * @return int  in the example 0 1 or 2 depending of the choice of the student
520
     */
521
    public static function getFillTheBlankMenuAnswerNum($correctAnswer, $studentAnswer)
522
    {
523
        $listChoices = self::getFillTheBlankMenuAnswers($correctAnswer, false);
524
        foreach ($listChoices as $num => $value) {
525
            if ($value == $studentAnswer) {
526
                return $num;
527
            }
528
        }
529
530
        // should not happened, because student choose the answer in a menu of possible answers
531
        return -1;
532
    }
533
534
535
    /**
536
     * Return the possible answer if the answer between brackets is a multiple choice menu
537
     * @param string $correctAnswer
538
     *
539
     * @return array
540
     */
541
    public static function getFillTheBlankSeveralAnswers($correctAnswer)
542
    {
543
        // is answer||Answer||response||Response , mean answer or Answer ...
544
        $listSeveral = api_preg_split("/\|\|/", $correctAnswer);
545
546
        return $listSeveral;
547
    }
548
549
    /**
550
     * Return true if student answer is right according to the correctAnswer
551
     * it is not as simple as equality, because of the type of Fill The Blank question
552
     * eg : studentAnswer = 'Un' and correctAnswer = 'Un||1||un'
553
     * @param string $studentAnswer [studentanswer] of the info array of the answer field
554
     * @param string $correctAnswer [tabwords] of the info array of the answer field
555
     *
556
     * @return bool
557
     */
558
    public static function isGoodStudentAnswer($studentAnswer, $correctAnswer)
559
    {
560
        switch (self::getFillTheBlankAnswerType($correctAnswer)) {
561
            case self::FILL_THE_BLANK_MENU:
562
                $listMenu = self::getFillTheBlankMenuAnswers($correctAnswer, false);
563
                $result = $listMenu[0] == $studentAnswer;
564
                break;
565
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
566
                // the answer must be one of the choice made
567
                $listSeveral = self::getFillTheBlankSeveralAnswers($correctAnswer);
568
                $result = in_array($studentAnswer, $listSeveral);
569
                break;
570
            case self::FILL_THE_BLANK_STANDARD:
571
            default:
572
                $result = $studentAnswer == $correctAnswer;
573
                break;
574
        }
575
576
        return $result;
577
    }
578
579
    /**
580
     * @param string $correctAnswer
581
     *
582
     * @return int
583
     */
584
    public static function getFillTheBlankAnswerType($correctAnswer)
585
    {
586
        if (api_strpos($correctAnswer, "|") && !api_strpos($correctAnswer, "||")) {
587
            return self::FILL_THE_BLANK_MENU;
588
        } elseif (api_strpos($correctAnswer, "||")) {
589
            return self::FILL_THE_BLANK_SEVERAL_ANSWER;
590
        } else {
591
            return self::FILL_THE_BLANK_STANDARD;
592
        }
593
    }
594
595
    /**
596
     * Return information about the answer
597
     * @param string $userAnswer the text of the answer of the question
598
     * @param bool   $isStudentAnswer true if it's a student answer false the empty question model
599
     *
600
     * @return array of information about the answer
601
     */
602
    public static function getAnswerInfo($userAnswer = "", $isStudentAnswer = false)
603
    {
604
        $listAnswerResults = array();
605
        $listAnswerResults['text'] = "";
606
        $listAnswerResults['wordsCount'] = 0;
607
        $listAnswerResults['tabwordsbracket'] = array();
608
        $listAnswerResults['tabwords'] = array();
609
        $listAnswerResults['tabweighting'] = array();
610
        $listAnswerResults['tabinputsize'] = array();
611
        $listAnswerResults['switchable'] = "";
612
        $listAnswerResults['studentanswer'] = array();
613
        $listAnswerResults['studentscore'] = array();
614
        $listAnswerResults['blankseparatornumber'] = 0;
615
        $listDoubleColon = array();
616
617
        api_preg_match("/(.*)::(.*)$/s", $userAnswer, $listResult);
618
619
        if (count($listResult) < 2) {
620
            $listDoubleColon[] = '';
621
            $listDoubleColon[] = '';
622
        } else {
623
            $listDoubleColon[] = $listResult[1];
624
            $listDoubleColon[] = $listResult[2];
625
        }
626
627
        $listAnswerResults['systemstring'] = $listDoubleColon[1];
628
629
        // make sure we only take the last bit to find special marks
630
        $listArobaseSplit = explode('@', $listDoubleColon[1]);
631
632
        if (count($listArobaseSplit) < 2) {
633
            $listArobaseSplit[1] = "";
634
        }
635
636
        // take the complete string except after the last '::'
637
        $listDetails = explode(":", $listArobaseSplit[0]);
638
639
        // < number of item after the ::[score]:[size]:[separator_id]@ , here there are 3
640
        if (count($listDetails) < 3) {
641
            $listWeightings = explode(',', $listDetails[0]);
642
            $listSizeOfInput = array();
643
            for ($i=0; $i < count($listWeightings); $i++) {
644
                $listSizeOfInput[] = 200;
645
            }
646
            $blankSeparatorNumber = 0;    // 0 is [...]
647
        } else {
648
            $listWeightings = explode(',', $listDetails[0]);
649
            $listSizeOfInput = explode(',', $listDetails[1]);
650
            $blankSeparatorNumber = $listDetails[2];
651
        }
652
653
        $listAnswerResults['text'] = $listDoubleColon[0];
654
        $listAnswerResults['tabweighting'] = $listWeightings;
655
        $listAnswerResults['tabinputsize'] = $listSizeOfInput;
656
        $listAnswerResults['switchable'] = $listArobaseSplit[1];
657
        $listAnswerResults['blankseparatorstart'] = self::getStartSeparator($blankSeparatorNumber);
658
        $listAnswerResults['blankseparatorend'] = self::getEndSeparator($blankSeparatorNumber);
659
        $listAnswerResults['blankseparatornumber'] = $blankSeparatorNumber;
660
661
        $blankCharStart = self::getStartSeparator($blankSeparatorNumber);
662
        $blankCharEnd = self::getEndSeparator($blankSeparatorNumber);
663
        $blankCharStartForRegexp = self::escapeForRegexp($blankCharStart);
664
        $blankCharEndForRegexp = self::escapeForRegexp($blankCharEnd);
665
666
        // get all blanks words
667
        $listAnswerResults['wordsCount'] = api_preg_match_all(
668
            '/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/',
669
            $listDoubleColon[0],
670
            $listWords
671
        );
672
673
        if ($listAnswerResults['wordsCount'] > 0) {
674
            $listAnswerResults['tabwordsbracket'] = $listWords[0];
675
            // remove [ and ] in string
676
            array_walk(
677
                $listWords[0],
678
                function (&$value, $key, $tabBlankChar) {
679
                    $trimChars = "";
680
                    for ($i=0; $i < count($tabBlankChar); $i++) {
681
                        $trimChars .= $tabBlankChar[$i];
682
                    }
683
                    $value = trim($value, $trimChars);
684
                },
685
                array($blankCharStart, $blankCharEnd)
686
            );
687
            $listAnswerResults['tabwords'] = $listWords[0];
688
        }
689
690
        // get all common words
691
        $commonWords = api_preg_replace(
692
            '/'.$blankCharStartForRegexp.'[^'.$blankCharEndForRegexp.']*'.$blankCharEndForRegexp.'/',
693
            "::",
694
            $listDoubleColon[0]
695
        );
696
697
        // if student answer, the second [] is the student answer,
698
        // the third is if student scored or not
699
        $listBrackets = array();
700
        $listWords =  array();
701
702
        if ($isStudentAnswer) {
703
            for ($i=0; $i < count($listAnswerResults['tabwords']); $i++) {
704
                $listBrackets[] = $listAnswerResults['tabwordsbracket'][$i];
705
                $listWords[] = $listAnswerResults['tabwords'][$i];
706
                if ($i+1 < count($listAnswerResults['tabwords'])) {
707
                    // should always be
708
                    $i++;
709
                }
710
                $listAnswerResults['studentanswer'][] = $listAnswerResults['tabwords'][$i];
711
                if ($i+1 < count($listAnswerResults['tabwords'])) {
712
                    // should always be
713
                    $i++;
714
                }
715
                $listAnswerResults['studentscore'][] = $listAnswerResults['tabwords'][$i];
716
            }
717
            $listAnswerResults['tabwords'] = $listWords;
718
            $listAnswerResults['tabwordsbracket'] = $listBrackets;
719
720
            // if we are in student view, we've got 3 times :::::: for common words
721
            $commonWords = api_preg_replace("/::::::/", "::", $commonWords);
722
        }
723
724
        $listAnswerResults['commonwords'] = explode("::", $commonWords);
725
726
        return $listAnswerResults;
727
    }
728
729
    /**
730
    * Return an array of student state answers for fill the blank questions
731
    * for each students that answered the question
732
    * -2  : didn't answer
733
    * -1  : student answer is wrong
734
    *  0  : student answer is correct
735
    * >0  : for fill the blank question with choice menu, is the index of the student answer (right answer indice is 0)
736
    *
737
    * @param $testId
738
    * @param $questionId
739
    * @param $studentsIdList
740
    * @param $startDate
741
    * @param $endDate
742
    * @param bool $useLastAnswerredAttempt
743
    * @return array
744
    * (
745
    *     [student_id] => Array
746
    *         (
747
    *             [first fill the blank for question] => -1
748
    *             [second fill the blank for question] => 2
749
    *             [third fill the blank for question] => -1
750
    *         )
751
    * )
752
    */
753
    public static function getFillTheBlankTabResult($testId, $questionId, $studentsIdList, $startDate, $endDate, $useLastAnswerredAttempt = true) {
754
755
       $tblTrackEAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
756
       $tblTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
757
       $courseId = api_get_course_int_id();
758
759
       require_once api_get_path(SYS_PATH).'main/exercice/fill_blanks.class.php';
760
761
       // request to have all the answers of student for this question
762
       // student may have doing it several time
763
       // student may have not answered the bracket id, in this case, is result of the answer is empty
764
765
       // we got the less recent attempt first
766
       $sql = '
767
           SELECT * FROM '.$tblTrackEAttempt.' tea
768
769
           LEFT JOIN '.$tblTrackEExercise.' tee
770
           ON tee.exe_id = tea.exe_id
771
           AND tea.c_id = '.$courseId.'
772
           AND exe_exo_id = '.$testId.'
773
774
           WHERE tee.c_id = '.$courseId.'
775
           AND question_id = '.$questionId.'
776
           AND tea.user_id IN ('.implode(',', $studentsIdList).')
777
           AND tea.tms >= "'.$startDate.'"
778
           AND tea.tms <= "'.$endDate.'"
779
           ORDER BY user_id, tea.exe_id;
780
       ';
781
782
       $res = Database::query($sql);
783
       $tabUserResult = array();
784
       $bracketNumber = 0;
785
       // foreach attempts for all students starting with his older attempt
786
       while ($data = Database::fetch_array($res)) {
787
           $tabAnswer = FillBlanks::getAnswerInfo($data['answer'], true);
788
789
           // for each bracket to find in this question
790
           foreach ($tabAnswer['studentanswer'] as $bracketNumber => $studentAnswer) {
791
792
               if ($tabAnswer['studentanswer'][$bracketNumber] != '') {
793
                   // student has answered this bracket, cool
794
                   switch (FillBlanks::getFillTheBlankAnswerType($tabAnswer['tabwords'][$bracketNumber])) {
795
                       case self::FILL_THE_BLANK_MENU :
796
                           // get the indice of the choosen answer in the menu
797
                           // we know that the right answer is the first entry of the menu, ie 0
798
                           // (remember, menu entries are shuffled when taking the test)
799
                           $tabUserResult[$data['user_id']][$bracketNumber] = FillBlanks::getFillTheBlankMenuAnswerNum($tabAnswer['tabwords'][$bracketNumber], $tabAnswer['studentanswer'][$bracketNumber]);
800
                           break;
801
                       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...
802
                           if (FillBlanks::isGoodStudentAnswer($tabAnswer['studentanswer'][$bracketNumber], $tabAnswer['tabwords'][$bracketNumber])) {
803
                               $tabUserResult[$data['user_id']][$bracketNumber] = 0;   //  right answer
804
                           } else {
805
                               $tabUserResult[$data['user_id']][$bracketNumber] = -1;  // wrong answer
806
                           }
807
                   }
808
               } else {
809
                   // student didn't answer this bracket
810
                   if ($useLastAnswerredAttempt) {
811
                       // if we take into account the last answered attempt
812
                       if (!isset($tabUserResult[$data['user_id']][$bracketNumber])) {
813
                           $tabUserResult[$data['user_id']][$bracketNumber] = -2;      // not answered
814
                       }
815
                   } else {
816
                       // we take the last attempt, even if the student answer the question before
817
                       $tabUserResult[$data['user_id']][$bracketNumber] = -2;      // not answered
818
                   }
819
               }
820
           }
821
822
823
       }
824
       return $tabUserResult;
825
    }
826
827
828
829
    /**
830
     * Return the number of student that give at leat an answer in the fill the blank test
831
     * @param $resultList
832
     * @return int
833
     */
834
    public static function getNbResultFillBlankAll($resultList)
835
    {
836
        $outRes = 0;
837
        // for each student in group
838
        foreach($resultList as $userId => $tabValue) {
839
            $trouve = false;
840
            // for each bracket, if student has at leat one answer ( choice > -2) then he pass the question
841
            foreach($tabValue as $i => $choice) {
842
                if ($choice > -2 && !$trouve) {
843
                    $outRes++;
844
                    $trouve = true;
845
                }
846
            }
847
        }
848
        return $outRes;
849
    }
850
851
    /**
852
     * Replace the occurrence of blank word with [correct answer][student answer][answer is correct]
853
     * @param array $listWithStudentAnswer
854
     *
855
     * @return string
856
     */
857
    public static function getAnswerInStudentAttempt($listWithStudentAnswer)
858
    {
859
        $separatorStart = $listWithStudentAnswer['blankseparatorstart'];
860
        $separatorEnd = $listWithStudentAnswer['blankseparatorend'];
861
        // lets rebuild the sentence with [correct answer][student answer][answer is correct]
862
        $result = "";
863
        for ($i=0; $i < count($listWithStudentAnswer['commonwords']) - 1; $i++) {
864
            $result .= $listWithStudentAnswer['commonwords'][$i];
865
            $result .= $listWithStudentAnswer['tabwordsbracket'][$i];
866
            $result .= $separatorStart.$listWithStudentAnswer['studentanswer'][$i].$separatorEnd;
867
            $result .= $separatorStart.$listWithStudentAnswer['studentscore'][$i].$separatorEnd;
868
        }
869
        $result .= $listWithStudentAnswer['commonwords'][$i];
870
        $result .= "::";
871
        // add the system string
872
        $result .= $listWithStudentAnswer['systemstring'];
873
874
        return $result;
875
    }
876
877
    /**
878
     * This function is the same than the js one above getBlankSeparatorRegexp
879
     * @param string $inChar
880
     *
881
     * @return string
882
     */
883
    public static function escapeForRegexp($inChar)
884
    {
885
        $listChars = [
886
            ".",
887
            "+",
888
            "*",
889
            "?",
890
            "[",
891
            "^",
892
            "]",
893
            "$",
894
            "(",
895
            ")",
896
            "{",
897
            "}",
898
            "=",
899
            "!",
900
            ">",
901
            "|",
902
            ":",
903
            "-",
904
            ")",
905
        ];
906
907
        if (in_array($inChar, $listChars)) {
908
            return "\\".$inChar;
909
        } else {
910
            return $inChar;
911
        }
912
    }
913
914
    /**
915
     * return $text protected for use in regexp
916
     * @param string $text
917
     *
918
     * @return mixed
919
     */
920
    public static function getRegexpProtected($text)
921
    {
922
        $listRegexpCharacters = [
923
            "/",
924
            ".",
925
            "+",
926
            "*",
927
            "?",
928
            "[",
929
            "^",
930
            "]",
931
            "$",
932
            "(",
933
            ")",
934
            "{",
935
            "}",
936
            "=",
937
            "!",
938
            ">",
939
            "|",
940
            ":",
941
            "-",
942
            ")",
943
        ];
944
        $result = $text;
945
        for ($i=0; $i < count($listRegexpCharacters); $i++) {
946
            $result = str_replace($listRegexpCharacters[$i], "\\".$listRegexpCharacters[$i], $result);
947
        }
948
949
        return $result;
950
    }
951
952
953
    /**
954
     * This function must be the same than the js one getSeparatorFromNumber above
955
     * @return array
956
     */
957
    public static function getAllowedSeparator()
958
    {
959
        $fillBlanksAllowedSeparator = array(
960
            array('[', ']'),
961
            array('{', '}'),
962
            array('(', ')'),
963
            array('*', '*'),
964
            array('#', '#'),
965
            array('%', '%'),
966
            array('$', '$'),
967
        );
968
969
        return $fillBlanksAllowedSeparator;
970
    }
971
972
    /**
973
     * return the start separator for answer
974
     * @param string $number
975
     *
976
     * @return mixed
977
     */
978
    public static function getStartSeparator($number)
979
    {
980
        $listSeparators = self::getAllowedSeparator();
981
982
        return $listSeparators[$number][0];
983
    }
984
985
    /**
986
     * return the end separator for answer
987
     * @param string $number
988
     *
989
     * @return mixed
990
     */
991
    public static function getEndSeparator($number)
992
    {
993
        $listSeparators = self::getAllowedSeparator();
994
995
        return $listSeparators[$number][1];
996
    }
997
998
    /**
999
     * Return as a description text, array of allowed separators for question
1000
     * eg: array("[...]", "(...)")
1001
     * @return array
1002
     */
1003
    public static function getAllowedSeparatorForSelect()
1004
    {
1005
        $listResults = array();
1006
        $fillBlanksAllowedSeparator = self::getAllowedSeparator();
1007
        for ($i=0; $i < count($fillBlanksAllowedSeparator); $i++) {
1008
            $listResults[] = $fillBlanksAllowedSeparator[$i][0]."...".$fillBlanksAllowedSeparator[$i][1];
1009
        }
1010
1011
        return $listResults;
1012
    }
1013
1014
    /**
1015
     * return the code number of the separator for the question
1016
     * @param string $startSeparator
1017
     * @param string $endSeparator
1018
     *
1019
     * @return int
1020
     */
1021
    public function getDefaultSeparatorNumber($startSeparator, $endSeparator)
1022
    {
1023
        $listSeparators = self::getAllowedSeparator();
1024
        $result = 0;
1025
        for ($i=0; $i < count($listSeparators); $i++) {
1026
            if ($listSeparators[$i][0] == $startSeparator &&
1027
                $listSeparators[$i][1] == $endSeparator
1028
            ) {
1029
                $result = $i;
1030
            }
1031
        }
1032
1033
        return $result;
1034
    }
1035
1036
    /**
1037
     * return the HTML display of the answer
1038
     * @param string $answer
1039
     * @param bool   $resultsDisabled
1040
     * @param bool $showTotalScoreAndUserChoices
1041
     *
1042
     * @return string
1043
     */
1044
    public static function getHtmlDisplayForAnswer($answer, $resultsDisabled = false, $showTotalScoreAndUserChoices = false)
1045
    {
1046
        $result = '';
1047
        $listStudentAnswerInfo = self::getAnswerInfo($answer, true);
1048
1049 View Code Duplication
        if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
1050
            if ($showTotalScoreAndUserChoices) {
1051
                $resultsDisabled = true;
1052
            } else {
1053
                $resultsDisabled = false;
1054
            }
1055
        }
1056
1057
        // rebuild the answer with good HTML style
1058
        // this is the student answer, right or wrong
1059
        for ($i=0; $i < count($listStudentAnswerInfo['studentanswer']); $i++) {
1060
            if ($listStudentAnswerInfo['studentscore'][$i] == 1) {
1061
                $listStudentAnswerInfo['studentanswer'][$i] = self::getHtmlRightAnswer(
1062
                    $listStudentAnswerInfo['studentanswer'][$i],
1063
                    $listStudentAnswerInfo['tabwords'][$i],
1064
                    $resultsDisabled
1065
                );
1066
            } else {
1067
                $listStudentAnswerInfo['studentanswer'][$i] = self::getHtmlWrongAnswer(
1068
                    $listStudentAnswerInfo['studentanswer'][$i],
1069
                    $listStudentAnswerInfo['tabwords'][$i],
1070
                    $resultsDisabled
1071
                );
1072
            }
1073
        }
1074
1075
1076
        // rebuild the sentence with student answer inserted
1077
        for ($i=0; $i < count($listStudentAnswerInfo['commonwords']); $i++) {
1078
            $result .= isset($listStudentAnswerInfo['commonwords'][$i]) ? $listStudentAnswerInfo['commonwords'][$i] : '';
1079
            $result .= isset($listStudentAnswerInfo['studentanswer'][$i]) ? $listStudentAnswerInfo['studentanswer'][$i] : '';
1080
        }
1081
1082
        // the last common word (should be </p>)
1083
        $result .= isset($listStudentAnswerInfo['commonwords'][$i]) ? $listStudentAnswerInfo['commonwords'][$i] : '';
1084
1085
        return $result;
1086
    }
1087
1088
    /**
1089
     * return the HTML code of answer for correct and wrong answer
1090
     * @param string $answer
1091
     * @param string $correct
1092
     * @param string $right
1093
     * @param bool   $resultsDisabled
1094
     *
1095
     * @return string
1096
     */
1097
    public static function getHtmlAnswer($answer, $correct, $right, $resultsDisabled = false)
1098
    {
1099
        $style = "color: green";
1100
        if (!$right) {
1101
            $style = "color: red; text-decoration: line-through;";
1102
        }
1103
        $type = FillBlanks::getFillTheBlankAnswerType($correct);
1104
        switch ($type) {
1105
            case self::FILL_THE_BLANK_MENU:
1106
                $correctAnswerHtml = '';
1107
                $listPossibleAnswers = FillBlanks::getFillTheBlankMenuAnswers($correct, false);
1108
                $correctAnswerHtml .= "<span style='color: green'>".$listPossibleAnswers[0]."</span>";
1109
                $correctAnswerHtml .= " <span style='font-weight:normal'>(";
1110
                for ($i=1; $i < count($listPossibleAnswers); $i++) {
1111
                    $correctAnswerHtml .= $listPossibleAnswers[$i];
1112
                    if ($i != count($listPossibleAnswers) - 1) {
1113
                        $correctAnswerHtml .= " | ";
1114
                    }
1115
                }
1116
                $correctAnswerHtml .= ")</span>";
1117
                break;
1118
            case self::FILL_THE_BLANK_SEVERAL_ANSWER:
1119
                $listCorrects = explode("||", $correct);
1120
                $firstCorrect = $correct;
1121
                if (count($listCorrects) > 0) {
1122
                    $firstCorrect = $listCorrects[0];
1123
                }
1124
                $correctAnswerHtml = "<span style='color: green'>".$firstCorrect."</span>";
1125
                break;
1126
            case self::FILL_THE_BLANK_STANDARD:
1127
            default:
1128
                $correctAnswerHtml = "<span style='color: green'>".$correct."</span>";
1129
        }
1130
1131
        if ($resultsDisabled) {
1132
            $correctAnswerHtml = "<span title='".get_lang("ExerciseWithFeedbackWithoutCorrectionComment")."'> - </span>";
1133
        }
1134
1135
        $result = "<span style='border:1px solid black; border-radius:5px; padding:2px; font-weight:bold;'>";
1136
        $result .= "<span style='$style'>".$answer."</span>";
1137
        $result .= "&nbsp;<span style='font-size:120%;'>/</span>&nbsp;";
1138
        $result .= $correctAnswerHtml;
1139
        $result .= "</span>";
1140
1141
        return $result;
1142
    }
1143
1144
    /**
1145
     * return HTML code for correct answer
1146
     * @param string $answer
1147
     * @param string $correct
1148
     * @param bool   $resultsDisabled
1149
     *
1150
     * @return string
1151
     */
1152
    public static function getHtmlRightAnswer($answer, $correct, $resultsDisabled = false)
1153
    {
1154
        return self::getHtmlAnswer($answer, $correct, true, $resultsDisabled);
1155
    }
1156
1157
    /**
1158
     * return HTML code for wrong answer
1159
     * @param string $answer
1160
     * @param string $correct
1161
     * @param bool   $resultsDisabled
1162
     *
1163
     * @return string
1164
     */
1165
    public static function getHtmlWrongAnswer($answer, $correct, $resultsDisabled = false)
1166
    {
1167
        return self::getHtmlAnswer($answer, $correct, false, $resultsDisabled);
1168
    }
1169
}
1170