Passed
Push — 1.11.x ( e367a6...5cce46 )
by Angel Fernando Quiroz
10:26 queued 16s
created

Ims2Question::processAnswersCreation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * Interface ImsAnswerInterface.
6
 */
7
interface ImsAnswerInterface
8
{
9
    /**
10
     * @param string $questionIdent
11
     * @param string $questionStatment
12
     * @param string $questionDesc
13
     * @param string $questionMedia
14
     *
15
     * @return string
16
     */
17
    public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '');
18
19
    /**
20
     * @param $questionIdent
21
     *
22
     * @return mixed
23
     */
24
    public function imsExportResponsesDeclaration($questionIdent, Question $question = null);
25
}
26
27
/**
28
 * @author Claro Team <[email protected]>
29
 * @author Yannick Warnier <[email protected]> -
30
 * updated ImsAnswerHotspot to match QTI norms
31
 */
32
class Ims2Question extends Question
33
{
34
    /**
35
     * Include the correct answer class and create answer.
36
     *
37
     * @return Answer
38
     */
39
    public function setAnswer()
40
    {
41
        switch ($this->type) {
42
            case MCUA:
43
                $answer = new ImsAnswerMultipleChoice($this->iid);
44
45
                return $answer;
46
            case MCMA:
47
            case MULTIPLE_ANSWER_DROPDOWN:
48
            case MULTIPLE_ANSWER_DROPDOWN_GLOBAL:
49
                $answer = new ImsAnswerMultipleChoice($this->iid);
50
51
                return $answer;
52
            case TF:
53
                $answer = new ImsAnswerMultipleChoice($this->iid);
54
55
                return $answer;
56
            case FIB:
57
                $answer = new ImsAnswerFillInBlanks($this->iid);
58
59
                return $answer;
60
            case MATCHING:
61
            case MATCHING_DRAGGABLE:
62
                $answer = new ImsAnswerMatching($this->iid);
63
64
                return $answer;
65
            case FREE_ANSWER:
66
                $answer = new ImsAnswerFree($this->iid);
67
68
                return $answer;
69
            case HOT_SPOT:
70
            case HOT_SPOT_GLOBAL:
71
                $answer = new ImsAnswerHotspot($this->iid);
72
73
                return $answer;
74
            default:
75
                $answer = null;
76
                break;
77
        }
78
79
        return $answer;
80
    }
81
82
    public function createAnswersForm($form)
83
    {
84
        return true;
85
    }
86
87
    public function processAnswersCreation($form, $exercise)
88
    {
89
        return true;
90
    }
91
}
92
93
/**
94
 * Class.
95
 */
96
class ImsAnswerMultipleChoice extends Answer implements ImsAnswerInterface
97
{
98
    /**
99
     * Return the XML flow for the possible answers.
100
     *
101
     * @param string $questionIdent
102
     * @param string $questionStatment
103
     * @param string $questionDesc
104
     * @param string $questionMedia
105
     *
106
     * @return string
107
     */
108
    public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
109
    {
110
        // @todo getAnswersList() converts the answers using api_html_entity_decode()
111
        $this->answerList = $this->getAnswersList(true);
112
        $out = '    <choiceInteraction responseIdentifier="'.$questionIdent.'" >'."\n";
113
        $out .= '      <prompt><![CDATA['.formatExerciseQtiText($questionDesc).']]></prompt>'."\n";
114
        if (is_array($this->answerList)) {
115
            foreach ($this->answerList as $current_answer) {
116
                $out .= '<simpleChoice identifier="answer_'.$current_answer['iid'].'" fixed="false">
117
                         <![CDATA['.formatExerciseQtiText($current_answer['answer']).']]>';
118
                if (isset($current_answer['comment']) && $current_answer['comment'] != '') {
119
                    $out .= '<feedbackInline identifier="answer_'.$current_answer['iid'].'">
120
                             <![CDATA['.formatExerciseQtiText($current_answer['comment']).']]>
121
                             </feedbackInline>';
122
                }
123
                $out .= '</simpleChoice>'."\n";
124
            }
125
        }
126
        $out .= '    </choiceInteraction>'."\n";
127
128
        return $out;
129
    }
130
131
    /**
132
     * Return the XML flow of answer ResponsesDeclaration.
133
     */
134
    public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
135
    {
136
        $this->answerList = $this->getAnswersList(true);
137
        $type = $this->getQuestionType();
138
        if (in_array($type, [MCMA, MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_GLOBAL])) {
139
            $cardinality = 'multiple';
140
        } else {
141
            $cardinality = 'single';
142
        }
143
144
        $out = '  <responseDeclaration identifier="'.$questionIdent.'" cardinality="'.$cardinality.'" baseType="identifier">'."\n";
145
146
        // Match the correct answers.
147
        if (is_array($this->answerList)) {
148
            $out .= '    <correctResponse>'."\n";
149
            foreach ($this->answerList as $current_answer) {
150
                if ($current_answer['correct']) {
151
                    $out .= '      <value>answer_'.$current_answer['iid'].'</value>'."\n";
152
                }
153
            }
154
            $out .= '    </correctResponse>'."\n";
155
        }
156
157
        // Add the grading
158
        if (is_array($this->answerList)) {
159
            $out .= '    <mapping';
160
161
            if (MULTIPLE_ANSWER_DROPDOWN_GLOBAL == $this->getQuestionType()) {
162
                $out .= ' defaultValue="'.$question->selectWeighting().'"';
0 ignored issues
show
Bug introduced by
The method selectWeighting() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

162
                $out .= ' defaultValue="'.$question->/** @scrutinizer ignore-call */ selectWeighting().'"';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
163
            }
164
165
            $out .= '>'."\n";
166
167
            foreach ($this->answerList as $current_answer) {
168
                if (isset($current_answer['grade'])) {
169
                    $out .= ' <mapEntry mapKey="answer_'.$current_answer['iid'].'" mappedValue="'.$current_answer['grade'].'" />'."\n";
170
                }
171
            }
172
            $out .= '    </mapping>'."\n";
173
        }
174
175
        $out .= '  </responseDeclaration>'."\n";
176
177
        return $out;
178
    }
179
}
180
181
/**
182
 * Class.
183
 *
184
 * @package chamilo.exercise
185
 */
186
class ImsAnswerFillInBlanks extends Answer implements ImsAnswerInterface
187
{
188
    private $answerList = [];
189
    private $gradeList = [];
190
191
    /**
192
     * Export the text with missing words.
193
     *
194
     * @param string $questionIdent
195
     * @param string $questionStatment
196
     * @param string $questionDesc
197
     * @param string $questionMedia
198
     *
199
     * @return string
200
     */
201
    public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
202
    {
203
        $this->answerList = $this->getAnswersList(true);
204
        $text = isset($this->answerText) ? $this->answerText : '';
0 ignored issues
show
Bug introduced by
The property answerText does not exist on ImsAnswerFillInBlanks. Did you mean answer?
Loading history...
205
        if (is_array($this->answerList)) {
206
            foreach ($this->answerList as $key => $answer) {
207
                $key = $answer['iid'];
208
                $answer = $answer['answer'];
209
                $len = api_strlen($answer);
210
                $text = str_replace('['.$answer.']', '<textEntryInteraction responseIdentifier="fill_'.$key.'" expectedLength="'.api_strlen($answer).'"/>', $text);
211
            }
212
        }
213
214
        return $text;
215
    }
216
217
    public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
218
    {
219
        $this->answerList = $this->getAnswersList(true);
220
        $this->gradeList = $this->getGradesList();
221
        $out = '';
222
        if (is_array($this->answerList)) {
223
            foreach ($this->answerList as $answer) {
224
                $answerKey = $answer['iid'];
225
                $answer = $answer['answer'];
226
                $out .= '  <responseDeclaration identifier="fill_'.$answerKey.'" cardinality="single" baseType="identifier">'."\n";
227
                $out .= '    <correctResponse>'."\n";
228
                $out .= '      <value><![CDATA['.formatExerciseQtiText($answer).']]></value>'."\n";
229
                $out .= '    </correctResponse>'."\n";
230
                if (isset($this->gradeList[$answerKey])) {
231
                    $out .= '    <mapping>'."\n";
232
                    $out .= '      <mapEntry mapKey="'.$answer.'" mappedValue="'.$this->gradeList[$answerKey].'"/>'."\n";
233
                    $out .= '    </mapping>'."\n";
234
                }
235
236
                $out .= '  </responseDeclaration>'."\n";
237
            }
238
        }
239
240
        return $out;
241
    }
242
}
243
244
/**
245
 * Class.
246
 *
247
 * @package chamilo.exercise
248
 */
249
class ImsAnswerMatching extends Answer implements ImsAnswerInterface
250
{
251
    public $leftList = [];
252
    public $rightList = [];
253
    private $answerList = [];
254
255
    /**
256
     * Export the question part as a matrix-choice, with only one possible answer per line.
257
     *
258
     * @param string $questionIdent
259
     * @param string $questionStatment
260
     * @param string $questionDesc
261
     * @param string $questionMedia
262
     *
263
     * @return string
264
     */
265
    public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
266
    {
267
        $this->answerList = $this->getAnswersList(true);
268
        $maxAssociation = max(count($this->leftList), count($this->rightList));
269
270
        $out = '<matchInteraction responseIdentifier="'.$questionIdent.'" maxAssociations="'.$maxAssociation.'">'."\n";
271
        $out .= $questionStatment;
272
273
        //add left column
274
        $out .= '  <simpleMatchSet>'."\n";
275
        if (is_array($this->leftList)) {
276
            foreach ($this->leftList as $leftKey => $leftElement) {
277
                $out .= '
278
                <simpleAssociableChoice identifier="left_'.$leftKey.'" >
279
                    <![CDATA['.formatExerciseQtiText($leftElement['answer']).']]>
280
                </simpleAssociableChoice>'."\n";
281
            }
282
        }
283
284
        $out .= '  </simpleMatchSet>'."\n";
285
286
        //add right column
287
        $out .= '  <simpleMatchSet>'."\n";
288
        $i = 0;
289
290
        if (is_array($this->rightList)) {
291
            foreach ($this->rightList as $rightKey => $rightElement) {
292
                $out .= '<simpleAssociableChoice identifier="right_'.$i.'" >
293
                        <![CDATA['.formatExerciseQtiText($rightElement['answer']).']]>
294
                        </simpleAssociableChoice>'."\n";
295
                $i++;
296
            }
297
        }
298
        $out .= '  </simpleMatchSet>'."\n";
299
        $out .= '</matchInteraction>'."\n";
300
301
        return $out;
302
    }
303
304
    public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
305
    {
306
        $this->answerList = $this->getAnswersList(true);
307
        $out = '  <responseDeclaration identifier="'.$questionIdent.'" cardinality="single" baseType="identifier">'."\n";
308
        $out .= '    <correctResponse>'."\n";
309
310
        $gradeArray = [];
311
        if (isset($this->leftList) && is_array($this->leftList)) {
312
            foreach ($this->leftList as $leftKey => $leftElement) {
313
                $i = 0;
314
                foreach ($this->rightList as $rightKey => $rightElement) {
315
                    if (($leftElement['match'] == $rightElement['code'])) {
316
                        $out .= '      <value>left_'.$leftKey.' right_'.$i.'</value>'."\n";
317
                        $gradeArray['left_'.$leftKey.' right_'.$i] = $leftElement['grade'];
318
                    }
319
                    $i++;
320
                }
321
            }
322
        }
323
        $out .= '    </correctResponse>'."\n";
324
325
        if (is_array($gradeArray)) {
326
            $out .= '    <mapping>'."\n";
327
            foreach ($gradeArray as $gradeKey => $grade) {
328
                $out .= '          <mapEntry mapKey="'.$gradeKey.'" mappedValue="'.$grade.'"/>'."\n";
329
            }
330
            $out .= '    </mapping>'."\n";
331
        }
332
333
        $out .= '  </responseDeclaration>'."\n";
334
335
        return $out;
336
    }
337
}
338
339
/**
340
 * Class.
341
 *
342
 * @package chamilo.exercise
343
 */
344
class ImsAnswerHotspot extends Answer implements ImsAnswerInterface
345
{
346
    private $answerList = [];
347
    private $gradeList = [];
348
349
    /**
350
     * @todo update this to match hot spots instead of copying matching
351
     * Export the question part as a matrix-choice, with only one possible answer per line.
352
     *
353
     * @param string $questionIdent
354
     * @param string $questionStatment
355
     * @param string $questionDesc
356
     * @param string $questionMedia
357
     *
358
     * @return string
359
     */
360
    public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
361
    {
362
        $this->answerList = $this->getAnswersList(true);
363
        $mediaFilePath = api_get_course_path().'/document/images/'.$questionMedia;
364
        $sysQuestionMediaPath = api_get_path(SYS_COURSE_PATH).$mediaFilePath;
365
        $questionMedia = api_get_path(WEB_COURSE_PATH).$mediaFilePath;
366
        $mimetype = mime_content_type($sysQuestionMediaPath);
367
        if (empty($mimetype)) {
368
            $mimetype = 'image/jpeg';
369
        }
370
371
        $text = '      <p>'.$questionStatment.'</p>'."\n";
372
        $text .= '      <graphicOrderInteraction responseIdentifier="hotspot_'.$questionIdent.'">'."\n";
373
        $text .= '        <prompt>'.$questionDesc.'</prompt>'."\n";
374
        $text .= '        <object type="'.$mimetype.'" width="250" height="230" data="'.$questionMedia.'">-</object>'."\n";
375
        if (is_array($this->answerList)) {
376
            foreach ($this->answerList as $key => $answer) {
377
                $key = $answer['iid'];
378
                $answerTxt = $answer['answer'];
379
                $len = api_strlen($answerTxt);
380
                //coords are transformed according to QTIv2 rules here: http://www.imsproject.org/question/qtiv2p1pd/imsqti_infov2p1pd.html#element10663
381
                $coords = '';
382
                $type = 'default';
383
                switch ($answer['hotspot_type']) {
384
                    case 'square':
385
                        $type = 'rect';
386
                        $res = [];
387
                        $coords = preg_match('/^\s*(\d+);(\d+)\|(\d+)\|(\d+)\s*$/', $answer['hotspot_coord'], $res);
388
                        $coords = $res[1].','.$res[2].','.((int) $res[1] + (int) $res[3]).",".((int) $res[2] + (int) $res[4]);
389
                        break;
390
                    case 'circle':
391
                        $type = 'circle';
392
                        $res = [];
393
                        $coords = preg_match('/^\s*(\d+);(\d+)\|(\d+)\|(\d+)\s*$/', $answer['hotspot_coord'], $res);
394
                        $coords = $res[1].','.$res[2].','.sqrt(pow(($res[1] - $res[3]), 2) + pow(($res[2] - $res[4])));
0 ignored issues
show
Bug introduced by
The call to pow() has too few arguments starting with exp. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

394
                        $coords = $res[1].','.$res[2].','.sqrt(pow(($res[1] - $res[3]), 2) + /** @scrutinizer ignore-call */ pow(($res[2] - $res[4])));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
395
                        break;
396
                    case 'poly':
397
                        $type = 'poly';
398
                        $coords = str_replace([';', '|'], [',', ','], $answer['hotspot_coord']);
399
                        break;
400
                    case 'delineation':
401
                        $type = 'delineation';
402
                        $coords = str_replace([';', '|'], [',', ','], $answer['hotspot_coord']);
403
                        break;
404
                }
405
                $text .= '        <hotspotChoice shape="'.$type.'" coords="'.$coords.'" identifier="'.$key.'"/>'."\n";
406
            }
407
        }
408
        $text .= '      </graphicOrderInteraction>'."\n";
409
410
        return $text;
411
    }
412
413
    public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
414
    {
415
        $this->answerList = $this->getAnswersList(true);
416
        $this->gradeList = $this->getGradesList();
417
        $out = '  <responseDeclaration identifier="hotspot_'.$questionIdent.'" cardinality="ordered" baseType="identifier">'."\n";
418
        if (is_array($this->answerList)) {
419
            $out .= '    <correctResponse>'."\n";
420
            foreach ($this->answerList as $answerKey => $answer) {
421
                $answerKey = $answer['iid'];
422
                $answer = $answer['answer'];
423
                $out .= '<value><![CDATA['.formatExerciseQtiText($answerKey).']]></value>';
424
            }
425
            $out .= '    </correctResponse>'."\n";
426
        }
427
        $out .= '  </responseDeclaration>'."\n";
428
429
        return $out;
430
    }
431
}
432
433
/**
434
 * Class.
435
 *
436
 * @package chamilo.exercise
437
 */
438
class ImsAnswerFree extends Answer implements ImsAnswerInterface
439
{
440
    /**
441
     * @todo implement
442
     * Export the question part as a matrix-choice, with only one possible answer per line.
443
     *
444
     * @param string $questionIdent
445
     * @param string $questionStatment
446
     * @param string $questionDesc
447
     * @param string $questionMedia
448
     *
449
     * @return string
450
     */
451
    public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
452
    {
453
        $questionDesc = formatExerciseQtiText($questionDesc);
454
455
        return '<extendedTextInteraction responseIdentifier="'.$questionIdent.'" >
456
            <prompt>
457
            '.$questionDesc.'
458
            </prompt>
459
            </extendedTextInteraction>';
460
    }
461
462
    public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
463
    {
464
        $out = '  <responseDeclaration identifier="'.$questionIdent.'" cardinality="single" baseType="string">';
465
        $out .= '<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
466
                <defaultValue><value>'.$question->weighting.'</value></defaultValue></outcomeDeclaration>';
467
        $out .= '  </responseDeclaration>'."\n";
468
469
        return $out;
470
    }
471
}
472