ILIAS  release_8 Revision v8.24
class.assClozeTestExport.php
Go to the documentation of this file.
1<?php
18use ILIAS\Refinery\Random\Group as RandomGroup;
19
20include_once "./Modules/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php";
21
32{
33 private RandomGroup $randomGroup;
34
35 public function __construct($object)
36 {
37 global $DIC;
38
40
41 $this->randomGroup = $DIC->refinery()->random();
42 }
43
49 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false): string
50 {
51 global $DIC;
52 $ilias = $DIC['ilias'];
53
54 include_once "./Services/Math/classes/class.EvalMath.php";
55 $eval = new EvalMath();
56 $eval->suppress_errors = true;
57 include_once("./Services/Xml/classes/class.ilXmlWriter.php");
58 $a_xml_writer = new ilXmlWriter();
59 // set xml header
60 $a_xml_writer->xmlHeader();
61 $a_xml_writer->xmlStartTag("questestinterop");
62 $attrs = array(
63 "ident" => "il_" . IL_INST_ID . "_qst_" . $this->object->getId(),
64 "title" => $this->object->getTitle(),
65 "maxattempts" => $this->object->getNrOfTries()
66 );
67 $a_xml_writer->xmlStartTag("item", $attrs);
68 // add question description
69 $a_xml_writer->xmlElement("qticomment", null, $this->object->getComment());
70 $a_xml_writer->xmlStartTag("itemmetadata");
71 $a_xml_writer->xmlStartTag("qtimetadata");
72 $a_xml_writer->xmlStartTag("qtimetadatafield");
73 $a_xml_writer->xmlElement("fieldlabel", null, "ILIAS_VERSION");
74 $a_xml_writer->xmlElement("fieldentry", null, $ilias->getSetting("ilias_version"));
75 $a_xml_writer->xmlEndTag("qtimetadatafield");
76 $a_xml_writer->xmlStartTag("qtimetadatafield");
77 $a_xml_writer->xmlElement("fieldlabel", null, "QUESTIONTYPE");
78 $a_xml_writer->xmlElement("fieldentry", null, CLOZE_TEST_IDENTIFIER);
79 $a_xml_writer->xmlEndTag("qtimetadatafield");
80 $a_xml_writer->xmlStartTag("qtimetadatafield");
81 $a_xml_writer->xmlElement("fieldlabel", null, "AUTHOR");
82 $a_xml_writer->xmlElement("fieldentry", null, $this->object->getAuthor());
83 $a_xml_writer->xmlEndTag("qtimetadatafield");
84
85 // additional content editing information
87 $this->addGeneralMetadata($a_xml_writer);
88
89 $a_xml_writer->xmlStartTag("qtimetadatafield");
90 $a_xml_writer->xmlElement("fieldlabel", null, "textgaprating");
91 $a_xml_writer->xmlElement("fieldentry", null, $this->object->getTextgapRating());
92 $a_xml_writer->xmlEndTag("qtimetadatafield");
93
94 $a_xml_writer->xmlStartTag("qtimetadatafield");
95 $a_xml_writer->xmlElement("fieldlabel", null, "fixedTextLength");
96 $a_xml_writer->xmlElement("fieldentry", null, $this->object->getFixedTextLength());
97 $a_xml_writer->xmlEndTag("qtimetadatafield");
98
99 $a_xml_writer->xmlStartTag("qtimetadatafield");
100 $a_xml_writer->xmlElement("fieldlabel", null, "identicalScoring");
101 $a_xml_writer->xmlElement("fieldentry", null, $this->object->getIdenticalScoring());
102 $a_xml_writer->xmlEndTag("qtimetadatafield");
103
104 $a_xml_writer->xmlStartTag("qtimetadatafield");
105 $a_xml_writer->xmlElement("fieldlabel", null, "feedback_mode");
106 $a_xml_writer->xmlElement("fieldentry", null, $this->object->getFeedbackMode());
107 $a_xml_writer->xmlEndTag("qtimetadatafield");
108
109 $a_xml_writer->xmlStartTag("qtimetadatafield");
110 $a_xml_writer->xmlElement("fieldlabel", null, "combinations");
111 $a_xml_writer->xmlElement("fieldentry", null, base64_encode(json_encode($this->object->getGapCombinations())));
112 $a_xml_writer->xmlEndTag("qtimetadatafield");
113
114 $a_xml_writer->xmlEndTag("qtimetadata");
115 $a_xml_writer->xmlEndTag("itemmetadata");
116
117 // PART I: qti presentation
118 $attrs = array(
119 "label" => $this->object->getTitle()
120 );
121 $a_xml_writer->xmlStartTag("presentation", $attrs);
122 // add flow to presentation
123 $a_xml_writer->xmlStartTag("flow");
124
125 $questionText = $this->object->getQuestion() ? $this->object->getQuestion() : '&nbsp;';
126 $this->object->addQTIMaterial($a_xml_writer, $questionText);
127
128 $text_parts = preg_split("/\[gap.*?\[\/gap\]/", $this->object->getClozeText());
129
130 // add material with question text to presentation
131 for ($i = 0; $i <= $this->object->getGapCount(); $i++) {
132 $this->object->addQTIMaterial($a_xml_writer, $text_parts[$i]);
133
134 if ($i < $this->object->getGapCount()) {
135 // add gap
136 $gap = $this->object->getGap($i);
137 switch ($gap->getType()) {
138 case CLOZE_SELECT:
139 // comboboxes
140 $attrs = array(
141 "ident" => "gap_$i",
142 "rcardinality" => "Single"
143 );
144 $a_xml_writer->xmlStartTag("response_str", $attrs);
145 $solution = $this->object->getSuggestedSolution($i);
146
147 if ($solution !== null && count($solution)) {
148 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
149 $attrs = array(
150 "label" => "suggested_solution"
151 );
152 $a_xml_writer->xmlStartTag("material", $attrs);
153 $intlink = "il_" . IL_INST_ID . "_" . $matches[2] . "_" . $matches[3];
154 if (strcmp($matches[1], "") != 0) {
155 $intlink = $solution["internal_link"];
156 }
157 $a_xml_writer->xmlElement("mattext", null, $intlink);
158 $a_xml_writer->xmlEndTag("material");
159 }
160 }
161
162 $attrs = array("shuffle" => ($gap->getShuffle() ? "Yes" : "No"));
163 $a_xml_writer->xmlStartTag("render_choice", $attrs);
164
165 // add answers
166 foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $answeritem) {
167 $attrs = array(
168 "ident" => $answeritem->getOrder()
169 );
170 $a_xml_writer->xmlStartTag("response_label", $attrs);
171 $a_xml_writer->xmlStartTag("material");
172 $a_xml_writer->xmlElement("mattext", null, $answeritem->getAnswertext());
173 $a_xml_writer->xmlEndTag("material");
174 $a_xml_writer->xmlEndTag("response_label");
175 }
176 $a_xml_writer->xmlEndTag("render_choice");
177 $a_xml_writer->xmlEndTag("response_str");
178 break;
179 case CLOZE_TEXT:
180 // text fields
181 $attrs = array(
182 "ident" => "gap_$i",
183 "rcardinality" => "Single"
184 );
185 $a_xml_writer->xmlStartTag("response_str", $attrs);
186 $solution = $this->object->getSuggestedSolution($i);
187 if ($solution !== null && count($solution)) {
188 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
189 $attrs = array(
190 "label" => "suggested_solution"
191 );
192 $a_xml_writer->xmlStartTag("material", $attrs);
193 $intlink = "il_" . IL_INST_ID . "_" . $matches[2] . "_" . $matches[3];
194 if (strcmp($matches[1], "") != 0) {
195 $intlink = $solution["internal_link"];
196 }
197 $a_xml_writer->xmlElement("mattext", null, $intlink);
198 $a_xml_writer->xmlEndTag("material");
199 }
200 }
201 $attrs = array(
202 "fibtype" => "String",
203 "prompt" => "Box",
204 "columns" => $gap->getMaxWidth(),
205 "maxchars" => $gap->getGapSize()
206 );
207 $a_xml_writer->xmlStartTag("render_fib", $attrs);
208 $a_xml_writer->xmlEndTag("render_fib");
209 $a_xml_writer->xmlEndTag("response_str");
210 break;
211 case CLOZE_NUMERIC:
212 // numeric fields
213 $attrs = array(
214 "ident" => "gap_$i",
215 "numtype" => "Decimal",
216 "rcardinality" => "Single"
217 );
218 $a_xml_writer->xmlStartTag("response_num", $attrs);
219 $solution = $this->object->getSuggestedSolution($i);
220 if ($solution !== null && count($solution)) {
221 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $solution["internal_link"], $matches)) {
222 $attrs = array(
223 "label" => "suggested_solution"
224 );
225 $a_xml_writer->xmlStartTag("material", $attrs);
226 $intlink = "il_" . IL_INST_ID . "_" . $matches[2] . "_" . $matches[3];
227 if (strcmp($matches[1], "") != 0) {
228 $intlink = $solution["internal_link"];
229 }
230 $a_xml_writer->xmlElement("mattext", null, $intlink);
231 $a_xml_writer->xmlEndTag("material");
232 }
233 }
234 $answeritem = $gap->getItem(0);
235 $attrs = array(
236 "fibtype" => "Decimal",
237 "prompt" => "Box",
238 "columns" => $gap->getMaxWidth(),
239 "maxchars" => $gap->getGapSize()
240 );
241 if (is_object($answeritem)) {
242 if ($eval->e($answeritem->getLowerBound()) !== false) {
243 $attrs["minnumber"] = $answeritem->getLowerBound();
244 }
245 if ($eval->e($answeritem->getUpperBound()) !== false) {
246 $attrs["maxnumber"] = $answeritem->getUpperBound();
247 }
248 }
249 $a_xml_writer->xmlStartTag("render_fib", $attrs);
250 $a_xml_writer->xmlEndTag("render_fib");
251 $a_xml_writer->xmlEndTag("response_num");
252 break;
253 }
254 }
255 }
256 $a_xml_writer->xmlEndTag("flow");
257 $a_xml_writer->xmlEndTag("presentation");
258
259 // PART II: qti resprocessing
260 $a_xml_writer->xmlStartTag("resprocessing");
261 $a_xml_writer->xmlStartTag("outcomes");
262 $a_xml_writer->xmlStartTag("decvar");
263 $a_xml_writer->xmlEndTag("decvar");
264 $a_xml_writer->xmlEndTag("outcomes");
265
266 // add response conditions
267 for ($i = 0; $i < $this->object->getGapCount(); $i++) {
268 $gap = $this->object->getGap($i);
269 switch ($gap->getType()) {
270 case CLOZE_SELECT:
271 foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $answer) {
272 $attrs = array(
273 "continue" => "Yes"
274 );
275 $a_xml_writer->xmlStartTag("respcondition", $attrs);
276 // qti conditionvar
277 $a_xml_writer->xmlStartTag("conditionvar");
278
279 $attrs = array(
280 "respident" => "gap_$i"
281 );
282 $a_xml_writer->xmlElement("varequal", $attrs, $answer->getAnswertext());
283 $a_xml_writer->xmlEndTag("conditionvar");
284 // qti setvar
285 $attrs = array(
286 "action" => "Add"
287 );
288 $a_xml_writer->xmlElement("setvar", $attrs, $answer->getPoints());
289 // qti displayfeedback
290 $linkrefid = "$i" . "_Response_" . $answer->getOrder();
291 $attrs = array(
292 "feedbacktype" => "Response",
293 "linkrefid" => $linkrefid
294 );
295 $a_xml_writer->xmlElement("displayfeedback", $attrs);
296 $a_xml_writer->xmlEndTag("respcondition");
297 }
298 break;
299 case CLOZE_NUMERIC:
300 case CLOZE_TEXT:
301 foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $answer) {
302 $attrs = array(
303 "continue" => "Yes"
304 );
305 $a_xml_writer->xmlStartTag("respcondition", $attrs);
306 // qti conditionvar
307 $a_xml_writer->xmlStartTag("conditionvar");
308 $attrs = array(
309 "respident" => "gap_$i"
310 );
311 $a_xml_writer->xmlElement("varequal", $attrs, $answer->getAnswertext());
312 $a_xml_writer->xmlEndTag("conditionvar");
313 // qti setvar
314 $attrs = array(
315 "action" => "Add"
316 );
317 $a_xml_writer->xmlElement("setvar", $attrs, $answer->getPoints());
318 // qti displayfeedback
319 $linkrefid = "$i" . "_Response_" . $answer->getOrder();
320 $attrs = array(
321 "feedbacktype" => "Response",
322 "linkrefid" => $linkrefid
323 );
324 $a_xml_writer->xmlElement("displayfeedback", $attrs);
325 $a_xml_writer->xmlEndTag("respcondition");
326 }
327 break;
328 }
329 }
330
331 $feedback_allcorrect = $this->object->feedbackOBJ->getGenericFeedbackExportPresentation(
332 $this->object->getId(),
333 true
334 );
335 if (strlen($feedback_allcorrect)) {
336 $attrs = array(
337 "continue" => "Yes"
338 );
339 $a_xml_writer->xmlStartTag("respcondition", $attrs);
340 // qti conditionvar
341 $a_xml_writer->xmlStartTag("conditionvar");
342
343 for ($i = 0; $i < $this->object->getGapCount(); $i++) {
344 $gap = $this->object->getGap($i);
345 $indexes = $gap->getBestSolutionIndexes();
346 if ($i > 0) {
347 $a_xml_writer->xmlStartTag("and");
348 }
349 switch ($gap->getType()) {
350 case CLOZE_TEXT:
351 case CLOZE_NUMERIC:
352 case CLOZE_SELECT:
353 $k = 0;
354 foreach ($indexes as $key) {
355 if ($k > 0) {
356 $a_xml_writer->xmlStartTag("or");
357 }
358 $attrs = array(
359 "respident" => "gap_$i"
360 );
361 $answer = $gap->getItem($key);
362 $a_xml_writer->xmlElement("varequal", $attrs, $answer->getAnswertext());
363 if ($k > 0) {
364 $a_xml_writer->xmlEndTag("or");
365 }
366 $k++;
367 }
368 break;
369 }
370 if ($i > 0) {
371 $a_xml_writer->xmlEndTag("and");
372 }
373 }
374 $a_xml_writer->xmlEndTag("conditionvar");
375 // qti displayfeedback
376 $attrs = array(
377 "feedbacktype" => "Response",
378 "linkrefid" => "response_allcorrect"
379 );
380 $a_xml_writer->xmlElement("displayfeedback", $attrs);
381 $a_xml_writer->xmlEndTag("respcondition");
382 }
383 $feedback_onenotcorrect = $this->object->feedbackOBJ->getGenericFeedbackExportPresentation(
384 $this->object->getId(),
385 false
386 );
387 if (strlen($feedback_onenotcorrect)) {
388 $attrs = array(
389 "continue" => "Yes"
390 );
391 $a_xml_writer->xmlStartTag("respcondition", $attrs);
392 // qti conditionvar
393 $a_xml_writer->xmlStartTag("conditionvar");
394
395 $a_xml_writer->xmlStartTag("not");
396 for ($i = 0; $i < $this->object->getGapCount(); $i++) {
397 $gap = $this->object->getGap($i);
398 $indexes = $gap->getBestSolutionIndexes();
399 if ($i > 0) {
400 $a_xml_writer->xmlStartTag("and");
401 }
402 switch ($gap->getType()) {
403 case CLOZE_TEXT:
404 case CLOZE_NUMERIC:
405 case CLOZE_SELECT:
406 $k = 0;
407 foreach ($indexes as $key) {
408 if ($k > 0) {
409 $a_xml_writer->xmlStartTag("or");
410 }
411 $attrs = array(
412 "respident" => "gap_$i"
413 );
414 $answer = $gap->getItem($key);
415 $a_xml_writer->xmlElement("varequal", $attrs, $answer->getAnswertext());
416 if ($k > 0) {
417 $a_xml_writer->xmlEndTag("or");
418 }
419 $k++;
420 }
421 break;
422 }
423 if ($i > 0) {
424 $a_xml_writer->xmlEndTag("and");
425 }
426 }
427 $a_xml_writer->xmlEndTag("not");
428 $a_xml_writer->xmlEndTag("conditionvar");
429 // qti displayfeedback
430 $attrs = array(
431 "feedbacktype" => "Response",
432 "linkrefid" => "response_onenotcorrect"
433 );
434 $a_xml_writer->xmlElement("displayfeedback", $attrs);
435 $a_xml_writer->xmlEndTag("respcondition");
436 }
437
438 $a_xml_writer->xmlEndTag("resprocessing");
439
440 // PART III: qti itemfeedback
441 for ($i = 0; $i < $this->object->getGapCount(); $i++) {
442 $gap = $this->object->getGap($i);
443 switch ($gap->getType()) {
444 case CLOZE_TEXT:
445 case CLOZE_NUMERIC:
446 case CLOZE_SELECT:
447 break;
448 }
449 }
450 $this->exportAnswerSpecificFeedbacks($a_xml_writer);
451
452 if (strlen($feedback_allcorrect)) {
453 $attrs = array(
454 "ident" => "response_allcorrect",
455 "view" => "All"
456 );
457 $a_xml_writer->xmlStartTag("itemfeedback", $attrs);
458 // qti flow_mat
459 $a_xml_writer->xmlStartTag("flow_mat");
460 $this->object->addQTIMaterial($a_xml_writer, $feedback_allcorrect);
461 $a_xml_writer->xmlEndTag("flow_mat");
462 $a_xml_writer->xmlEndTag("itemfeedback");
463 }
464 if (strlen($feedback_onenotcorrect)) {
465 $attrs = array(
466 "ident" => "response_onenotcorrect",
467 "view" => "All"
468 );
469 $a_xml_writer->xmlStartTag("itemfeedback", $attrs);
470 // qti flow_mat
471 $a_xml_writer->xmlStartTag("flow_mat");
472 $this->object->addQTIMaterial($a_xml_writer, $feedback_onenotcorrect);
473 $a_xml_writer->xmlEndTag("flow_mat");
474 $a_xml_writer->xmlEndTag("itemfeedback");
475 }
476
477 $a_xml_writer = $this->addSolutionHints($a_xml_writer);
478
479 $a_xml_writer->xmlEndTag("item");
480 $a_xml_writer->xmlEndTag("questestinterop");
481
482 $xml = $a_xml_writer->xmlDumpMem(false);
483 if (!$a_include_header) {
484 $pos = strpos($xml, "?>");
485 $xml = substr($xml, $pos + 2);
486 }
487 return $xml;
488 }
489
493 protected function exportAnswerSpecificFeedbacks(ilXmlWriter $xmlWriter): void
494 {
495 require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php';
496 $feedbackIdentifierList = new ilAssSpecificFeedbackIdentifierList();
497 $feedbackIdentifierList->load($this->object->getId());
498
499 foreach ($feedbackIdentifierList as $fbIdentifier) {
500 $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation(
501 $this->object->getId(),
502 $fbIdentifier->getQuestionIndex(),
503 $fbIdentifier->getAnswerIndex()
504 );
505
506 $xmlWriter->xmlStartTag("itemfeedback", array(
507 "ident" => $this->buildQtiExportIdent($fbIdentifier), "view" => "All"
508 ));
509
510 $xmlWriter->xmlStartTag("flow_mat");
511 $this->object->addQTIMaterial($xmlWriter, $feedback);
512 $xmlWriter->xmlEndTag("flow_mat");
513
514 $xmlWriter->xmlEndTag("itemfeedback");
515 }
516 }
517
522 public function buildQtiExportIdent(ilAssSpecificFeedbackIdentifier $fbIdentifier): string
523 {
524 return "{$fbIdentifier->getQuestionIndex()}_{$fbIdentifier->getAnswerIndex()}";
525 }
526}
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
exportAnswerSpecificFeedbacks(ilXmlWriter $xmlWriter)
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
Returns a QTI xml representation of the question Returns a QTI xml representation of the question and...
__construct($object)
assQuestionExport constructor
buildQtiExportIdent(ilAssSpecificFeedbackIdentifier $fbIdentifier)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
addGeneralMetadata(ilXmlWriter $xmlwriter)
addSolutionHints(ilXmlWriter $writer)
addAdditionalContentEditingModeInformation(ilXmlWriter $a_xml_writer)
adds a qti meta data field for ilias specific information of "additional content editing mode" (xml w...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
xmlEndTag(string $tag)
Writes an endtag.
xmlStartTag(string $tag, ?array $attrs=null, bool $empty=false, bool $encode=true, bool $escape=true)
Writes a starttag.
const IL_INST_ID
Definition: constants.php:40
global $DIC
Definition: feed.php:28
const CLOZE_TEST_IDENTIFIER
Question identifier constants.
const CLOZE_NUMERIC
const CLOZE_SELECT
const CLOZE_TEXT
Cloze question constants.
$i
Definition: metadata.php:41
$xml
Definition: metadata.php:351
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193