Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Question type class for the calculated multiple-choice question type.
19
 *
20
 * @package    qtype
21
 * @subpackage calculatedmulti
22
 * @copyright  2009 Pierre Pichet
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
use qtype_calculatedmulti\qtype_calculatedmulti_answer;
27
 
28
defined('MOODLE_INTERNAL') || die();
29
 
30
require_once($CFG->dirroot . '/question/type/multichoice/questiontype.php');
31
require_once($CFG->dirroot . '/question/type/calculated/questiontype.php');
32
 
33
 
34
/**
35
 * The calculated multiple-choice question type.
36
 *
37
 * @copyright  2009 Pierre Pichet
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class qtype_calculatedmulti extends qtype_calculated {
41
 
42
    public function save_question_options($question) {
43
        global $DB;
44
        $context = $question->context;
45
 
46
        // During validation, we will call the parent's validation method. It will check
47
        // the answer options (choices) for formula errors. This method is shared with
48
        // calculated and calculatedsimple types for which the text is just a formula and
49
        // thus plain text from a regular text field. Therefore, we must transform the answers
50
        // before sending them upstream. We save the original data for later.
51
        $question->originalanswer = [];
52
        foreach ($question->answer as $key => $answerdata) {
53
            $question->originalanswer[$key] = $answerdata;
54
            if (is_array($answerdata)) {
55
                $question->answer[$key] = $answerdata['text'];
56
            } else {
57
                $question->answer[$key] = $answerdata;
58
            }
59
        }
60
 
61
        // Make it impossible to save bad formulas anywhere.
62
        $this->validate_question_data($question);
63
 
64
        // Calculated options.
65
        $options = $DB->get_record('question_calculated_options',
66
                array('question' => $question->id));
67
        if (!$options) {
68
            $options = new stdClass();
69
            $options->question = $question->id;
70
            $options->correctfeedback = '';
71
            $options->partiallycorrectfeedback = '';
72
            $options->incorrectfeedback = '';
73
            $options->id = $DB->insert_record('question_calculated_options', $options);
74
        }
75
        $options->synchronize = $question->synchronize;
76
        $options->single = $question->single;
77
        $options->answernumbering = $question->answernumbering;
78
        $options->shuffleanswers = $question->shuffleanswers;
79
        $options = $this->save_combined_feedback_helper($options, $question, $context, true);
80
        $DB->update_record('question_calculated_options', $options);
81
 
82
        // Get old versions of the objects.
83
        if (!$oldanswers = $DB->get_records('question_answers',
84
                array('question' => $question->id), 'id ASC')) {
85
            $oldanswers = array();
86
        }
87
        if (!$oldoptions = $DB->get_records('question_calculated',
88
                array('question' => $question->id), 'answer ASC')) {
89
            $oldoptions = array();
90
        }
91
 
92
        // Insert all the new answers.
93
        foreach ($question->originalanswer as $key => $answerdata) {
94
            if (is_array($answerdata)) {
95
                $answertext = $answerdata['text'];
96
                $answerformat = $answerdata['format'];
97
            } else {
98
                $answertext = $answerdata;
99
                // If no format is set, assume it is a legacy question and use FORMAT_PLAIN.
100
                $answerformat = FORMAT_PLAIN;
101
            }
102
            if (trim($answertext) == '') {
103
                continue;
104
            }
105
 
106
            // Update an existing answer if possible.
107
            $answer = array_shift($oldanswers);
108
            if (!$answer) {
109
                $answer = new stdClass();
110
                $answer->question = $question->id;
111
                $answer->answer   = '';
112
                $answer->feedback = '';
113
                $answer->id       = $DB->insert_record('question_answers', $answer);
114
            }
115
 
116
            $answer->answer = $this->import_or_save_files($question->originalanswer[$key], $context,
117
                    'question', 'answer', $answer->id);
118
            $answer->answerformat = $answerformat;
119
            $answer->fraction = $question->fraction[$key];
120
            $answer->feedback = $this->import_or_save_files($question->feedback[$key],
121
                    $context, 'question', 'answerfeedback', $answer->id);
122
            $answer->feedbackformat = $question->feedback[$key]['format'];
123
 
124
            $DB->update_record("question_answers", $answer);
125
 
126
            // Set up the options object.
127
            if (!$options = array_shift($oldoptions)) {
128
                $options = new stdClass();
129
            }
130
            $options->question            = $question->id;
131
            $options->answer              = $answer->id;
132
            $options->tolerance           = trim($question->tolerance[$key]);
133
            $options->tolerancetype       = trim($question->tolerancetype[$key]);
134
            $options->correctanswerlength = trim($question->correctanswerlength[$key]);
135
            $options->correctanswerformat = trim($question->correctanswerformat[$key]);
136
 
137
            // Save options.
138
            if (isset($options->id)) {
139
                // Reusing existing record.
140
                $DB->update_record('question_calculated', $options);
141
            } else {
142
                // New options.
143
                $DB->insert_record('question_calculated', $options);
144
            }
145
        }
146
 
147
        // Delete old answer records.
148
        if (!empty($oldanswers)) {
149
            foreach ($oldanswers as $oa) {
150
                $DB->delete_records('question_answers', array('id' => $oa->id));
151
            }
152
        }
153
        if (!empty($oldoptions)) {
154
            foreach ($oldoptions as $oo) {
155
                $DB->delete_records('question_calculated', array('id' => $oo->id));
156
            }
157
        }
158
 
159
        $this->save_hints($question, true);
160
 
161
        if (isset($question->import_process) && $question->import_process) {
162
            $this->import_datasets($question);
163
        }
164
 
165
        return true;
166
    }
167
 
168
    protected function validate_answer($answer) {
169
        $error = qtype_calculated_find_formula_errors_in_text($answer);
170
        if ($error) {
171
            throw new coding_exception($error);
172
        }
173
    }
174
 
175
    protected function validate_question_data($question) {
176
        parent::validate_question_data($question);
177
        $this->validate_text($question->correctfeedback['text']);
178
        $this->validate_text($question->partiallycorrectfeedback['text']);
179
        $this->validate_text($question->incorrectfeedback['text']);
180
    }
181
 
182
    protected function make_question_instance($questiondata) {
183
        question_bank::load_question_definition_classes($this->name());
184
        if ($questiondata->options->single) {
185
            $class = 'qtype_calculatedmulti_single_question';
186
        } else {
187
            $class = 'qtype_calculatedmulti_multi_question';
188
        }
189
        return new $class();
190
    }
191
 
192
    protected function initialise_question_instance(question_definition $question, $questiondata) {
193
        question_type::initialise_question_instance($question, $questiondata);
194
 
195
        $question->shuffleanswers = $questiondata->options->shuffleanswers;
196
        $question->answernumbering = $questiondata->options->answernumbering;
197
        if (!empty($questiondata->options->layout)) {
198
            $question->layout = $questiondata->options->layout;
199
        } else {
200
            $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
201
        }
202
 
203
        $question->synchronised = $questiondata->options->synchronize;
204
 
205
        $this->initialise_combined_feedback($question, $questiondata, true);
206
        $this->initialise_question_answers($question, $questiondata, false);
207
 
208
        foreach ($questiondata->options->answers as $a) {
209
            $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength;
210
            $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat;
211
        }
212
 
213
        $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
214
    }
215
 
216
    /**
217
     * Public override method, created so that make_answer will be available
218
     * for use by classes which extend qtype_multichoice_base.
219
     *
220
     * @param stdClass $answer  Moodle DB question_answers object.
221
     * @return question_answer
222
     */
223
    public function make_answer($answer) {
224
        return new qtype_calculatedmulti_answer($answer->id, $answer->answer,
225
                $answer->fraction, $answer->feedback, $answer->feedbackformat);
226
    }
227
 
228
    protected function make_hint($hint) {
229
        return question_hint_with_parts::load_from_record($hint);
230
    }
231
 
232
    public function comment_header($question) {
233
        $strheader = '';
234
        $delimiter = '';
235
 
236
        $answers = $question->options->answers;
237
 
238
        foreach ($answers as $key => $answer) {
239
            $ans = shorten_text($answer->answer, 17, true);
240
            $strheader .= $delimiter.$ans;
241
            $delimiter = '<br/><br/>';
242
        }
243
        return $strheader;
244
    }
245
 
246
    public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
247
            $answers, $data, $number) {
248
 
249
        $comment = new stdClass();
250
        $comment->stranswers = array();
251
        $comment->outsidelimit = false;
252
        $comment->answers = array();
253
 
254
        $answers = fullclone($answers);
255
        foreach ($answers as $key => $answer) {
256
            // Evaluate the equations i.e {=5+4).
257
            $anssubstituted = $this->substitute_variables($answer->answer, $data);
258
            $formulas = $this->find_formulas($anssubstituted);
259
            $replaces = [];
260
            foreach ($formulas as $formula) {
261
                if ($formulaerrors = qtype_calculated_find_formula_errors($formula)) {
262
                    $str = $formulaerrors;
263
                } else {
264
                    eval('$str = ' . $formula . ';');
265
                }
266
                $replaces[$formula] = $str;
267
            }
268
            $anstext = str_replace(array_keys($replaces), array_values($replaces), $anssubstituted);
269
            $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext;
270
        }
271
        return fullclone($comment);
272
    }
273
 
274
    public function get_virtual_qtype() {
275
        return question_bank::get_qtype('multichoice');
276
    }
277
 
278
    public function get_possible_responses($questiondata) {
279
        if ($questiondata->options->single) {
280
            $responses = array();
281
 
282
            foreach ($questiondata->options->answers as $aid => $answer) {
283
                $responses[$aid] = new question_possible_response($answer->answer,
284
                        $answer->fraction);
285
            }
286
 
287
            $responses[null] = question_possible_response::no_response();
288
            return array($questiondata->id => $responses);
289
        } else {
290
            $parts = array();
291
 
292
            foreach ($questiondata->options->answers as $aid => $answer) {
293
                $parts[$aid] = array($aid =>
294
                        new question_possible_response($answer->answer, $answer->fraction));
295
            }
296
 
297
            return $parts;
298
        }
299
    }
300
 
301
    public function move_files($questionid, $oldcontextid, $newcontextid) {
302
        $fs = get_file_storage();
303
 
304
        parent::move_files($questionid, $oldcontextid, $newcontextid);
305
        $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
306
        $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
307
 
308
        $fs->move_area_files_to_new_context($oldcontextid,
309
                $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid);
310
        $fs->move_area_files_to_new_context($oldcontextid,
311
                $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid);
312
        $fs->move_area_files_to_new_context($oldcontextid,
313
                $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid);
314
    }
315
 
316
    protected function delete_files($questionid, $contextid) {
317
        $fs = get_file_storage();
318
 
319
        parent::delete_files($questionid, $contextid);
320
        $this->delete_files_in_answers($questionid, $contextid, true);
321
        $this->delete_files_in_hints($questionid, $contextid);
322
 
323
        $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
324
                'correctfeedback', $questionid);
325
        $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
326
                'partiallycorrectfeedback', $questionid);
327
        $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
328
                'incorrectfeedback', $questionid);
329
    }
330
}