MDL-34050 Lesson Module: improved matching question logic for checking user response...
[moodle.git] / mod / lesson / pagetypes / matching.php
CommitLineData
0a4abb73
SH
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * Matching
20 *
cc3dbaaa
PS
21 * @package mod
22 * @subpackage lesson
23 * @copyright 2009 Sam Hemelryk
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0a4abb73
SH
25 **/
26
1e7f8ea2
PS
27defined('MOODLE_INTERNAL') || die();
28
0a4abb73
SH
29/** Matching question type */
30define("LESSON_PAGE_MATCHING", "5");
31
32class lesson_page_type_matching extends lesson_page {
33
34 protected $type = lesson_page::TYPE_QUESTION;
35 protected $typeid = LESSON_PAGE_MATCHING;
36 protected $typeidstring = 'matching';
37 protected $string = null;
38
39 public function get_typeid() {
40 return $this->typeid;
41 }
42 public function get_typestring() {
43 if ($this->string===null) {
44 $this->string = get_string($this->typeidstring, 'lesson');
45 }
46 return $this->string;
47 }
48 public function get_idstring() {
49 return $this->typeidstring;
50 }
51 public function display($renderer, $attempt) {
52 global $USER, $CFG, $PAGE;
53 $mform = $this->make_answer_form($attempt);
54 $data = new stdClass;
55 $data->id = $PAGE->cm->id;
56 $data->pageid = $this->properties->id;
57 $mform->set_data($data);
58 return $mform->display();
59 }
60
61 protected function make_answer_form($attempt=null) {
62 global $USER, $CFG;
4fb3f253 63 // don't shuffle answers (could be an option??)
57a4bd03
RW
64 $getanswers = array_slice($this->get_answers(), 2);
65
57a4bd03
RW
66 $answers = array();
67 foreach ($getanswers as $getanswer) {
68 $answers[$getanswer->id] = $getanswer;
69 }
70
0a4abb73
SH
71 $responses = array();
72 foreach ($answers as $answer) {
73 // get all the response
74 if ($answer->response != NULL) {
2e48a424 75 $responses[$answer->id] = trim($answer->response);
0a4abb73
SH
76 }
77 }
78
04932409 79 $responseoptions = array(''=>get_string('choosedots'));
0a4abb73 80 if (!empty($responses)) {
2e48a424
RW
81 $shuffleresponses = $responses;
82 shuffle($shuffleresponses);
83 $shuffleresponses = array_unique($shuffleresponses);
84 foreach ($shuffleresponses as $response) {
85 $key = array_search($response, $responses);
86 $responseoptions[$key] = $response;
0a4abb73
SH
87 }
88 }
89 if (isset($USER->modattempts[$this->lesson->id]) && !empty($attempt->useranswer)) {
90 $useranswers = explode(',', $attempt->useranswer);
91 $t = 0;
92 } else {
93 $useranswers = array();
94 }
95
96 $action = $CFG->wwwroot.'/mod/lesson/continue.php';
97 $params = array('answers'=>$answers, 'useranswers'=>$useranswers, 'responseoptions'=>$responseoptions, 'lessonid'=>$this->lesson->id, 'contents'=>$this->get_contents());
98 $mform = new lesson_display_answer_form_matching($action, $params);
99 return $mform;
100 }
101
102 public function create_answers($properties) {
103 global $DB;
104 // now add the answers
105 $newanswer = new stdClass;
106 $newanswer->lessonid = $this->lesson->id;
107 $newanswer->pageid = $this->properties->id;
108 $newanswer->timecreated = $this->properties->timecreated;
109
110 $answers = array();
111
112 // need to add two to offset correct response and wrong response
113 $this->lesson->maxanswers = $this->lesson->maxanswers + 2;
114 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
115 $answer = clone($newanswer);
981debb7 116 if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
01c37ef1
PS
117 $answer->answer = $properties->answer_editor[$i]['text'];
118 $answer->answerformat = $properties->answer_editor[$i]['format'];
981debb7 119 }
58b72abc
JR
120 if (!empty($properties->response_editor[$i])) {
121 $answer->response = $properties->response_editor[$i];
122 $answer->responseformat = 0;
981debb7
SH
123 }
124
b43251b8
RW
125 if (isset($properties->jumpto[$i])) {
126 $answer->jumpto = $properties->jumpto[$i];
127 }
128 if ($this->lesson->custom && isset($properties->score[$i])) {
129 $answer->score = $properties->score[$i];
130 }
131
6d84e82f 132 if (isset($answer->answer) && $answer->answer != '') {
0a4abb73
SH
133 $answer->id = $DB->insert_record("lesson_answers", $answer);
134 $answers[$answer->id] = new lesson_page_answer($answer);
135 } else if ($i < 2) {
136 $answer->id = $DB->insert_record("lesson_answers", $answer);
137 $answers[$answer->id] = new lesson_page_answer($answer);
138 } else {
139 break;
140 }
141 }
142 $this->answers = $answers;
143 return $answers;
144 }
145
146 public function check_answer() {
df0442c6 147 global $CFG, $PAGE;
01c37ef1 148
39790bd8 149 $formattextdefoptions = new stdClass();
01c37ef1
PS
150 $formattextdefoptions->noclean = true;
151 $formattextdefoptions->para = false;
152
0a4abb73
SH
153 $result = parent::check_answer();
154
155 $mform = $this->make_answer_form();
156
157 $data = $mform->get_data();
158 require_sesskey();
159
160 if (!$data) {
a6855934 161 redirect(new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$this->properties->id)));
0a4abb73 162 }
2f67a9b3 163
0a4abb73
SH
164 $response = $data->response;
165 if (!is_array($response)) {
166 $result->noanswer = true;
167 return $result;
168 }
a675ada5 169
0a4abb73 170 $answers = $this->get_answers();
a675ada5
PS
171
172 $correct = array_shift($answers);
173 $wrong = array_shift($answers);
174
175 foreach ($answers as $key=>$answer) {
2e48a424
RW
176 if ($answer->answer !== '' or $answer->response !== '') {
177 $answers[$answer->id] = $answer;
0a4abb73 178 }
2e48a424 179 unset($answers[$key]);
0a4abb73
SH
180 }
181 // get he users exact responses for record keeping
a675ada5 182 $hits = 0;
0a4abb73 183 $userresponse = array();
2e48a424
RW
184 foreach ($response as $id => $value) {
185 $userresponse[] = $value;
186 // Make sure the user's answer is exist in question's answer
187 if (array_key_exists($id, $answers)) {
188 $answer = $answers[$id];
189 $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$answers[$value]->response;
190 if ($id == $value) {
a675ada5
PS
191 $hits++;
192 }
0a4abb73
SH
193 }
194 }
2e48a424 195
0a4abb73
SH
196 $result->userresponse = implode(",", $userresponse);
197
a675ada5 198 if ($hits == count($answers)) {
0a4abb73 199 $result->correctanswer = true;
a675ada5
PS
200 $result->response = format_text($correct->answer, $correct->answerformat, $formattextdefoptions);
201 $result->answerid = $correct->id;
202 $result->newpageid = $correct->jumpto;
0a4abb73 203 } else {
a675ada5
PS
204 $result->correctanswer = false;
205 $result->response = format_text($wrong->answer, $wrong->answerformat, $formattextdefoptions);
206 $result->answerid = $wrong->id;
207 $result->newpageid = $wrong->jumpto;
0a4abb73 208 }
a675ada5 209
0a4abb73
SH
210 return $result;
211 }
212
213 public function option_description_string() {
214 return get_string("firstanswershould", "lesson");
215 }
216
217 public function display_answers(html_table $table) {
218 $answers = $this->get_answers();
219 $options = new stdClass;
220 $options->noclean = true;
221 $options->para = false;
222 $i = 1;
223 $n = 0;
2f67a9b3 224
0a4abb73
SH
225 foreach ($answers as $answer) {
226 if ($n < 2) {
227 if ($answer->answer != NULL) {
228 $cells = array();
229 if ($n == 0) {
230 $cells[] = "<span class=\"label\">".get_string("correctresponse", "lesson").'</span>';
231 } else {
232 $cells[] = "<span class=\"label\">".get_string("wrongresponse", "lesson").'</span>';
233 }
01c37ef1 234 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
8cea545e 235 $table->data[] = new html_table_row($cells);
0a4abb73 236 }
b43251b8
RW
237
238 if ($n == 0) {
239 $cells = array();
240 $cells[] = '<span class="label">'.get_string("correctanswerscore", "lesson")."</span>: ";
241 $cells[] = $answer->score;
242 $table->data[] = new html_table_row($cells);
243
244 $cells = array();
245 $cells[] = '<span class="label">'.get_string("correctanswerjump", "lesson")."</span>: ";
246 $cells[] = $this->get_jump_name($answer->jumpto);
247 $table->data[] = new html_table_row($cells);
248 } elseif ($n == 1) {
249 $cells = array();
250 $cells[] = '<span class="label">'.get_string("wronganswerscore", "lesson")."</span>: ";
251 $cells[] = $answer->score;
252 $table->data[] = new html_table_row($cells);
253
254 $cells = array();
255 $cells[] = '<span class="label">'.get_string("wronganswerjump", "lesson")."</span>: ";
256 $cells[] = $this->get_jump_name($answer->jumpto);
257 $table->data[] = new html_table_row($cells);
258 }
259
260 if ($n === 0){
261 $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
262 }
0a4abb73
SH
263 $n++;
264 $i--;
265 } else {
266 $cells = array();
267 if ($this->lesson->custom && $answer->score > 0) {
268 // if the score is > 0, then it is correct
269 $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
270 } else if ($this->lesson->custom) {
271 $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
272 } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) {
273 $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
274 } else {
275 $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
276 }
01c37ef1 277 $cells[] = format_text($answer->answer, $answer->answerformat, $options);
8cea545e 278 $table->data[] = new html_table_row($cells);
0a4abb73
SH
279
280 $cells = array();
281 $cells[] = '<span class="label">'.get_string("matchesanswer", "lesson")." $i</span>: ";
01c37ef1 282 $cells[] = format_text($answer->response, $answer->responseformat, $options);
8cea545e 283 $table->data[] = new html_table_row($cells);
0a4abb73 284 }
0a4abb73
SH
285 $i++;
286 }
287 return $table;
288 }
981debb7
SH
289 /**
290 * Updates the page and its answers
291 *
292 * @global moodle_database $DB
293 * @global moodle_page $PAGE
294 * @param stdClass $properties
295 * @return bool
296 */
0ed6f712 297 public function update($properties, $context = null, $maxbytes = null) {
0a4abb73
SH
298 global $DB, $PAGE;
299 $answers = $this->get_answers();
300 $properties->id = $this->properties->id;
301 $properties->lessonid = $this->lesson->id;
5918e371 302 $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$PAGE->course->maxbytes), context_module::instance($PAGE->cm->id), 'mod_lesson', 'page_contents', $properties->id);
0a4abb73
SH
303 $DB->update_record("lesson_pages", $properties);
304
305 // need to add two to offset correct response and wrong response
306 $this->lesson->maxanswers += 2;
307 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
308 if (!array_key_exists($i, $this->answers)) {
309 $this->answers[$i] = new stdClass;
310 $this->answers[$i]->lessonid = $this->lesson->id;
311 $this->answers[$i]->pageid = $this->id;
312 $this->answers[$i]->timecreated = $this->timecreated;
313 }
981debb7
SH
314
315 if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
01c37ef1
PS
316 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
317 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
981debb7 318 }
58b72abc
JR
319 if (!empty($properties->response_editor[$i])) {
320 $this->answers[$i]->response = $properties->response_editor[$i];
321 $this->answers[$i]->responseformat = 0;
981debb7
SH
322 }
323
b43251b8
RW
324 if (isset($properties->jumpto[$i])) {
325 $this->answers[$i]->jumpto = $properties->jumpto[$i];
326 }
327 if ($this->lesson->custom && isset($properties->score[$i])) {
328 $this->answers[$i]->score = $properties->score[$i];
329 }
330
6d84e82f
RW
331 // we don't need to check for isset here because properties called it's own isset method.
332 if ($this->answers[$i]->answer != '') {
0a4abb73
SH
333 if (!isset($this->answers[$i]->id)) {
334 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
335 } else {
336 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
337 }
0a4abb73
SH
338 } else if ($i < 2) {
339 if (!isset($this->answers[$i]->id)) {
340 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
341 } else {
342 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
343 }
344
981debb7
SH
345 } else if (isset($this->answers[$i]->id)) {
346 $DB->delete_records('lesson_answers', array('id'=>$this->answers[$i]->id));
347 unset($this->answers[$i]);
0a4abb73
SH
348 }
349 }
350 return true;
351 }
352 public function stats(array &$pagestats, $tries) {
353 if(count($tries) > $this->lesson->maxattempts) { // if there are more tries than the max that is allowed, grab the last "legal" attempt
354 $temp = $tries[$this->lesson->maxattempts - 1];
355 } else {
356 // else, user attempted the question less than the max, so grab the last one
357 $temp = end($tries);
358 }
359 if ($temp->correct) {
360 if (isset($pagestats[$temp->pageid]["correct"])) {
361 $pagestats[$temp->pageid]["correct"]++;
362 } else {
363 $pagestats[$temp->pageid]["correct"] = 1;
364 }
365 }
366 if (isset($pagestats[$temp->pageid]["total"])) {
367 $pagestats[$temp->pageid]["total"]++;
368 } else {
369 $pagestats[$temp->pageid]["total"] = 1;
370 }
371 return true;
372 }
373 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
374 $answers = array();
375 foreach ($this->get_answers() as $answer) {
376 $answers[$answer->id] = $answer;
377 }
378 $formattextdefoptions = new stdClass;
379 $formattextdefoptions->para = false; //I'll use it widely in this page
380 foreach ($answers as $answer) {
381 if ($n == 0 && $useranswer != NULL && $useranswer->correct) {
382 if ($answer->response == NULL && $useranswer != NULL) {
383 $answerdata->response = get_string("thatsthecorrectanswer", "lesson");
384 } else {
385 $answerdata->response = $answer->response;
386 }
c90ba16f 387 if ($this->lesson->custom) {
6d3ae910
AB
388 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
389 } else {
390 $answerdata->score = get_string("receivedcredit", "lesson");
391 }
0a4abb73
SH
392 } elseif ($n == 1 && $useranswer != NULL && !$useranswer->correct) {
393 if ($answer->response == NULL && $useranswer != NULL) {
394 $answerdata->response = get_string("thatsthewronganswer", "lesson");
395 } else {
396 $answerdata->response = $answer->response;
397 }
c90ba16f
RW
398 if ($this->lesson->custom) {
399 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
400 } else {
401 $answerdata->score = get_string("didnotreceivecredit", "lesson");
0a4abb73 402 }
c90ba16f 403 } elseif ($n > 1) {
68ca508f
RW
404 $data = '<label class="accesshide" for="answer_' . $n . '">' . get_string('answer', 'lesson') . '</label>';
405 $data .= "<select id=\"answer_". $n ."\" disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->answer))."</option></select>";
0a4abb73
SH
406 if ($useranswer != NULL) {
407 $userresponse = explode(",", $useranswer->useranswer);
68ca508f
RW
408 $data .= '<label class="accesshide" for="stu_answer_response_' . $n . '">' . get_string('matchesanswer', 'lesson') . '</label>';
409 $data .= "<select id=\"stu_answer_response_" . $n . "\" disabled=\"disabled\"><option selected=\"selected\">";
981debb7
SH
410 if (array_key_exists($i, $userresponse)) {
411 $data .= strip_tags(format_string($answers[$userresponse[$i]]->response));
412 }
413 $data .= "</option></select>";
0a4abb73 414 } else {
68ca508f
RW
415 $data .= '<label class="accesshide" for="answer_response_' . $n . '">' . get_string('matchesanswer', 'lesson') . '</label>';
416 $data .= "<select id=\"answer_response_" . $n . "\" disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->response))."</option></select>";
0a4abb73
SH
417 }
418
419 if ($n == 2) {
420 if (isset($pagestats[$this->properties->id])) {
421 if (!array_key_exists('correct', $pagestats[$this->properties->id])) {
422 $pagestats[$this->properties->id]["correct"] = 0;
423 }
424 $percent = $pagestats[$this->properties->id]["correct"] / $pagestats[$this->properties->id]["total"] * 100;
425 $percent = round($percent, 2);
426 $percent .= "% ".get_string("answeredcorrectly", "lesson");
427 } else {
428 $percent = get_string("nooneansweredthisquestion", "lesson");
429 }
430 } else {
c90ba16f 431 $percent = '';
0a4abb73
SH
432 }
433
434 $answerdata->answers[] = array($data, $percent);
435 $i++;
436 }
437 $n++;
438 $answerpage->answerdata = $answerdata;
439 }
440 return $answerpage;
441 }
442 public function get_jumps() {
443 global $DB;
86d99db3 444 // The jumps for matching question type are stored in the 1st and 2nd answer record.
0a4abb73 445 $jumps = array();
04932409 446 if ($answers = $DB->get_records("lesson_answers", array("lessonid" => $this->lesson->id, "pageid" => $this->properties->id), 'id', '*', 0, 2)) {
0a4abb73
SH
447 foreach ($answers as $answer) {
448 $jumps[] = $this->get_jump_name($answer->jumpto);
449 }
9170dea7
SH
450 } else {
451 $jumps[] = $this->get_jump_name($this->properties->nextpageid);
0a4abb73
SH
452 }
453 return $jumps;
454 }
455}
456
457class lesson_add_page_form_matching extends lesson_add_page_form_base {
458
459 public $qtype = 'matching';
460 public $qtypestring = 'matching';
461
462 public function custom_definition() {
463
464 $this->_form->addElement('header', 'correctresponse', get_string('correctresponse', 'lesson'));
cb2c1963 465 $this->_form->addElement('editor', 'answer_editor[0]', get_string('correctresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
76f5f66f 466 $this->add_jumpto(0, get_string('correctanswerjump','lesson'), LESSON_NEXTPAGE);
b43251b8 467 $this->add_score(0, get_string("correctanswerscore", "lesson"), 1);
0a4abb73
SH
468
469 $this->_form->addElement('header', 'wrongresponse', get_string('wrongresponse', 'lesson'));
cb2c1963 470 $this->_form->addElement('editor', 'answer_editor[1]', get_string('wrongresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
76f5f66f 471 $this->add_jumpto(1, get_string('wronganswerjump','lesson'), LESSON_THISPAGE);
b43251b8 472 $this->add_score(1, get_string("wronganswerscore", "lesson"), 0);
0a4abb73
SH
473
474 for ($i = 2; $i < $this->_customdata['lesson']->maxanswers+2; $i++) {
475 $this->_form->addElement('header', 'matchingpair'.($i-1), get_string('matchingpair', 'lesson', $i-1));
a675ada5 476 $this->add_answer($i, NULL, ($i < 4));
58b72abc
JR
477 $required = ($i < 4);
478 $label = get_string('matchesanswer','lesson');
479 $count = $i;
480 $this->_form->addElement('text', 'response_editor['.$count.']', $label, array('size'=>'50'));
481 $this->_form->setDefault('response_editor['.$count.']', '');
482 if ($required) {
483 $this->_form->addRule('response_editor['.$count.']', get_string('required'), 'required', null, 'client');
484 }
0a4abb73
SH
485 }
486 }
487}
488
0a4abb73
SH
489class lesson_display_answer_form_matching extends moodleform {
490
491 public function definition() {
492 global $USER, $OUTPUT;
493 $mform = $this->_form;
494 $answers = $this->_customdata['answers'];
495 $useranswers = $this->_customdata['useranswers'];
496 $responseoptions = $this->_customdata['responseoptions'];
497 $lessonid = $this->_customdata['lessonid'];
498 $contents = $this->_customdata['contents'];
499
ffdf7f8a
DM
500 $mform->addElement('header', 'pageheader');
501
502 $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
0a4abb73 503
abd5c24e
RW
504 $hasattempt = false;
505 $disabled = '';
506 if (isset($useranswers) && !empty($useranswers)) {
507 $hasattempt = true;
508 $disabled = array('disabled' => 'disabled');
509 }
510
0a4abb73
SH
511 $options = new stdClass;
512 $options->para = false;
513 $options->noclean = true;
514
515 $mform->addElement('hidden', 'id');
516 $mform->setType('id', PARAM_INT);
517
518 $mform->addElement('hidden', 'pageid');
519 $mform->setType('pageid', PARAM_INT);
520
521 $i = 0;
522 foreach ($answers as $answer) {
523 $mform->addElement('html', '<div class="answeroption">');
524 if ($answer->response != NULL) {
abd5c24e
RW
525 $responseid = 'response['.$answer->id.']';
526 if ($hasattempt) {
527 $responseid = 'response_'.$answer->id;
528 $mform->addElement('hidden', 'response['.$answer->id.']', htmlspecialchars(trim($answers[$useranswers[$i]]->response)));
529 $mform->setType('response['.$answer->id.']', PARAM_TEXT);
530 }
531 $mform->addElement('select', $responseid, format_text($answer->answer,$answer->answerformat,$options), $responseoptions, $disabled);
532 $mform->setType($responseid, PARAM_TEXT);
533 if ($hasattempt) {
534 $mform->setDefault($responseid, htmlspecialchars(trim($answers[$useranswers[$i]]->response))); //TODO: this is suspicious
0a4abb73 535 } else {
abd5c24e 536 $mform->setDefault($responseid, 'answeroption');
0a4abb73
SH
537 }
538 }
539 $mform->addElement('html', '</div>');
540 $i++;
541 }
abd5c24e
RW
542 if ($hasattempt) {
543 $this->add_action_buttons(null, get_string("nextpage", "lesson"));
544 } else {
545 $this->add_action_buttons(null, get_string("submit", "lesson"));
546 }
0a4abb73
SH
547 }
548
ffdf7f8a 549}