|
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) { |
|
|
|
|
|
|
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(/"/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(/ /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 |
|
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 : |
|
|
|
|
|
|
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 .= " <span style='font-size:120%;'>/</span> "; |
|
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
|
|
|
|
This check looks for calls to
isset(...)orempty()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.