UniqueAnswer   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 460
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 275
dl 0
loc 460
rs 3.6
c 0
b 0
f 0
wmc 60

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setDirectOptions() 0 14 1
B processAnswersCreation() 0 59 11
F createAnswersForm() 0 255 40
A return_header() 0 20 4
A addAdaptiveScenarioFields() 0 60 3

How to fix   Complexity   

Complex Class

Complex classes like UniqueAnswer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UniqueAnswer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use ChamiloSession as Session;
6
7
/**
8
 * Class UniqueAnswer.
9
 *
10
 * This class allows to instantiate an object of type UNIQUE_ANSWER
11
 * (MULTIPLE CHOICE, UNIQUE ANSWER),
12
 * extending the class question
13
 *
14
 * @author Eric Marguin
15
 * @author Julio Montoya
16
 */
17
class UniqueAnswer extends Question
18
{
19
    public $typePicture = 'mcua.png';
20
    public $explanationLangVar = 'Multiple choice';
21
22
    public function __construct()
23
    {
24
        parent::__construct();
25
        $this->type = UNIQUE_ANSWER;
26
        $this->isContent = $this->getIsContent();
27
    }
28
29
    public function createAnswersForm($form)
30
    {
31
        // Getting the exercise list
32
        /** @var Exercise $obj_ex */
33
        $obj_ex = Session::read('objExercise');
34
35
        $editor_config = [
36
            'ToolbarSet' => 'TestProposedAnswer',
37
            'Width' => '100%',
38
            'Height' => '125',
39
        ];
40
41
        //this line defines how many questions by default appear when creating a choice question
42
        // The previous default value was 2. See task #1759.
43
        $nb_answers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : 4;
44
        $nb_answers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
45
46
        $feedback_title = '';
47
        switch ($obj_ex->getFeedbackType()) {
48
            case EXERCISE_FEEDBACK_TYPE_DIRECT:
49
                // Scenario
50
                $comment_title = '<th width="20%">'.get_lang('Comment').'</th>';
51
                break;
52
            case EXERCISE_FEEDBACK_TYPE_POPUP:
53
                $comment_title = '<th width="20%">'.get_lang('Comment').'</th>';
54
55
                break;
56
            default:
57
                $comment_title = '<th width="40%">'.get_lang('Comment').'</th>';
58
59
                break;
60
        }
61
62
        $html = '<table class="table table-striped table-hover">
63
            <thead>
64
                <tr style="text-align: center;">
65
                    <th width="5%">'.get_lang('N°').'</th>
66
                    <th width="5%"> '.get_lang('True').'</th>
67
                    <th width="40%">'.get_lang('Answer').'</th>
68
                        '.$comment_title.'
69
                    <th width="10%">'.get_lang('Score').'</th>
70
                </tr>
71
            </thead>
72
            <tbody>';
73
74
        $form->addHeader(get_lang('Answers'));
75
        $form->addHtml($html);
76
77
        $defaults = [];
78
        $correct = 0;
79
        if (!empty($this->id)) {
80
            $answer = new Answer($this->id);
81
            $answer->read();
82
            if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
83
                $nb_answers = $answer->nbrAnswers;
84
            }
85
        }
86
        $form->addHidden('nb_answers', $nb_answers);
87
88
        $obj_ex->setQuestionList(true);
89
        $question_list = $obj_ex->getQuestionList();
90
        $select_question = [];
91
        $select_question[0] = get_lang('Select target question');
92
        if (is_array($question_list)) {
93
            foreach ($question_list as $key => $questionid) {
94
                //To avoid warning messages
95
                if (!is_numeric($questionid)) {
96
                    continue;
97
                }
98
                $question = Question::read($questionid);
99
                $questionTitle = strip_tags($question->selectTitle());
100
                $select_question[$questionid] = "Q$key: $questionTitle";
101
            }
102
        }
103
        $select_question[-1] = get_lang('Exit test');
104
105
        $list = new LearnpathList(api_get_user_id());
106
        $flat_list = $list->get_flat_list();
107
        $select_lp_id = [];
108
        $select_lp_id[0] = get_lang('Select target course');
109
110
        foreach ($flat_list as $id => $details) {
111
            $select_lp_id[$id] = cut($details['lp_name'], 20);
112
        }
113
114
        $temp_scenario = [];
115
        if ($nb_answers < 1) {
116
            $nb_answers = 1;
117
            echo Display::return_message(get_lang('You have to create at least one answer'));
118
        }
119
120
        for ($i = 1; $i <= $nb_answers; $i++) {
121
            $form->addHtml('<tr>');
122
            if (isset($answer) && is_object($answer)) {
123
                if (isset($answer->correct[$i]) && $answer->correct[$i]) {
124
                    $correct = $i;
125
                }
126
                $defaults['answer['.$i.']'] = isset($answer->answer[$i]) ? $answer->answer[$i] : '';
127
                $defaults['comment['.$i.']'] = isset($answer->comment[$i]) ? $answer->comment[$i] : '';
128
                $defaults['weighting['.$i.']'] = isset($answer->weighting[$i]) ? float_format($answer->weighting[$i], 1) : 0;
129
            } else {
130
                $defaults['answer[1]'] = get_lang('A then B then C');
131
                $defaults['weighting[1]'] = 10;
132
                $defaults['answer[2]'] = get_lang('A then C then B');
133
                $defaults['weighting[2]'] = 0;
134
                $temp_scenario['destination'.$i] = ['0'];
135
                $temp_scenario['lp'.$i] = ['0'];
136
            }
137
            $defaults['scenario'] = $temp_scenario;
138
139
            $renderer = $form->defaultRenderer();
140
141
            $renderer->setElementTemplate(
142
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
143
                'correct'
144
            );
145
            $renderer->setElementTemplate(
146
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
147
                'counter['.$i.']'
148
            );
149
            $renderer->setElementTemplate(
150
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
151
                'answer['.$i.']'
152
            );
153
            $renderer->setElementTemplate(
154
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
155
                'comment['.$i.']'
156
            );
157
            $renderer->setElementTemplate(
158
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
159
                'weighting['.$i.']'
160
            );
161
162
            $answerNumber = $form->addText(
163
                'counter['.$i.']',
164
                null,
165
                false,
166
                ['value' => $i]
167
            );
168
            $answerNumber->freeze();
169
170
            $form->addElement(
171
                'radio',
172
                'correct',
173
                null,
174
                null,
175
                $i,
176
                ['class' => 'checkbox']
177
            );
178
179
            $form->addHtmlEditor('answer['.$i.']', null, null, false, $editor_config);
180
181
            $form->addRule(
182
                'answer['.$i.']',
183
                get_lang('Required field'),
184
                'required'
185
            );
186
187
            switch ($obj_ex->getFeedbackType()) {
188
                case EXERCISE_FEEDBACK_TYPE_DIRECT:
189
                    $this->setDirectOptions($i, $form, $renderer, $select_lp_id, $select_question);
190
191
                    break;
192
                case EXERCISE_FEEDBACK_TYPE_POPUP:
193
                default:
194
                    $form->addHtmlEditor('comment['.$i.']', null, null, false, $editor_config);
195
196
                    break;
197
            }
198
            $form->addText('weighting['.$i.']', null, null, ['value' => '0']);
199
            $form->addHtml('</tr>');
200
        }
201
202
        $form->addHtml('</tbody>');
203
        $form->addHtml('</table>');
204
205
        global $text;
206
        $buttonGroup = [];
207
208
        if (true == $obj_ex->edit_exercise_in_lp ||
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
209
            (empty($this->exerciseList) && empty($obj_ex->id))
210
        ) {
211
212
            if (api_get_setting('enable_quiz_scenario') === 'true' && $obj_ex->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_DIRECT) {
213
                $this->addAdaptiveScenarioFields($form, $question_list);
214
            }
215
216
            //setting the save button here and not in the question class.php
217
            $buttonGroup[] = $form->addButtonDelete(get_lang('Remove answer option'), 'lessAnswers', true);
218
            $buttonGroup[] = $form->addButtonCreate(get_lang('Add answer option'), 'moreAnswers', true);
219
            $buttonGroup[] = $form->addButton(
220
                'submitQuestion',
221
                $text,
222
                'check',
223
                'primary',
224
                'default',
225
                null,
226
                ['id' => 'submit-question'],
227
                true
228
            );
229
            $form->addGroup($buttonGroup);
230
        }
231
232
        // We check the first radio button to be sure a radio button will be check
233
        if (0 == $correct) {
234
            $correct = 1;
235
        }
236
237
        if (isset($_POST) && isset($_POST['correct'])) {
238
            $correct = (int) $_POST['correct'];
239
        }
240
241
        $defaults['correct'] = $correct;
242
243
        if (!empty($this->id)) {
244
245
            if (!empty($this->id)) {
246
                $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
247
                $res = Database::select(
248
                    'destination',
249
                    $table,
250
                    ['where' => ['question_id = ? AND quiz_id = ?' => [$this->id, $obj_ex->id]], 'limit' => 1],
251
                    'first'
252
                );
253
254
                if (!empty($res['destination'])) {
255
                    $json = json_decode($res['destination'], true);
256
                    $defaults['scenario_success_selector'] = $json['success'] ?? '';
257
                    $defaults['scenario_failure_selector'] = $json['failure'] ?? '';
258
259
                    if (str_starts_with($json['success'] ?? '', '/')) {
260
                        $defaults['scenario_success_selector'] = 'url';
261
                        $defaults['scenario_success_url'] = $json['success'];
262
                    }
263
                    if (str_starts_with($json['failure'] ?? '', '/')) {
264
                        $defaults['scenario_failure_selector'] = 'url';
265
                        $defaults['scenario_failure_url'] = $json['failure'];
266
                    }
267
                }
268
            }
269
            $form->setDefaults($defaults);
270
        } else {
271
            if (1 == $this->isContent) {
272
                // Default sample content.
273
                $form->setDefaults($defaults);
274
            } else {
275
                $correct = 1;
276
                if (isset($_POST) && isset($_POST['correct'])) {
277
                    $correct = (int) $_POST['correct'];
278
                }
279
280
                $form->setDefaults(['correct' => $correct]);
281
            }
282
        }
283
        $form->setConstants(['nb_answers' => $nb_answers]);
284
    }
285
286
    /**
287
     * Add adaptive scenario selector fields (success/failure) to the question form.
288
     */
289
    private function addAdaptiveScenarioFields(FormValidator $form, array $questionList)
290
    {
291
        // Section header
292
        $form->addHtml('<h4 class="m-4">'.get_lang('Adaptive behavior (Success / Failure)').'</h4>');
293
294
        // Options for redirection behavior
295
        $questionListOptions = [
296
            '' => get_lang('Select destination'),
297
            'repeat' => get_lang('Repeat question'),
298
            '-1' => get_lang('End of test'),
299
            'url' => get_lang('Other (custom URL)'),
300
        ];
301
302
        // Append available questions to the dropdown
303
        foreach ($questionList as $index => $qid) {
304
            if (!is_numeric($qid)) {
305
                continue;
306
            }
307
            $q = Question::read($qid);
308
            $questionListOptions[(string) $qid] = "Q$index: " . strip_tags($q->selectTitle());
309
        }
310
311
        // Success selector and optional URL field
312
        $form->addSelect(
313
            'scenario_success_selector',
314
            get_lang('On success'),
315
            $questionListOptions,
316
            ['id' => 'scenario_success_selector']
317
        );
318
        $form->addText(
319
            'scenario_success_url',
320
            get_lang('Custom URL'),
321
            false,
322
            [
323
                'class' => 'form-control mb-5',
324
                'id' => 'scenario_success_url',
325
                'placeholder' => '/main/lp/134',
326
            ]
327
        );
328
329
        // Failure selector and optional URL field
330
        $form->addSelect(
331
            'scenario_failure_selector',
332
            get_lang('On failure'),
333
            $questionListOptions,
334
            ['id' => 'scenario_failure_selector']
335
        );
336
        $form->addText(
337
            'scenario_failure_url',
338
            get_lang('Custom URL'),
339
            false,
340
            [
341
                'class' => 'form-control mb-5',
342
                'id' => 'scenario_failure_url',
343
                'placeholder' => '/main/lp/134',
344
            ]
345
        );
346
347
        // JavaScript to toggle custom URL fields when 'url' is selected
348
        $form->addHtml('
349
        <script>
350
            function toggleScenarioUrlFields() {
351
                const successSelector = document.getElementById("scenario_success_selector");
352
                const successUrlRow = document.getElementById("scenario_success_url").parentNode.parentNode;
353
354
                const failureSelector = document.getElementById("scenario_failure_selector");
355
                const failureUrlRow = document.getElementById("scenario_failure_url").parentNode.parentNode;
356
357
                if (successSelector && successSelector.value === "url") {
358
                    successUrlRow.style.display = "table-row";
359
                } else {
360
                    successUrlRow.style.display = "none";
361
                }
362
363
                if (failureSelector && failureSelector.value === "url") {
364
                    failureUrlRow.style.display = "table-row";
365
                } else {
366
                    failureUrlRow.style.display = "none";
367
                }
368
            }
369
370
            document.addEventListener("DOMContentLoaded", toggleScenarioUrlFields);
371
            document.getElementById("scenario_success_selector").addEventListener("change", toggleScenarioUrlFields);
372
            document.getElementById("scenario_failure_selector").addEventListener("change", toggleScenarioUrlFields);
373
        </script>
374
    ');
375
    }
376
377
    public function setDirectOptions($i, FormValidator $form, $renderer, $select_lp_id, $select_question)
378
    {
379
        $editor_config = [
380
            'ToolbarSet' => 'TestProposedAnswer',
381
            'Width' => '100%',
382
            'Height' => '125',
383
        ];
384
385
        $form->addHtmlEditor(
386
            'comment['.$i.']',
387
            null,
388
            null,
389
            false,
390
            $editor_config
391
        );
392
    }
393
394
    public function processAnswersCreation($form, $exercise)
395
    {
396
        $questionWeighting = $nbrGoodAnswers = 0;
397
        $correct = $form->getSubmitValue('correct');
398
        $objAnswer = new Answer($this->id);
399
        $nb_answers = $form->getSubmitValue('nb_answers');
400
401
        for ($i = 1; $i <= $nb_answers; $i++) {
402
            $answer = trim($form->getSubmitValue('answer['.$i.']'));
403
            $comment = trim($form->getSubmitValue('comment['.$i.']'));
404
            $weighting = trim($form->getSubmitValue('weighting['.$i.']'));
405
            $goodAnswer = $correct == $i;
406
407
            if ($goodAnswer) {
408
                $nbrGoodAnswers++;
409
                $weighting = abs($weighting);
410
                if ($weighting > 0) {
411
                    $questionWeighting += $weighting;
412
                }
413
            }
414
415
            $objAnswer->createAnswer(
416
                $answer,
417
                $goodAnswer,
418
                $comment,
419
                $weighting,
420
                $i
421
            );
422
        }
423
424
        $objAnswer->save();
425
426
        $this->updateWeighting($questionWeighting);
427
        $this->save($exercise);
428
429
        $scenarioEnabled = api_get_setting('enable_quiz_scenario') === 'true';
430
        $isAdaptative = $exercise && $exercise->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_DIRECT;
431
        if ($scenarioEnabled && $isAdaptative) {
432
            $successSelector = trim($form->getSubmitValue('scenario_success_selector'));
433
            $successUrl = trim($form->getSubmitValue('scenario_success_url'));
434
            $failureSelector = trim($form->getSubmitValue('scenario_failure_selector'));
435
            $failureUrl = trim($form->getSubmitValue('scenario_failure_url'));
436
437
            $success = $successSelector === 'url' ? $successUrl : $successSelector;
438
            $failure = $failureSelector === 'url' ? $failureUrl : $failureSelector;
439
440
            $destination = json_encode([
441
                'success' => $success ?: '',
442
                'failure' => $failure ?: '',
443
            ]);
444
445
            $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
446
            $questionId = $this->id;
447
            $exerciseId = $exercise->id;
448
449
            Database::update(
450
                $table,
451
                ['destination' => $destination],
452
                ['question_id = ? AND quiz_id = ?' => [$questionId, $exerciseId]]
453
            );
454
        }
455
    }
456
457
    public function return_header(Exercise $exercise, $counter = null, $score = [])
458
    {
459
        $header = parent::return_header($exercise, $counter, $score);
460
        $header .= '<table class="'.$this->questionTableClass.'"><tr>';
461
462
        $header .= '<th>'.get_lang('Your choice').'</th>';
463
        if ($exercise->showExpectedChoiceColumn()) {
464
            $header .= '<th>'.get_lang('Expected choice').'</th>';
465
        }
466
467
        $header .= '<th>'.get_lang('Answer').'</th>';
468
        if ($exercise->showExpectedChoice()) {
469
            $header .= '<th class="text-center">'.get_lang('Status').'</th>';
470
        }
471
        if (false === $exercise->hideComment) {
472
            $header .= '<th>'.get_lang('Comment').'</th>';
473
        }
474
        $header .= '</tr>';
475
476
        return $header;
477
    }
478
}
479