ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.assClozeTestImport.php
Go to the documentation of this file.
1 <?php
2 
29 {
43  public function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_counter, $import_mapping): array
44  {
45  global $DIC;
46  $ilUser = $DIC['ilUser'];
47 
48  // empty session variable for imported xhtml mobs
49  ilSession::clear('import_mob_xhtml');
50  $presentation = $item->getPresentation();
51 
52  $questiontext = $this->processNonAbstractedImageReferences(
53  $item->getMetadataEntry("question") ?? '&nbsp;',
54  $item->getIliasSourceNic()
55  );
56 
57  $clozetext_array = [];
58  $shuffle = 0;
59  $gaps = [];
60  foreach ($presentation->order as $entry) {
61  switch ($entry["type"]) {
62  case "material":
63 
64  $material_string = $this->QTIMaterialToString(
65  $presentation->material[$entry["index"]]
66  );
67 
68  if ($questiontext === '&nbsp;') {
69  //needs to be checked here to suport imports from >= 5.4 < 9.0
70  $questiontext = $material_string;
71  } else {
72  array_push($clozetext_array, $material_string);
73  }
74 
75  break;
76  case "response":
77  $response = $presentation->response[$entry["index"]];
78  $rendertype = $response->getRenderType();
79  array_push($clozetext_array, "<<" . $response->getIdent() . ">>");
80 
81  switch (strtolower(get_class($response->getRenderType()))) {
82  case "ilqtirenderfib":
83  switch ($response->getRenderType()->getFibtype()) {
86  array_push(
87  $gaps,
88  [
89  "ident" => $response->getIdent(),
90  "type" => CLOZE_NUMERIC,
91  "answers" => [],
92  "minnumber" => $response->getRenderType()->getMinnumber(),
93  "maxnumber" => $response->getRenderType()->getMaxnumber(),
94  'gap_size' => $response->getRenderType()->getMaxchars()
95  ]
96  );
97  break;
98  default:
100  array_push(
101  $gaps,
102  ["ident" => $response->getIdent(),
103  "type" => CLOZE_TEXT,
104  "answers" => [],
105  'gap_size' => $response->getRenderType()->getMaxchars()
106  ]
107  );
108  break;
109  }
110  break;
111  case "ilqtirenderchoice":
112  $answers = [];
113  $shuffle = $rendertype->getShuffle();
114  $answerorder = 0;
115  foreach ($rendertype->response_labels as $response_label) {
116  $ident = $response_label->getIdent();
117  $answertext = "";
118  foreach ($response_label->material as $mat) {
119  $answertext .= $this->QTIMaterialToString($mat);
120  }
121  $answers[$ident] = [
122  "answertext" => $answertext,
123  "points" => 0,
124  "answerorder" => $answerorder++,
125  "action" => "",
126  "shuffle" => $rendertype->getShuffle()
127  ];
128  }
129  array_push($gaps, ["ident" => $response->getIdent(), "type" => CLOZE_SELECT, "shuffle" => $rendertype->getShuffle(), "answers" => $answers]);
130  break;
131  }
132  break;
133  }
134  }
135  $responses = [];
136  $feedbacks = [];
137  $feedbacksgeneric = [];
138  foreach ($item->resprocessing as $resprocessing) {
139  foreach ($resprocessing->respcondition as $respcondition) {
140  $ident = "";
141  $correctness = 1;
142  $conditionvar = $respcondition->getConditionvar();
143  foreach ($conditionvar->order as $order) {
144  switch ($order["field"]) {
145  case "varequal":
146  $equals = $conditionvar->varequal[$order["index"]]->getContent();
147  $gapident = $conditionvar->varequal[$order["index"]]->getRespident();
148  break;
149  }
150  }
151  foreach ($respcondition->setvar as $setvar) {
152  if (strcmp($gapident, "") != 0) {
153  foreach ($gaps as $gi => $g) {
154  if (strcmp($g["ident"], $gapident) == 0) {
155  if ($g["type"] == CLOZE_SELECT) {
156  foreach ($gaps[$gi]["answers"] as $ai => $answer) {
157  if (strcmp($answer["answertext"], $equals) == 0) {
158  $gaps[$gi]["answers"][$ai]["action"] = $setvar->getAction();
159  $gaps[$gi]["answers"][$ai]["points"] = $setvar->getContent();
160  }
161  }
162  } elseif ($g["type"] == CLOZE_TEXT) {
163  array_push($gaps[$gi]["answers"], [
164  "answertext" => $equals,
165  "points" => $setvar->getContent(),
166  "answerorder" => count($gaps[$gi]["answers"]),
167  "action" => $setvar->getAction()
168 
169  ]);
170  } elseif ($g["type"] == CLOZE_NUMERIC) {
171  array_push($gaps[$gi]["answers"], [
172  "answertext" => $equals,
173  "points" => $setvar->getContent(),
174  "answerorder" => count($gaps[$gi]["answers"]),
175  "action" => $setvar->getAction()
176  ]);
177  }
178  }
179  }
180  }
181  }
182 
183  if (count($respcondition->displayfeedback)) {
184  foreach ($respcondition->displayfeedback as $feedbackpointer) {
185  if (strlen($feedbackpointer->getLinkrefid())) {
186  foreach ($item->itemfeedback as $ifb) {
187  if (strcmp($ifb->getIdent(), "response_allcorrect") == 0) {
188  // found a feedback for the identifier
189  if (count($ifb->material)) {
190  foreach ($ifb->material as $material) {
191  $feedbacksgeneric[1] = $material;
192  }
193  }
194  if ((count($ifb->flow_mat) > 0)) {
195  foreach ($ifb->flow_mat as $fmat) {
196  if (count($fmat->material)) {
197  foreach ($fmat->material as $material) {
198  $feedbacksgeneric[1] = $material;
199  }
200  }
201  }
202  }
203  } elseif (strcmp($ifb->getIdent(), "response_onenotcorrect") == 0) {
204  // found a feedback for the identifier
205  if (count($ifb->material)) {
206  foreach ($ifb->material as $material) {
207  $feedbacksgeneric[0] = $material;
208  }
209  }
210  if ((count($ifb->flow_mat) > 0)) {
211  foreach ($ifb->flow_mat as $fmat) {
212  if (count($fmat->material)) {
213  foreach ($fmat->material as $material) {
214  $feedbacksgeneric[0] = $material;
215  }
216  }
217  }
218  }
219  } else {
220  // found a feedback for the identifier
221  if (count($ifb->material)) {
222  foreach ($ifb->material as $material) {
223  $feedbacks[$ifb->getIdent()] = $material;
224  }
225  }
226  if ((count($ifb->flow_mat) > 0)) {
227  foreach ($ifb->flow_mat as $fmat) {
228  if (count($fmat->material)) {
229  foreach ($fmat->material as $material) {
230  $feedbacks[$ifb->getIdent()] = $material;
231  }
232  }
233  }
234  }
235  }
236  }
237  }
238  }
239  }
240  }
241  }
242 
243  $this->addGeneralMetadata($item);
244  $this->object->setTitle($item->getTitle());
245  $this->object->setNrOfTries((int) $item->getMaxattempts());
246  $this->object->setComment($item->getComment());
247  $this->object->setAuthor($item->getAuthor());
248  $this->object->setOwner($ilUser->getId());
249  $this->object->setObjId($questionpool_id);
250  $textgap_rating = $item->getMetadataEntry("textgaprating");
251  $this->object->setFixedTextLength($item->getMetadataEntry("fixedTextLength"));
252  $this->object->setIdenticalScoring($item->getMetadataEntry("identicalScoring"));
253  $this->object->setFeedbackMode(
254  strlen($item->getMetadataEntry("feedback_mode")) ?
255  $item->getMetadataEntry("feedback_mode") : ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION
256  );
257  $combinations = json_decode(base64_decode($item->getMetadataEntry("combinations")));
258  if (strlen($textgap_rating) == 0) {
259  $textgap_rating = "ci";
260  }
261  $this->object->setTextgapRating($textgap_rating);
262  $gaptext = [];
263  foreach ($gaps as $gapidx => $gap) {
264  $gapcontent = [];
265  $clozegap = new assClozeGap($gap["type"]);
266  foreach ($gap["answers"] as $index => $answer) {
267  $gapanswer = new assAnswerCloze($answer["answertext"], $answer["points"], $answer["answerorder"]);
268  $gapanswer->setGapSize((int) ($gap["gap_size"] ?? 0));
269  switch ($clozegap->getType()) {
270  case CLOZE_SELECT:
271  $clozegap->setShuffle($answer["shuffle"]);
272  break;
273  case CLOZE_NUMERIC:
274  $gapanswer->setLowerBound($gap["minnumber"]);
275  $gapanswer->setUpperBound($gap["maxnumber"]);
276  break;
277  }
278  $clozegap->setGapSize((int) ($gap["gap_size"] ?? 0));
279  $clozegap->addItem($gapanswer);
280  array_push($gapcontent, $answer["answertext"]);
281  }
282  $this->object->addGapAtIndex($clozegap, $gapidx);
283  $gaptext[$gap["ident"]] = "[gap]" . join(",", $gapcontent) . "[/gap]";
284  }
285 
286  $this->object->setQuestion($questiontext);
287  $clozetext = join("", $clozetext_array);
288 
289  foreach ($gaptext as $idx => $val) {
290  $clozetext = str_replace("<<" . $idx . ">>", $val, $clozetext);
291  }
292  $this->object->setClozeTextValue($clozetext);
293 
294  // additional content editing mode information
295  $this->object->setAdditionalContentEditingMode(
297  );
298  $this->object->saveToDb();
299 
300  if (is_array($combinations) && count($combinations) > 0) {
302  assClozeGapCombination::importGapCombinationToDb($this->object->getId(), $combinations);
303  $gap_combinations = new assClozeGapCombination();
304  $gap_combinations->loadFromDb($this->object->getId());
305  $this->object->setGapCombinations($gap_combinations);
306  $this->object->setGapCombinationsExists(true);
307  }
308 
309  // handle the import of media objects in XHTML code
310  foreach ($feedbacks as $ident => $material) {
311  $m = $this->QTIMaterialToString($material);
312  $feedbacks[$ident] = $m;
313  }
314  foreach ($feedbacksgeneric as $correctness => $material) {
315  $m = $this->QTIMaterialToString($material);
316  $feedbacksgeneric[$correctness] = $m;
317  }
318  if (is_array(ilSession::get("import_mob_xhtml"))) {
319  foreach (ilSession::get("import_mob_xhtml") as $mob) {
320  if ($tst_id > 0) {
321  $importfile = $this->getTstImportArchivDirectory() . '/' . $mob["uri"];
322  } else {
323  $importfile = $this->getQplImportArchivDirectory() . '/' . $mob["uri"];
324  }
325  global $DIC;
326  /* @var ILIAS\DI\Container $DIC */
327  $DIC['ilLog']->write(__METHOD__ . ': import mob from dir: ' . $importfile);
328 
329  $media_object = ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, false);
330  $questiontext = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $questiontext);
331  $clozetext = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $clozetext);
332  foreach ($feedbacks as $ident => $material) {
333  $feedbacks[$ident] = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $material);
334  }
335  foreach ($feedbacksgeneric as $correctness => $material) {
336  $feedbacksgeneric[$correctness] = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $material);
337  }
338  }
339  }
340  $this->object->setQuestion(ilRTE::_replaceMediaObjectImageSrc($questiontext, 1));
341  $this->object->setClozeTextValue(ilRTE::_replaceMediaObjectImageSrc($clozetext, 1));
342  foreach ($feedbacks as $ident => $material) {
343  $fbIdentifier = $this->buildFeedbackIdentifier($ident);
344  $this->object->feedbackOBJ->importSpecificAnswerFeedback(
345  $this->object->getId(),
346  $fbIdentifier->getQuestionIndex(),
347  $fbIdentifier->getAnswerIndex(),
349  );
350  }
351  foreach ($feedbacksgeneric as $correctness => $material) {
352  $this->object->feedbackOBJ->importGenericFeedback(
353  $this->object->getId(),
354  $correctness,
356  );
357  }
358  $this->object->saveToDb();
359  $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions);
360  if (isset($tst_id) && $tst_id !== $questionpool_id) {
361  $qpl_qid = $this->object->getId();
362  $tst_qid = $this->object->duplicate(true, "", "", -1, $tst_id);
363  $tst_object->questions[$question_counter++] = $tst_qid;
364  $import_mapping[$item->getIdent()] = ["pool" => $qpl_qid, "test" => $tst_qid];
365  return $import_mapping;
366  }
367 
368  if ($tst_id > 0) {
369  $tst_object->questions[$question_counter++] = $this->object->getId();
370  $import_mapping[$item->getIdent()] = ["pool" => 0, "test" => $this->object->getId()];
371  return $import_mapping;
372  }
373 
374  $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0];
375  return $import_mapping;
376  }
377 
383  {
384  $fbIdentifier = new ilAssSpecificFeedbackIdentifier();
385 
386  $ident = explode('_', $ident);
387 
388  if (count($ident) > 1) {
389  $fbIdentifier->setQuestionIndex($ident[0]);
390  $fbIdentifier->setAnswerIndex($ident[1]);
391  } else {
392  $fbIdentifier->setQuestionIndex($ident[0]);
393  $fbIdentifier->setAnswerIndex(0);
394  }
395 
396  return $fbIdentifier;
397  }
398 }
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
static get(string $a_var)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
addGeneralMetadata(ilQTIItem $item)
const IL_INST_ID
Definition: constants.php:40
QTIMaterialToString(ilQTIMaterial $a_material)
Reads an QTI material tag and creates a text or XHTML string.
static importGapCombinationToDb($question_id, $gap_combinations)
const CLOZE_TEXT
Cloze question constants.
processNonAbstractedImageReferences($text, $sourceNic)
fetchAdditionalContentEditingModeInformation($qtiItem)
fetches the "additional content editing mode" information from qti item and falls back to ADDITIONAL_...
$response
Definition: xapitoken.php:93
getQplImportArchivDirectory()
returns the full path to extracted qpl import archiv (qpl import dir + qpl archiv subdir) ...
Class for cloze question gaps.
global $DIC
Definition: feed.php:28
const CLOZE_SELECT
fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_counter, $import_mapping)
Creates a question from a QTI file.
static clearGapCombinationsFromDb($question_id)
importSuggestedSolutions(int $question_id, array $solution_from_import)
static _saveTempFileAsMediaObject(string $name, string $tmp_name, bool $upload=true)
Create new media object and update page in db and return new media object.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getTstImportArchivDirectory()
returns the full path to extracted tst import archiv (tst import dir + tst archiv subdir) ...
const CLOZE_NUMERIC
Class for question imports.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static clear(string $a_var)
const FB_MODE_GAP_QUESTION
constants for different feedback modes (per gap or per gap-answers/options)