Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
50.70% |
181 / 357 |
|
25.00% |
3 / 12 |
CRAP | |
0.00% |
0 / 1 |
Question | |
50.70% |
181 / 357 |
|
25.00% |
3 / 12 |
2488.49 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
setState | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
19 | |||
getState | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
5.02 | |||
parseHeader | |
64.71% |
11 / 17 |
|
0.00% |
0 / 1 |
3.40 | |||
parseParameters | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
8 | |||
checkRequestOrder | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
8.02 | |||
singleChoiceParseObject | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
multipleChoiceParseObject | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
basicTypeParseObject | |
0.00% |
0 / 130 |
|
0.00% |
0 / 1 |
1980 | |||
parseCategories | |
88.89% |
16 / 18 |
|
0.00% |
0 / 1 |
5.03 | |||
textFieldParseObject | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
30 | |||
parseTextField | |
92.17% |
106 / 115 |
|
0.00% |
0 / 1 |
40.77 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Quiz; |
4 | |
5 | use MediaWiki\Html\TemplateParser; |
6 | use MediaWiki\Parser\Parser; |
7 | use MediaWiki\Request\WebRequest; |
8 | use MediaWiki\Xml\Xml; |
9 | use UnexpectedValueException; |
10 | |
11 | class Question { |
12 | |
13 | /** @var WebRequest */ |
14 | private $mRequest; |
15 | |
16 | /** @var int */ |
17 | private $mQuestionId; |
18 | |
19 | /** @var bool */ |
20 | private $mBeingCorrected; |
21 | |
22 | /** @var bool */ |
23 | private $mCaseSensitive; |
24 | |
25 | /** @var bool */ |
26 | private $shuffleAnswers; |
27 | |
28 | /** @var Parser */ |
29 | private $mParser; |
30 | |
31 | /** @var string */ |
32 | private $mState; |
33 | |
34 | /** @var string */ |
35 | private $mProposalPattern = '`^([+-]) ?(.*)`'; |
36 | |
37 | /** @var string */ |
38 | private $mCorrectionPattern = '`^\|\|(.*)`'; |
39 | |
40 | /** @var string */ |
41 | private $mCategoryPattern = '`^\|(\n|[^\|].*\n)`'; |
42 | |
43 | /** @var string */ |
44 | private $mTextFieldPattern = '`\{ ([^\}]*?)(_([\d]*) ?| )\}`'; |
45 | |
46 | /** @var string */ |
47 | public $mType = 'multipleChoice'; |
48 | |
49 | /** @var int */ |
50 | public $mCoef = 1; |
51 | |
52 | /** |
53 | * @param bool $beingCorrected Identifier for quiz being corrected. |
54 | * @param bool $caseSensitive Identifier for case sensitive inputs. |
55 | * @param int $questionId the Identifier of the question used to generate input names. |
56 | * @param bool $shufAns Identifier if answers are supposed to be shuffled. |
57 | * @param Parser $parser Parser the wikitext parser. |
58 | */ |
59 | public function __construct( $beingCorrected, $caseSensitive, $questionId, $shufAns, $parser ) { |
60 | global $wgRequest; |
61 | $this->mRequest = $wgRequest; |
62 | $this->mQuestionId = $questionId; |
63 | $this->mBeingCorrected = $beingCorrected; |
64 | $this->mCaseSensitive = $caseSensitive; |
65 | $this->shuffleAnswers = $shufAns; |
66 | $this->mParser = $parser; |
67 | $this->mState = ( $beingCorrected ) ? 'NA' : ''; |
68 | } |
69 | |
70 | /** |
71 | * Mutator of the question state |
72 | * |
73 | * @param string $pState |
74 | */ |
75 | private function setState( $pState ) { |
76 | if ( $pState == 'error' || ( $pState == 'incorrect' && $this->mState != 'error' ) || |
77 | ( $pState == 'correct' && ( $this->mState == 'NA' || $this->mState == 'na_correct' ) ) || |
78 | ( $pState == 'na_incorrect' && ( $this->mState == 'NA' || $this->mState == 'na_correct' ) ) || |
79 | ( $pState == 'na_correct' && ( $this->mState == 'NA' ) ) || |
80 | ( $pState == 'new_NA' && ( $this->mState == 'NA' || $this->mState == 'correct' ) ) |
81 | ) { |
82 | $this->mState = $pState; |
83 | } |
84 | |
85 | // Special cases |
86 | if ( ( $pState == 'na_incorrect' && $this->mState == 'correct' ) || |
87 | ( $pState == 'correct' && $this->mState == 'na_incorrect' ) |
88 | ) { |
89 | $this->mState = 'incorrect'; |
90 | } |
91 | } |
92 | |
93 | /** |
94 | * Accessor of the question state. |
95 | * |
96 | * @return string |
97 | */ |
98 | public function getState() { |
99 | if ( $this->mState == 'na_correct' ) { |
100 | return 'correct'; |
101 | } elseif ( $this->mState == 'na_incorrect' || $this->mState == 'new_NA' ) { |
102 | return 'NA'; |
103 | } else { |
104 | return $this->mState; |
105 | } |
106 | } |
107 | |
108 | /** |
109 | * Convert the question's header into HTML. |
110 | * |
111 | * @param string $input the quiz header in quiz syntax. |
112 | * @return string |
113 | */ |
114 | public function parseHeader( $input ) { |
115 | $parametersPattern = '`\n\|([^\|].*)\s*$`'; |
116 | $input = preg_replace_callback( |
117 | $parametersPattern, |
118 | [ $this, 'parseParameters' ], |
119 | $input |
120 | ); |
121 | $splitHeaderPattern = '`\n\|\|`'; |
122 | $unparsedHeader = preg_split( $splitHeaderPattern, $input ); |
123 | $output = $this->mParser->recursiveTagParse( trim( $unparsedHeader[0] ) ); |
124 | if ( array_key_exists( 1, $unparsedHeader ) && $this->mBeingCorrected ) { |
125 | $output .= "\n"; |
126 | $output .= '<table class="correction"><tr>'; |
127 | $output .= '<td>→</td><td>'; |
128 | $output .= $this->mParser->recursiveTagParse( trim( $unparsedHeader[1] ) ); |
129 | $output .= '</td>'; |
130 | $output .= '</tr></table>'; |
131 | } |
132 | return $output; |
133 | } |
134 | |
135 | /** |
136 | * Determine the question's parameters. |
137 | * |
138 | * @param array $matches elements matching $parametersPattern |
139 | * $matches[0] are the potential question parameters. |
140 | */ |
141 | private function parseParameters( $matches ) { |
142 | $typePattern = '`t[yi]p[eo]?="(.*?)"`'; |
143 | if ( preg_match( $typePattern, $matches[1], $type ) ) { |
144 | // List of all object type code and the correspondant question type. |
145 | switch ( $type[1] ) { |
146 | case '{}': |
147 | $this->mType = 'textField'; |
148 | break; |
149 | case '()': |
150 | $this->mType = 'singleChoice'; |
151 | break; |
152 | case '[]': |
153 | $this->mType = 'multipleChoice'; |
154 | break; |
155 | } |
156 | } |
157 | $coefPattern = '`[ck]oef="(.*?)"`'; |
158 | if ( preg_match( $coefPattern, $matches[1], $coef ) && |
159 | is_numeric( $coef[1] ) && $coef[1] > 0 |
160 | ) { |
161 | $this->mCoef = (int)$coef[1]; |
162 | } |
163 | } |
164 | |
165 | /** |
166 | * Check order obtained from request |
167 | * |
168 | * @param string $order The order obtained from request |
169 | * @param int $proposalIndex Contains the index of last Proposal |
170 | * |
171 | * @return int |
172 | */ |
173 | private function checkRequestOrder( $order, $proposalIndex ) { |
174 | $tempOrder = explode( ' ', $order ); |
175 | |
176 | // Check the number of order matches number of proposals |
177 | if ( count( $tempOrder ) !== $proposalIndex + 1 ) { |
178 | return 1; |
179 | } |
180 | |
181 | // Check if each value is numeric and is within 0 to proposalIndex rannge |
182 | foreach ( $tempOrder as $orderVal ) { |
183 | if ( !is_numeric( $orderVal ) ) { |
184 | return 1; |
185 | } |
186 | if ( $orderVal < 0 || $orderVal > $proposalIndex ) { |
187 | return 1; |
188 | } |
189 | } |
190 | '@phan-var int[] $tempOrder'; |
191 | |
192 | // Check for repeated values |
193 | $orderChecker = array_fill( 0, $proposalIndex + 1, 0 ); |
194 | foreach ( $tempOrder as $orderVal ) { |
195 | if ( $orderChecker[ $orderVal ] !== 1 ) { |
196 | $orderChecker[ $orderVal ] = 1; |
197 | } else { |
198 | return 1; |
199 | } |
200 | } |
201 | return 0; |
202 | } |
203 | |
204 | /** |
205 | * Transmit a single choice object to the basic type parser. |
206 | * |
207 | * @param string $input A question object in quiz syntax. |
208 | * |
209 | * @return string A question object in HTML. |
210 | */ |
211 | public function singleChoiceParseObject( $input ) { |
212 | return $this->basicTypeParseObject( $input, 'radio' ); |
213 | } |
214 | |
215 | /** |
216 | * Transmit a multiple choice object to the basic type parser. |
217 | * |
218 | * @param string $input A question object in quiz syntax. |
219 | * |
220 | * @return string A question object in HTML. |
221 | */ |
222 | public function multipleChoiceParseObject( $input ) { |
223 | return $this->basicTypeParseObject( $input, 'checkbox' ); |
224 | } |
225 | |
226 | /** |
227 | * Convert a basic type object from quiz syntax to HTML. |
228 | * |
229 | * @param string $input A question object in quiz syntax |
230 | * @param string $inputType |
231 | * |
232 | * @return string A question object in HTML. |
233 | */ |
234 | private function basicTypeParseObject( $input, $inputType ) { |
235 | $output = preg_match( $this->mCategoryPattern, $input, $matches ) |
236 | ? $this->parseCategories( $matches[1] ) |
237 | : ''; |
238 | $raws = preg_split( '`\n`s', $input, -1, PREG_SPLIT_NO_EMPTY ); |
239 | // Parameters used in some special cases. |
240 | $expectOn = 0; |
241 | $attemptChecker = 0; |
242 | $lines = []; |
243 | $proposalIndex = -1; |
244 | foreach ( $raws as $proposalId => $raw ) { |
245 | $text = null; |
246 | $colSpan = ''; |
247 | $rawClass = ''; |
248 | $signesOutput = ''; |
249 | if ( preg_match( $this->mProposalPattern, $raw, $matches ) ) { |
250 | $proposalIndex++; |
251 | $rawClass = 'proposal'; |
252 | // Insulate the proposal signes. |
253 | $text = array_pop( $matches ); |
254 | array_shift( $matches ); |
255 | // Determine a type ID, according to the questionType and the number of signes. |
256 | $typeId = substr( $this->mType, 0, 1 ); |
257 | $typeId .= array_key_exists( 1, $matches ) ? 'c' : 'n'; |
258 | foreach ( $matches as $signId => $sign ) { |
259 | $attribs = []; |
260 | $attribs['type'] = $inputType; |
261 | $attribs['class'] = 'check'; |
262 | // Determine the input's name and value. |
263 | switch ( $typeId ) { |
264 | case 'mn': |
265 | $name = 'q' . $this->mQuestionId . 'p' . $proposalId; |
266 | $value = 'p' . $proposalId; |
267 | break; |
268 | case 'sn': |
269 | $name = 'q' . $this->mQuestionId; |
270 | $value = 'p' . $proposalId; |
271 | break; |
272 | case 'mc': |
273 | $name = 'q' . $this->mQuestionId . 'p' . $proposalId . 's' . $signId; |
274 | $value = 's' . $signId; |
275 | break; |
276 | case 'sc': |
277 | $name = 'q' . $this->mQuestionId . 'p' . $proposalId; |
278 | $value = 's' . $signId; |
279 | break; |
280 | default: |
281 | throw new UnexpectedValueException( 'unknown typeId' ); |
282 | } |
283 | // Determine if the input had to be checked. |
284 | if ( $this->mBeingCorrected && $this->mRequest->getVal( $name ) == $value ) { |
285 | $attribs['checked'] = 'checked'; |
286 | } |
287 | // Determine if the proposal has been attempted |
288 | $attemptChecker = ( $this->mBeingCorrected && $this->mRequest->getVal( $name ) === $value ) |
289 | ? 1 |
290 | : 0; |
291 | // Determine the color of the cell and modify the state of the question. |
292 | switch ( $sign ) { |
293 | case '+': |
294 | $expectOn++; |
295 | // A single choice object with many correct proposal is a syntax error. |
296 | if ( $this->mType == 'singleChoice' && $expectOn > 1 ) { |
297 | $this->setState( 'error' ); |
298 | $attribs['class'] .= ' error'; |
299 | $attribs['title'] = wfMessage( 'quiz_legend_error' )->text(); |
300 | $attribs['disabled'] = 'disabled'; |
301 | } |
302 | if ( $this->mBeingCorrected ) { |
303 | if ( array_key_exists( 'checked', $attribs ) ) { |
304 | $this->setState( 'correct' ); |
305 | $attribs['class'] .= ' correct'; |
306 | $attribs['title'] = wfMessage( 'quiz_legend_correct' )->text(); |
307 | } else { |
308 | $this->setState( 'na_incorrect' ); |
309 | $attribs['class'] .= ' incorrect'; |
310 | $attribs['title'] = wfMessage( 'quiz_legend_incorrect' )->text(); |
311 | } |
312 | } |
313 | break; |
314 | case '-': |
315 | if ( $this->mBeingCorrected ) { |
316 | if ( array_key_exists( 'checked', $attribs ) ) { |
317 | $this->setState( 'incorrect' ); |
318 | $attribs['class'] .= ' incorrect'; |
319 | $attribs['title'] = wfMessage( 'quiz_legend_incorrect' )->text(); |
320 | } else { |
321 | $this->setState( 'na_correct' ); |
322 | } |
323 | } |
324 | break; |
325 | default: |
326 | $this->setState( 'error' ); |
327 | $attribs['class'] .= ' error'; |
328 | $attribs['title'] = wfMessage( 'quiz_legend_error' )->text(); |
329 | $attribs['disabled'] = 'disabled'; |
330 | break; |
331 | } |
332 | $signesOutput .= '<td class="sign">'; |
333 | $signesOutput .= Xml::input( $name, false, $value, $attribs ); |
334 | $signesOutput .= '</td>'; |
335 | } |
336 | if ( $typeId == 'sc' ) { |
337 | // A single choice object with no correct proposal is a syntax error. |
338 | if ( $expectOn == 0 ) { |
339 | $this->setState( 'error' ); |
340 | } |
341 | $expectOn = 0; |
342 | } |
343 | // If the proposal text is empty, the question has a syntax error. |
344 | if ( trim( $text ) == '' ) { |
345 | $text = '???'; |
346 | $this->setState( 'error' ); |
347 | } |
348 | } elseif ( preg_match( $this->mCorrectionPattern, $raw, $matches ) && |
349 | $this->mBeingCorrected |
350 | ) { |
351 | $rawClass = $attemptChecker ? 'correction selected' : 'correction unselected'; |
352 | $text = array_pop( $matches ); |
353 | $signesOutput = '<td>→</td>'; |
354 | // Hacks to avoid counting the number of signes. |
355 | $colSpan = ' colspan="13"'; |
356 | } |
357 | if ( $text !== null ) { |
358 | $lineOutput = '<tr class="' . $rawClass . '">' . "\n"; |
359 | $lineOutput .= $signesOutput; |
360 | $lineOutput .= '<td' . $colSpan . '>'; |
361 | $lineOutput .= $this->mParser->recursiveTagParse( $text ); |
362 | $lineOutput .= '</td>'; |
363 | $lineOutput .= '</tr>' . "\n"; |
364 | if ( $rawClass === 'correction selected' || $rawClass === 'correction unselected' ) { |
365 | if ( $proposalIndex === -1 ) { |
366 | // Add to output directly |
367 | $output .= $lineOutput; |
368 | } else { |
369 | // Add feedback to previous proposal |
370 | $lines[ $proposalIndex ] .= $lineOutput; |
371 | } |
372 | } else { |
373 | // Add lineOutput for proposal |
374 | $lines[ $proposalIndex ] = $lineOutput; |
375 | } |
376 | } |
377 | } |
378 | // A single choice object with no correct proposal is a syntax error. |
379 | if ( isset( $typeId ) && $typeId == 'sn' && $expectOn == 0 ) { |
380 | $this->setState( 'error' ); |
381 | } |
382 | // Finding order of shuffled proposals |
383 | $order = ''; |
384 | if ( $this->shuffleAnswers ) { |
385 | if ( $this->mBeingCorrected ) { |
386 | $order = $this->mRequest->getVal( $this->mQuestionId . '|order', '' ); |
387 | |
388 | // Check order values |
389 | $orderInvalid = $this->checkRequestOrder( $order, $proposalIndex ); |
390 | |
391 | // If order is invalid then order is reset |
392 | if ( $orderInvalid ) { |
393 | $order = ''; |
394 | for ( $i = 0; $i <= $proposalIndex; $i++ ) { |
395 | $order .= ' ' . $i; |
396 | } |
397 | } |
398 | } else { |
399 | $orderArray = range( 0, $proposalIndex ); |
400 | shuffle( $orderArray ); |
401 | $order = implode( ' ', $orderArray ); |
402 | } |
403 | } else { |
404 | for ( $i = 0; $i <= $proposalIndex; $i++ ) { |
405 | $order .= ' ' . $i; |
406 | } |
407 | } |
408 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal |
409 | $order = ltrim( $order ); |
410 | $tempOrder = explode( ' ', $order ); |
411 | for ( $i = 0; $i <= $proposalIndex; ++$i ) { |
412 | $output .= $lines[ $tempOrder[ $i ] ]; |
413 | } |
414 | $orderName = $this->mQuestionId . '|order'; |
415 | $orderValue = $order; |
416 | $attribs['hidden'] = 'hidden'; |
417 | $attribs['checked'] = 'checked'; |
418 | |
419 | return $this->shuffleAnswers |
420 | ? Xml::input( $orderName, false, $orderValue, $attribs ) . $output |
421 | : $output; |
422 | } |
423 | |
424 | /** |
425 | * Determine the object's parameters and convert a list of categories from |
426 | * quiz syntax to HTML. |
427 | * |
428 | * @param string $input pipe-separated list of the various categories. |
429 | * @return string |
430 | */ |
431 | private function parseCategories( $input ) { |
432 | $linkPattern = '`(\[\[.*?\]\](*SKIP)(*FAIL)|\|)|({{.*?}}(*SKIP)(*FAIL)|\|)`'; |
433 | $categories = preg_split( $linkPattern, $input ); |
434 | // Less than two categories is a syntax error. |
435 | if ( !array_key_exists( 1, $categories ) ) { |
436 | $categories[1] = '???'; |
437 | $this->setState( 'error' ); |
438 | } |
439 | $output = '<tr class="categories">' . "\n"; |
440 | $this->mProposalPattern = '`^'; |
441 | foreach ( $categories as $key => $category ) { |
442 | // If a category name is empty, the question has a syntax error. |
443 | if ( trim( $category ) == '' ) { |
444 | $category = '???'; |
445 | $this->setState( 'error' ); |
446 | } |
447 | $output .= '<th>' . $this->mParser->recursiveTagParse( $category ) . '</th>'; |
448 | if ( $key == 0 ) { |
449 | $this->mProposalPattern .= '([+-]) ?'; |
450 | } else { |
451 | $this->mProposalPattern .= '([+-])? ?'; |
452 | } |
453 | } |
454 | $output .= '</tr>' . "\n"; |
455 | $this->mProposalPattern .= '(.*)`'; |
456 | return $output; |
457 | } |
458 | |
459 | /** |
460 | * Convert a "text field" object to HTML. |
461 | * |
462 | * @param string $input A question object in quiz syntax. |
463 | * |
464 | * @return string A question object in HTML. |
465 | */ |
466 | public function textFieldParseObject( $input ) { |
467 | $raws = preg_split( '`\n`s', $input, -1, PREG_SPLIT_NO_EMPTY ); |
468 | global $wqInputId; |
469 | $wqInputId = $this->mQuestionId * 100; |
470 | $output = ''; |
471 | foreach ( $raws as $raw ) { |
472 | if ( preg_match( $this->mCorrectionPattern, $raw, $matches ) ) { |
473 | if ( $this->mBeingCorrected ) { |
474 | $rawClass = 'correction'; |
475 | $text = '<td>→ ' . $this->mParser->recursiveTagParse( $matches[1] ) . |
476 | '</td>'; |
477 | } else { |
478 | continue; |
479 | } |
480 | } elseif ( trim( $raw ) != '' ) { |
481 | $rawClass = 'proposal'; |
482 | $text = $this->mParser->recursiveTagParse( $raw ); |
483 | $text = preg_replace_callback( |
484 | $this->mTextFieldPattern, |
485 | [ $this, 'parseTextField' ], |
486 | $text |
487 | ); |
488 | $text = '<td class="input">' . $text . '</td>'; |
489 | } else { |
490 | continue; |
491 | } |
492 | $output .= '<tr class="' . $rawClass . '">' . "\n"; |
493 | $output .= $text . "\n"; |
494 | $output .= '</tr>' . "\n"; |
495 | } |
496 | return $output; |
497 | } |
498 | |
499 | /** |
500 | * @param array $input |
501 | * @return string |
502 | */ |
503 | private function parseTextField( $input ) { |
504 | global $wqInputId; |
505 | $wqInputId++; |
506 | $title = ''; |
507 | $state = ''; |
508 | $spanClass = ''; |
509 | $size = ''; |
510 | $maxlength = ''; |
511 | $class = ''; |
512 | $value = ''; |
513 | $disabled = ''; |
514 | $big = ''; |
515 | $poss = ''; |
516 | $bigDisplay = ''; |
517 | // determine size and maxlength of the input. |
518 | if ( array_key_exists( 3, $input ) ) { |
519 | $size = $input[3]; |
520 | if ( $size < 3 ) { |
521 | $size = 1; |
522 | } elseif ( $size < 12 ) { |
523 | $size -= 2; |
524 | } else { |
525 | $size--; |
526 | } |
527 | $maxlength = $input[3]; |
528 | } |
529 | $templateParser = new TemplateParser( __DIR__ . '/../templates' ); |
530 | // Syntax error if there is no input text. |
531 | if ( $input[1] === "" ) { |
532 | $value = 'value="???"'; |
533 | $state = 'error'; |
534 | } else { |
535 | // For hiding down arrow |
536 | $bigDisplay = 'display: none'; |
537 | if ( $this->mBeingCorrected ) { |
538 | // @phan-suppress-next-line PhanTypeMismatchArgument |
539 | $value = trim( $this->mRequest->getVal( $wqInputId, '' ) ); |
540 | $state = 'NA'; |
541 | $title = wfMessage( 'quiz_legend_unanswered' )->escaped(); |
542 | } |
543 | $class = 'numbers'; |
544 | $poss = ' '; |
545 | foreach ( |
546 | preg_split( '` *\| *`', trim( $input[1] ), -1, PREG_SPLIT_NO_EMPTY ) as $possibility |
547 | ) { |
548 | if ( $state == '' || $state == 'NA' || $state == 'incorrect' ) { |
549 | if ( preg_match( |
550 | '`^(-?\d+\.?\d*)(-(-?\d+\.?\d*)| (\d+\.?\d*)(%))?$`', |
551 | str_replace( ',', '.', $possibility ), |
552 | $matches ) |
553 | ) { |
554 | if ( array_key_exists( 5, $matches ) ) { |
555 | $strlen = $size = $maxlength = ''; |
556 | } elseif ( array_key_exists( 3, $matches ) ) { |
557 | $strlen = strlen( $matches[1] ) > strlen( $matches[3] ) |
558 | ? strlen( $matches[1] ) |
559 | : strlen( $matches[3] ); |
560 | } else { |
561 | $strlen = strlen( $matches[1] ); |
562 | } |
563 | if ( $this->mBeingCorrected && $value !== "" ) { |
564 | $value = str_replace( ',', '.', $value ); |
565 | if ( is_numeric( $value ) && ( |
566 | ( |
567 | array_key_exists( 5, $matches ) |
568 | && $value >= |
569 | ( (int)$matches[1] - ( (int)$matches[1] * (int)$matches[4] ) / 100 ) |
570 | && $value <= |
571 | ( (int)$matches[1] + ( (int)$matches[1] * (int)$matches[4] ) / 100 ) |
572 | ) || ( |
573 | array_key_exists( 3, $matches ) && |
574 | $value >= $matches[1] && $value <= $matches[3] |
575 | ) || $value == $possibility ) |
576 | ) { |
577 | $state = 'correct'; |
578 | $title = wfMessage( 'quiz_legend_correct' )->escaped(); |
579 | } else { |
580 | $state = 'incorrect'; |
581 | $title = wfMessage( 'quiz_legend_incorrect' )->escaped(); |
582 | } |
583 | } |
584 | } else { |
585 | $strlen = preg_match( '` \(i\)$`', $possibility ) |
586 | ? mb_strlen( $possibility ) - 4 |
587 | : mb_strlen( $possibility ); |
588 | $class = 'words'; |
589 | if ( $this->mBeingCorrected && $value !== "" ) { |
590 | if ( $value == $possibility || |
591 | ( preg_match( |
592 | '`^' . preg_quote( $value, '`' ) . ' \(i\)$`i', $possibility |
593 | ) ) || |
594 | ( !$this->mCaseSensitive && preg_match( |
595 | '`^' . preg_quote( $value, '`' ) . '$`i', $possibility |
596 | ) ) |
597 | ) { |
598 | $state = 'correct'; |
599 | $title = wfMessage( 'quiz_legend_correct' )->escaped(); |
600 | } else { |
601 | $state = 'incorrect'; |
602 | $title = wfMessage( 'quiz_legend_incorrect' )->escaped(); |
603 | } |
604 | } |
605 | } |
606 | if ( array_key_exists( 3, $input ) && $strlen > $input[3] ) { |
607 | // The textfield is too short for the answer |
608 | $state = 'error'; |
609 | $value = '<_' . $possibility . '_>'; |
610 | } |
611 | } |
612 | if ( $this->mBeingCorrected ) { |
613 | $strlen = preg_match( '` \(i\)$`', $possibility ) |
614 | ? mb_strlen( $possibility ) - 4 |
615 | : mb_strlen( $possibility ); |
616 | $possibility = mb_substr( $possibility, 0, $strlen ); |
617 | $poss .= $possibility . '<br />'; |
618 | } |
619 | } |
620 | if ( $this->mBeingCorrected ) { |
621 | $big = '▼'; |
622 | $bigDisplay = ' '; |
623 | } |
624 | } |
625 | if ( $state == 'error' || $this->mBeingCorrected ) { |
626 | $spanClass .= " border $state"; |
627 | $this->setState( $value === "" ? 'new_NA' : $state ); |
628 | if ( $state == 'error' ) { |
629 | $size = ''; |
630 | $maxlength = ''; |
631 | $disabled = 'disabled'; |
632 | $title = wfMessage( 'quiz_legend_error' )->escaped(); |
633 | } |
634 | } |
635 | $name = $wqInputId; |
636 | return $templateParser->processTemplate( |
637 | 'Answer', |
638 | [ |
639 | 'title' => $title, |
640 | 'class' => $class, |
641 | 'spanClass' => trim( $spanClass ), |
642 | 'value' => $value, |
643 | 'correction' => $this->mBeingCorrected, |
644 | 'possibility' => $poss, |
645 | 'disabled' => $disabled, |
646 | 'size' => $size, |
647 | 'big' => $big, |
648 | 'maxlength' => $maxlength, |
649 | 'name' => $name, |
650 | 'bigDisplay' => $bigDisplay, |
651 | ] |
652 | ); |
653 | } |
654 | } |