ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
class.assClozeTest.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6 require_once './Modules/TestQuestionPool/classes/class.assClozeGapCombination.php';
7 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
8 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
9 require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
10 require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
11 
24 {
32  public $gaps;
33 
42 
43 
45 
53  public $start_tag;
54 
62  public $end_tag;
63 
75 
86 
93 
94  public $cloze_text;
95 
109  public function __construct(
110  $title = "",
111  $comment = "",
112  $author = "",
113  $owner = -1,
114  $question = ""
115  ) {
116  parent::__construct($title, $comment, $author, $owner, $question);
117  $this->start_tag = "[gap]";
118  $this->end_tag = "[/gap]";
119  $this->gaps = array();
120  $this->setQuestion($question); // @TODO: Should this be $question?? See setter for why this is not trivial.
121  $this->fixedTextLength = "";
122  $this->identical_scoring = 1;
123  $this->gap_combinations_exists = false;
124  $this->gap_combinations = array();
125  }
126 
132  public function isComplete()
133  {
134  if (strlen($this->getTitle())
135  && $this->getAuthor()
136  && $this->getClozeText()
137  && count($this->getGaps())
138  && $this->getMaximumPoints() > 0) {
139  return true;
140  }
141  return false;
142  }
143 
151  public function cleanQuestiontext($text)
152  {
153  $text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
154  $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
155  $text = str_replace("</gap>", "[/gap]", $text);
156  return $text;
157  }
158 
165  public function loadFromDb($question_id)
166  {
167  global $ilDB;
168  $result = $ilDB->queryF(
169  "SELECT qpl_questions.*, " . $this->getAdditionalTableName() . ".* FROM qpl_questions LEFT JOIN " . $this->getAdditionalTableName() . " ON " . $this->getAdditionalTableName() . ".question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s",
170  array("integer"),
171  array($question_id)
172  );
173  if ($result->numRows() == 1) {
174  $data = $ilDB->fetchAssoc($result);
175  $this->setId($question_id);
176  $this->setNrOfTries($data['nr_of_tries']);
177  $this->setObjId($data["obj_fi"]);
178  $this->setTitle($data["title"]);
179  $this->setComment($data["description"]);
180  $this->setOriginalId($data["original_id"]);
181  $this->setAuthor($data["author"]);
182  $this->setPoints($data["points"]);
183  $this->setOwner($data["owner"]);
184  $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
185  $this->setClozeText($data['cloze_text']);
186  $this->setFixedTextLength($data["fixed_textlen"]);
187  $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
188  // replacement of old syntax with new syntax
189  include_once("./Services/RTE/classes/class.ilRTE.php");
190  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
191  $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
192  $this->setTextgapRating($data["textgap_rating"]);
193  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
194 
195  try {
196  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
197  } catch (ilTestQuestionPoolException $e) {
198  }
199 
200  // open the cloze gaps with all answers
201  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
202  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
203  $result = $ilDB->queryF(
204  "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
205  array("integer"),
206  array($question_id)
207  );
208  if ($result->numRows() > 0) {
209  $this->gaps = array();
210  while ($data = $ilDB->fetchAssoc($result)) {
211  switch ($data["cloze_type"]) {
212  case CLOZE_TEXT:
213  if (!array_key_exists($data["gap_id"], $this->gaps)) {
214  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
215  }
216  $answer = new assAnswerCloze(
217  $data["answertext"],
218  $data["points"],
219  $data["aorder"]
220  );
221  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
222 
223  $this->gaps[$data["gap_id"]]->addItem($answer);
224  break;
225  case CLOZE_SELECT:
226  if (!array_key_exists($data["gap_id"], $this->gaps)) {
227  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
228  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
229  }
230  $answer = new assAnswerCloze(
231  $data["answertext"],
232  $data["points"],
233  $data["aorder"]
234  );
235  $this->gaps[$data["gap_id"]]->addItem($answer);
236  break;
237  case CLOZE_NUMERIC:
238  if (!array_key_exists($data["gap_id"], $this->gaps)) {
239  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
240  }
241  $answer = new assAnswerCloze(
242  $data["answertext"],
243  $data["points"],
244  $data["aorder"]
245  );
246  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
247  $answer->setLowerBound($data["lowerlimit"]);
248  $answer->setUpperBound($data["upperlimit"]);
249  $this->gaps[$data["gap_id"]]->addItem($answer);
250  break;
251  }
252  }
253  }
254  }
255  $assClozeGapCombinationObj = new assClozeGapCombination();
256  $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
257  if (count($check_for_gap_combinations) != 0) {
258  $this->setGapCombinationsExists(true);
259  $this->setGapCombinations($check_for_gap_combinations);
260  }
261  parent::loadFromDb($question_id);
262  }
263 
264  #region Save question to db
265 
275  public function saveToDb($original_id = "")
276  {
280 
281  parent::saveToDb($original_id);
282  }
283 
287  public function saveAnswerSpecificDataToDb()
288  {
289  global $ilDB;
290 
291  $ilDB->manipulateF(
292  "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
293  array( "integer" ),
294  array( $this->getId() )
295  );
296 
297  foreach ($this->gaps as $key => $gap) {
298  $this->saveClozeGapItemsToDb($gap, $key);
299  }
300  }
301 
308  {
309  global $ilDB;
310 
311  $ilDB->manipulateF(
312  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
313  array( "integer" ),
314  array( $this->getId() )
315  );
316 
317  $ilDB->manipulateF(
318  "INSERT INTO " . $this->getAdditionalTableName()
319  . " (question_fi, textgap_rating, identical_scoring, fixed_textlen, cloze_text) VALUES (%s, %s, %s, %s, %s)",
320  array(
321  "integer",
322  "text",
323  "text",
324  "integer",
325  "text"
326  ),
327  array(
328  $this->getId(),
329  $this->getTextgapRating(),
330  $this->getIdenticalScoring(),
331  $this->getFixedTextLength() ? $this->getFixedTextLength() : null,
333  )
334  );
335  }
336 
343  protected function saveClozeGapItemsToDb($gap, $key)
344  {
345  global $ilDB;
346  foreach ($gap->getItems($this->getShuffler()) as $item) {
347  $query = "";
348  $next_id = $ilDB->nextId('qpl_a_cloze');
349  switch ($gap->getType()) {
350  case CLOZE_TEXT:
351  $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
352  break;
353  case CLOZE_SELECT:
354  $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
355  break;
356  case CLOZE_NUMERIC:
357  $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
358  break;
359  }
360  }
361  }
362 
371  protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
372  {
373  global $ilDB;
374  $ilDB->manipulateF(
375  "INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, gap_size) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
376  array(
377  "integer",
378  "integer",
379  "integer",
380  "text",
381  "float",
382  "integer",
383  "text",
384  "integer"
385  ),
386  array(
387  $next_id,
388  $this->getId(),
389  $key,
390  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
391  $item->getPoints(),
392  $item->getOrder(),
393  $gap->getType(),
394  (int) $gap->getGapSize()
395  )
396  );
397  }
398 
407  protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
408  {
409  global $ilDB;
410  $ilDB->manipulateF(
411  "INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, shuffle) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
412  array(
413  "integer",
414  "integer",
415  "integer",
416  "text",
417  "float",
418  "integer",
419  "text",
420  "text"
421  ),
422  array(
423  $next_id,
424  $this->getId(),
425  $key,
426  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
427  $item->getPoints(),
428  $item->getOrder(),
429  $gap->getType(),
430  ($gap->getShuffle()) ? "1" : "0"
431  )
432  );
433  }
434 
443  protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
444  {
445  global $ilDB;
446 
447  include_once "./Services/Math/classes/class.EvalMath.php";
448  $eval = new EvalMath();
449  $eval->suppress_errors = true;
450  $ilDB->manipulateF(
451  "INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, lowerlimit, upperlimit, gap_size) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
452  array(
453  "integer",
454  "integer",
455  "integer",
456  "text",
457  "float",
458  "integer",
459  "text",
460  "text",
461  "text",
462  "integer"
463  ),
464  array(
465  $next_id,
466  $this->getId(),
467  $key,
468  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
469  $item->getPoints(),
470  $item->getOrder(),
471  $gap->getType(),
472  ($eval->e($item->getLowerBound() !== false) && strlen(
473  $item->getLowerBound()
474  ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
475  ($eval->e($item->getUpperBound() !== false) && strlen(
476  $item->getUpperBound()
477  ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
478  (int) $gap->getGapSize()
479  )
480  );
481  }
482 
483 
484 
485  #endregion Save question to db
486 
493  public function getGaps()
494  {
495  return $this->gaps;
496  }
497 
498 
505  public function flushGaps()
506  {
507  $this->gaps = array();
508  }
509 
519  public function setClozeText($cloze_text = "")
520  {
521  $this->gaps = array();
523  $this->cloze_text = $cloze_text;
525  }
526 
527  public function setClozeTextValue($cloze_text = "")
528  {
529  $this->cloze_text = $cloze_text;
530  }
531 
539  public function getClozeText()
540  {
541  return $this->cloze_text;
542  }
543 
551  public function getStartTag()
552  {
553  return $this->start_tag;
554  }
555 
563  public function setStartTag($start_tag = "[gap]")
564  {
565  $this->start_tag = $start_tag;
566  }
567 
575  public function getEndTag()
576  {
577  return $this->end_tag;
578  }
579 
587  public function setEndTag($end_tag = "[/gap]")
588  {
589  $this->end_tag = $end_tag;
590  }
591 
598  public function createGapsFromQuestiontext()
599  {
600  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
601  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
602  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
603  preg_match_all($search_pattern, $this->getClozeText(), $found);
604  $this->gaps = array();
605  if (count($found[0])) {
606  foreach ($found[1] as $gap_index => $answers) {
607  // create text gaps by default
608  $gap = new assClozeGap(CLOZE_TEXT);
609  $textparams = preg_split("/(?<!\\\\),/", $answers);
610  foreach ($textparams as $key => $value) {
611  $answer = new assAnswerCloze($value, 0, $key);
612  $gap->addItem($answer);
613  }
614  $this->gaps[$gap_index] = $gap;
615  }
616  }
617  }
618 
624  public function setGapType($gap_index, $gap_type)
625  {
626  if (array_key_exists($gap_index, $this->gaps)) {
627  $this->gaps[$gap_index]->setType($gap_type);
628  }
629  }
630 
640  public function setGapShuffle($gap_index = 0, $shuffle = 1)
641  {
642  if (array_key_exists($gap_index, $this->gaps)) {
643  $this->gaps[$gap_index]->setShuffle($shuffle);
644  }
645  }
646 
653  public function clearGapAnswers()
654  {
655  foreach ($this->gaps as $gap_index => $gap) {
656  $this->gaps[$gap_index]->clearItems();
657  }
658  }
659 
667  public function getGapCount()
668  {
669  if (is_array($this->gaps)) {
670  return count($this->gaps);
671  } else {
672  return 0;
673  }
674  }
675 
686  public function addGapAnswer($gap_index, $order, $answer)
687  {
688  if (array_key_exists($gap_index, $this->gaps)) {
689  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC) {
690  // only allow notation with "." for real numbers
691  $answer = str_replace(",", ".", $answer);
692  }
693  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
694  }
695  }
696 
705  public function getGap($gap_index = 0)
706  {
707  if (array_key_exists($gap_index, $this->gaps)) {
708  return $this->gaps[$gap_index];
709  } else {
710  return null;
711  }
712  }
713 
714  public function setGapSize($gap_index, $order, $size)
715  {
716  if (array_key_exists($gap_index, $this->gaps)) {
717  $this->gaps[$gap_index]->setGapSize($size);
718  }
719  }
720 
731  public function setGapAnswerPoints($gap_index, $order, $points)
732  {
733  if (array_key_exists($gap_index, $this->gaps)) {
734  $this->gaps[$gap_index]->setItemPoints($order, $points);
735  }
736  }
737 
746  public function addGapText($gap_index)
747  {
748  if (array_key_exists($gap_index, $this->gaps)) {
749  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
750  $answer = new assAnswerCloze(
751  "",
752  0,
753  $this->gaps[$gap_index]->getItemCount()
754  );
755  $this->gaps[$gap_index]->addItem($answer);
756  }
757  }
758 
767  public function addGapAtIndex($gap, $index)
768  {
769  $this->gaps[$index] = $gap;
770  }
771 
782  public function setGapAnswerLowerBound($gap_index, $order, $bound)
783  {
784  if (array_key_exists($gap_index, $this->gaps)) {
785  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
786  }
787  }
788 
799  public function setGapAnswerUpperBound($gap_index, $order, $bound)
800  {
801  if (array_key_exists($gap_index, $this->gaps)) {
802  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
803  }
804  }
805 
812  public function getMaximumPoints()
813  {
814  $assClozeGapCombinationObj = new assClozeGapCombination();
815  $points = 0;
816  $gaps_used_in_combination = array();
817  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
818  $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
819  $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
820  }
821  foreach ($this->gaps as $gap_index => $gap) {
822  if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
823  if ($gap->getType() == CLOZE_TEXT) {
824  $gap_max_points = 0;
825  foreach ($gap->getItems($this->getShuffler()) as $item) {
826  if ($item->getPoints() > $gap_max_points) {
827  $gap_max_points = $item->getPoints();
828  }
829  }
830  $points += $gap_max_points;
831  } elseif ($gap->getType() == CLOZE_SELECT) {
832  $srpoints = 0;
833  foreach ($gap->getItems($this->getShuffler()) as $item) {
834  if ($item->getPoints() > $srpoints) {
835  $srpoints = $item->getPoints();
836  }
837  }
838  $points += $srpoints;
839  } elseif ($gap->getType() == CLOZE_NUMERIC) {
840  $numpoints = 0;
841  foreach ($gap->getItems($this->getShuffler()) as $item) {
842  if ($item->getPoints() > $numpoints) {
843  $numpoints = $item->getPoints();
844  }
845  }
846  $points += $numpoints;
847  }
848  }
849  }
850 
851  return $points;
852  }
853 
859  public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
860  {
861  if ($this->id <= 0) {
862  // The question has not been saved. It cannot be duplicated
863  return;
864  }
865  // duplicate the question in database
866  $this_id = $this->getId();
867  $thisObjId = $this->getObjId();
868 
869  $clone = $this;
870  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
872  $clone->id = -1;
873 
874  if ((int) $testObjId > 0) {
875  $clone->setObjId($testObjId);
876  }
877 
878  if ($title) {
879  $clone->setTitle($title);
880  }
881  if ($author) {
882  $clone->setAuthor($author);
883  }
884  if ($owner) {
885  $clone->setOwner($owner);
886  }
887  if ($for_test) {
888  $clone->saveToDb($original_id);
889  } else {
890  $clone->saveToDb();
891  }
892  if ($this->gap_combinations_exists) {
893  $this->copyGapCombination($this_id, $clone->getId());
894  }
895  if ($for_test) {
896  $clone->saveToDb($original_id);
897  } else {
898  $clone->saveToDb();
899  }
900  // copy question page content
901  $clone->copyPageOfQuestion($this_id);
902  // copy XHTML media objects
903  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
904 
905  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
906 
907  return $clone->getId();
908  }
909 
915  public function copyObject($target_questionpool_id, $title = "")
916  {
917  if ($this->getId() <= 0) {
918  // The question has not been saved. It cannot be duplicated
919  return;
920  }
921 
922  $thisId = $this->getId();
923  $thisObjId = $this->getObjId();
924 
925  $clone = $this;
926  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
928  $clone->id = -1;
929  $clone->setObjId($target_questionpool_id);
930  if ($title) {
931  $clone->setTitle($title);
932  }
933 
934  $clone->saveToDb();
935 
936  if ($this->gap_combinations_exists) {
937  $this->copyGapCombination($original_id, $clone->getId());
938  $clone->saveToDb();
939  }
940 
941  // copy question page content
942  $clone->copyPageOfQuestion($original_id);
943  // copy XHTML media objects
944  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
945 
946  $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
947 
948  return $clone->getId();
949  }
950 
951  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
952  {
953  if ($this->id <= 0) {
954  // The question has not been saved. It cannot be duplicated
955  return;
956  }
957 
958  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
959 
960  $sourceQuestionId = $this->id;
961  $sourceParentId = $this->getObjId();
962 
963  // duplicate the question in database
964  $clone = $this;
965  $clone->id = -1;
966 
967  $clone->setObjId($targetParentId);
968 
969  if ($targetQuestionTitle) {
970  $clone->setTitle($targetQuestionTitle);
971  }
972 
973  $clone->saveToDb();
974 
975  if ($this->gap_combinations_exists) {
976  $this->copyGapCombination($sourceQuestionId, $clone->getId());
977  $clone->saveToDb();
978  }
979  // copy question page content
980  $clone->copyPageOfQuestion($sourceQuestionId);
981  // copy XHTML media objects
982  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
983 
984  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
985 
986  return $clone->id;
987  }
988 
989  public function copyGapCombination($orgID, $newID)
990  {
991  $assClozeGapCombinationObj = new assClozeGapCombination();
992  $array = $assClozeGapCombinationObj->loadFromDb($orgID);
993  $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array);
994  }
995 
1001  public function updateClozeTextFromGaps()
1002  {
1003  $output = $this->getClozeText();
1004  foreach ($this->getGaps() as $gap_index => $gap) {
1005  $answers = array();
1006  foreach ($gap->getItemsRaw() as $item) {
1007  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1008  }
1009  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]", $output, 1);
1010  }
1011  $output = str_replace("_gap]", "gap]", $output);
1012  $this->cloze_text = $output;
1013  }
1014 
1024  public function deleteAnswerText($gap_index, $answer_index)
1025  {
1026  if (array_key_exists($gap_index, $this->gaps)) {
1027  if ($this->gaps[$gap_index]->getItemCount() == 1) {
1028  // this is the last answer text => remove the gap
1029  $this->deleteGap($gap_index);
1030  } else {
1031  // remove the answer text
1032  $this->gaps[$gap_index]->deleteItem($answer_index);
1033  $this->updateClozeTextFromGaps();
1034  }
1035  }
1036  }
1037 
1046  public function deleteGap($gap_index)
1047  {
1048  if (array_key_exists($gap_index, $this->gaps)) {
1049  $output = $this->getClozeText();
1050  foreach ($this->getGaps() as $replace_gap_index => $gap) {
1051  $answers = array();
1052  foreach ($gap->getItemsRaw() as $item) {
1053  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1054  }
1055  if ($replace_gap_index == $gap_index) {
1056  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1);
1057  } else {
1058  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1);
1059  }
1060  }
1061  $output = str_replace("_gap]", "gap]", $output);
1062  $this->cloze_text = $output;
1063  unset($this->gaps[$gap_index]);
1064  $this->gaps = array_values($this->gaps);
1065  }
1066  }
1067 
1077  public function getTextgapPoints($a_original, $a_entered, $max_points)
1078  {
1079  include_once "./Services/Utilities/classes/class.ilStr.php";
1080  $result = 0;
1081  $gaprating = $this->getTextgapRating();
1082  switch ($gaprating) {
1084  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) {
1085  $result = $max_points;
1086  }
1087  break;
1089  if (strcmp($a_original, $a_entered) == 0) {
1090  $result = $max_points;
1091  }
1092  break;
1094  if (levenshtein($a_original, $a_entered) <= 1) {
1095  $result = $max_points;
1096  }
1097  break;
1099  if (levenshtein($a_original, $a_entered) <= 2) {
1100  $result = $max_points;
1101  }
1102  break;
1104  if (levenshtein($a_original, $a_entered) <= 3) {
1105  $result = $max_points;
1106  }
1107  break;
1109  if (levenshtein($a_original, $a_entered) <= 4) {
1110  $result = $max_points;
1111  }
1112  break;
1114  if (levenshtein($a_original, $a_entered) <= 5) {
1115  $result = $max_points;
1116  }
1117  break;
1118  }
1119  return $result;
1120  }
1121 
1131  public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1132  {
1133  // fau: fixGapFormula - check entered value by evalMath
1134  // if( ! $this->checkForValidFormula($a_entered) )
1135  // {
1136  // return 0;
1137  // }
1138 
1139  include_once "./Services/Math/classes/class.EvalMath.php";
1140  $eval = new EvalMath();
1141  $eval->suppress_errors = true;
1142  $result = 0;
1143 
1144  if ($eval->e($a_entered) === false) {
1145  return 0;
1146  } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) {
1147  // fau.
1148  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1149  $result = $max_points;
1150  }
1151  } elseif ($eval->e($lowerBound) !== false) {
1152  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
1153  $result = $max_points;
1154  }
1155  } elseif ($eval->e($upperBound) !== false) {
1156  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1157  $result = $max_points;
1158  }
1159  } else {
1160  if ($eval->e($a_entered) == $eval->e($a_original)) {
1161  $result = $max_points;
1162  }
1163  }
1164  return $result;
1165  }
1166 
1171  public function checkForValidFormula($value)
1172  {
1173  return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1174  }
1185  public function calculateReachedPoints($active_id, $pass = null, $authorized = true, $returndetails = false)
1186  {
1187  global $ilDB;
1188 
1189  if (is_null($pass)) {
1190  $pass = $this->getSolutionMaxPass($active_id);
1191  }
1192 
1193  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1194  $user_result = array();
1195  while ($data = $ilDB->fetchAssoc($result)) {
1196  if (strcmp($data["value2"], "") != 0) {
1197  $user_result[$data["value1"]] = array(
1198  "gap_id" => $data["value1"],
1199  "value" => $data["value2"]
1200  );
1201  }
1202  }
1203 
1204  ksort($user_result); // this is required when identical scoring for same solutions is disabled
1205 
1206  if ($returndetails) {
1207  $detailed = array();
1208  $this->calculateReachedPointsForSolution($user_result, $detailed);
1209  return $detailed;
1210  }
1211 
1212  return $this->calculateReachedPointsForSolution($user_result);
1213  }
1214 
1215  protected function isValidNumericSubmitValue($submittedValue)
1216  {
1217  if (is_numeric($submittedValue)) {
1218  return true;
1219  }
1220 
1221  if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1222  return true;
1223  }
1224 
1225  return false;
1226  }
1227 
1228  public function validateSolutionSubmit()
1229  {
1230  foreach ($this->getSolutionSubmit() as $gapIndex => $value) {
1231  $gap = $this->getGap($gapIndex);
1232 
1233  if ($gap->getType() != CLOZE_NUMERIC) {
1234  continue;
1235  }
1236 
1237  if (strlen($value) && !$this->isValidNumericSubmitValue($value)) {
1238  ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1239  return false;
1240  }
1241  }
1242 
1243  return true;
1244  }
1245 
1246  public function fetchSolutionSubmit($submit)
1247  {
1248  $solutionSubmit = array();
1249 
1250  foreach ($submit as $key => $value) {
1251  if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1252  $value = ilUtil::stripSlashes($value, false);
1253  if (strlen($value)) {
1254  $gap = $this->getGap($matches[1]);
1255  if (is_object($gap)) {
1256  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1257  if ($gap->getType() == CLOZE_NUMERIC) {
1258  $value = str_replace(",", ".", $value);
1259  }
1260  $solutionSubmit[trim($matches[1])] = $value;
1261  }
1262  }
1263  }
1264  }
1265  }
1266 
1267  return $solutionSubmit;
1268  }
1269 
1270  public function getSolutionSubmit()
1271  {
1272  return $this->fetchSolutionSubmit($_POST);
1273  }
1274 
1283  public function saveWorkingData($active_id, $pass = null, $authorized = true)
1284  {
1285  global $ilDB;
1286  global $ilUser;
1287  if (is_null($pass)) {
1288  include_once "./Modules/Test/classes/class.ilObjTest.php";
1289  $pass = ilObjTest::_getPass($active_id);
1290  }
1291 
1292  $entered_values = 0;
1293 
1294  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
1295  $this->removeCurrentSolution($active_id, $pass, $authorized);
1296 
1297  foreach ($this->getSolutionSubmit() as $val1 => $val2) {
1298  $value = trim(ilUtil::stripSlashes($val2, false));
1299  if (strlen($value)) {
1300  $gap = $this->getGap(trim(ilUtil::stripSlashes($val1)));
1301  if (is_object($gap)) {
1302  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1303  $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized);
1304  $entered_values++;
1305  }
1306  }
1307  }
1308  }
1309  });
1310 
1311  if ($entered_values) {
1312  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1314  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1315  }
1316  } else {
1317  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1319  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1320  }
1321  }
1322 
1323  return true;
1324  }
1325 
1329  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
1330  {
1331  // nothing to rework!
1332  }
1333 
1340  public function getQuestionType()
1341  {
1342  return "assClozeTest";
1343  }
1344 
1352  public function getTextgapRating()
1353  {
1354  return $this->textgap_rating;
1355  }
1356 
1364  public function setTextgapRating($a_textgap_rating)
1365  {
1366  switch ($a_textgap_rating) {
1374  $this->textgap_rating = $a_textgap_rating;
1375  break;
1376  default:
1377  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1378  break;
1379  }
1380  }
1381 
1389  public function getIdenticalScoring()
1390  {
1391  return ($this->identical_scoring) ? 1 : 0;
1392  }
1393 
1401  public function setIdenticalScoring($a_identical_scoring)
1402  {
1403  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1404  }
1405 
1412  public function getAdditionalTableName()
1413  {
1414  return "qpl_qst_cloze";
1415  }
1416 
1423  public function getAnswerTableName()
1424  {
1425  return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1426  }
1427 
1434  public function setFixedTextLength($a_text_len)
1435  {
1436  $this->fixedTextLength = $a_text_len;
1437  }
1438 
1445  public function getFixedTextLength()
1446  {
1447  return $this->fixedTextLength;
1448  }
1449 
1458  public function getMaximumGapPoints($gap_index)
1459  {
1460  $points = 0;
1461  $gap_max_points = 0;
1462  if (array_key_exists($gap_index, $this->gaps)) {
1463  $gap =&$this->gaps[$gap_index];
1464  foreach ($gap->getItems($this->getShuffler()) as $answer) {
1465  if ($answer->getPoints() > $gap_max_points) {
1466  $gap_max_points = $answer->getPoints();
1467  }
1468  }
1469  $points += $gap_max_points;
1470  }
1471  return $points;
1472  }
1473 
1478  public function getRTETextWithMediaObjects()
1479  {
1480  return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1481  }
1482  public function getGapCombinationsExists()
1483  {
1485  }
1486 
1487  public function getGapCombinations()
1488  {
1489  return $this->gap_combinations;
1490  }
1491 
1492  public function setGapCombinationsExists($value)
1493  {
1494  $this->gap_combinations_exists = $value;
1495  }
1496 
1497  public function setGapCombinations($value)
1498  {
1499  $this->gap_combinations = $value;
1500  }
1501 
1505  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1506  {
1507  parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1508 
1509  $solution = $this->getSolutionValues($active_id, $pass);
1510  $i = 1;
1511  foreach ($this->getGaps() as $gap_index => $gap) {
1512  $worksheet->setCell($startrow + $i, 0, $this->lng->txt("gap") . " $i");
1513  $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1514  $checked = false;
1515  foreach ($solution as $solutionvalue) {
1516  if ($gap_index == $solutionvalue["value1"]) {
1517  $string_escaping_org_value = $worksheet->getStringEscaping();
1518  try {
1519  $worksheet->setStringEscaping(false);
1520 
1521  switch ($gap->getType()) {
1522  case CLOZE_SELECT:
1523  $worksheet->setCell($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1524  break;
1525  case CLOZE_NUMERIC:
1526  case CLOZE_TEXT:
1527  $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1528  break;
1529  }
1530  } finally {
1531  $worksheet->setStringEscaping($string_escaping_org_value);
1532  }
1533  }
1534  }
1535  $i++;
1536  }
1537 
1538  return $startrow + $i + 1;
1539  }
1540 
1545  {
1546  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1547  //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1548  $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1549  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1550  }
1551 
1555  public function toJSON()
1556  {
1557  include_once("./Services/RTE/classes/class.ilRTE.php");
1558  $result = array();
1559  $result['id'] = (int) $this->getId();
1560  $result['type'] = (string) $this->getQuestionType();
1561  $result['title'] = (string) $this->getTitle();
1562  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1563  $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1564  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1565  $result['shuffle'] = (bool) $this->getShuffle();
1566  $result['feedback'] = array(
1567  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1568  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1569  );
1570 
1571  $gaps = array();
1572  foreach ($this->getGaps() as $key => $gap) {
1573  $items = array();
1574  foreach ($gap->getItems($this->getShuffler()) as $item) {
1575  $jitem = array();
1576  $jitem['points'] = $item->getPoints();
1577  $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1578  $jitem['order'] = $item->getOrder();
1579  if ($gap->getType() == CLOZE_NUMERIC) {
1580  $jitem['lowerbound'] = $item->getLowerBound();
1581  $jitem['upperbound'] = $item->getUpperBound();
1582  } else {
1583  $jitem['value'] = trim($jitem['value']);
1584  }
1585  array_push($items, $jitem);
1586  }
1587 
1588  if ($gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC)) {
1589  $jgap['size'] = $gap->getGapSize();
1590  }
1591 
1592  $jgap['shuffle'] = $gap->getShuffle();
1593  $jgap['type'] = $gap->getType();
1594  $jgap['item'] = $items;
1595 
1596  array_push($gaps, $jgap);
1597  }
1598  $result['gaps'] = $gaps;
1599  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1600  $result['mobs'] = $mobs;
1601  return json_encode($result);
1602  }
1603 
1612  public function getOperators($expression)
1613  {
1614  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1616  }
1617 
1622  public function getExpressionTypes()
1623  {
1624  return array(
1630  );
1631  }
1632 
1641  public function getUserQuestionResult($active_id, $pass)
1642  {
1644  global $ilDB;
1645  $result = new ilUserQuestionResult($this, $active_id, $pass);
1646 
1647  $maxStep = $this->lookupMaxStep($active_id, $pass);
1648 
1649  if ($maxStep !== null) {
1650  $data = $ilDB->queryF(
1651  "
1652  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1653  FROM tst_solutions sol
1654  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1655  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1656  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1657  ",
1658  array("integer", "integer", "integer","integer"),
1659  array($active_id, $pass, $this->getId(), $maxStep)
1660  );
1661  } else {
1662  $data = $ilDB->queryF(
1663  "
1664  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1665  FROM tst_solutions sol
1666  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1667  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1668  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1669  ",
1670  array("integer", "integer", "integer"),
1671  array($active_id, $pass, $this->getId())
1672  );
1673  }
1674 
1675  while ($row = $ilDB->fetchAssoc($data)) {
1676  if ($row["cloze_type"] == 1) {
1677  $row["value2"]++;
1678  }
1679  $result->addKeyValue($row["val"], $row["value2"]);
1680  }
1681 
1682  $points = $this->calculateReachedPoints($active_id, $pass);
1683  $max_points = $this->getMaximumPoints();
1684 
1685  $result->setReachedPercentage(($points/$max_points) * 100);
1686 
1687  return $result;
1688  }
1689 
1698  public function getAvailableAnswerOptions($index = null)
1699  {
1700  if ($index !== null) {
1701  return $this->getGap($index);
1702  } else {
1703  return $this->getGaps();
1704  }
1705  }
1706 
1707  public function calculateCombinationResult($user_result)
1708  {
1709  $points = 0;
1710 
1711  $assClozeGapCombinationObj = new assClozeGapCombination();
1712 
1713  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1714  $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1715  $gap_answers = array();
1716  $gap_used_in_combination = array();
1717  foreach ($user_result as $user_result_build_list) {
1718  if (is_array($user_result_build_list)) {
1719  $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1720  }
1721  }
1722 
1723  foreach ($combinations_for_question as $combination) {
1724  foreach ($combination as $row_key => $row_answers) {
1725  $combination_fulfilled = true;
1726  $points_for_combination = $row_answers['points'];
1727  foreach ($row_answers as $gap_key => $combination_gap_answer) {
1728  if ($gap_key !== 'points') {
1729  $gap_used_in_combination[$gap_key]= $gap_key;
1730  }
1731  if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1732  switch ($combination_gap_answer['type']) {
1733  case CLOZE_TEXT:
1734  $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1735  if ($is_text_gap_correct != 1) {
1736  $combination_fulfilled = false;
1737  }
1738  break;
1739  case CLOZE_SELECT:
1740  $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1741  $answertext = $answer->getAnswertext();
1742  if ($answertext != $combination_gap_answer['answer']) {
1743  $combination_fulfilled = false;
1744  }
1745  break;
1746  case CLOZE_NUMERIC:
1747  $answer = $this->gaps[$gap_key]->getItem(0);
1748  if ($combination_gap_answer['answer'] != 'out_of_bound') {
1749  $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1750  if ($is_numeric_gap_correct != 1) {
1751  $combination_fulfilled = false;
1752  }
1753  } else {
1754  $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1755  if ($wrong_is_the_new_right == 1) {
1756  $combination_fulfilled = false;
1757  }
1758  }
1759  break;
1760  }
1761  } else {
1762  if ($gap_key !== 'points') {
1763  $combination_fulfilled = false;
1764  }
1765  }
1766  }
1767  if ($combination_fulfilled) {
1768  $points += $points_for_combination;
1769  }
1770  }
1771  }
1772  }
1773  return array($points, $gap_used_in_combination);
1774  }
1780  protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1781  {
1782  if ($detailed === null) {
1783  $detailed = array();
1784  }
1785 
1786  $assClozeGapCombinationObj = new assClozeGapCombination();
1787  $combinations[1] = array();
1788  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1789  $combinations = $this->calculateCombinationResult($user_result);
1790  $points = $combinations[0];
1791  }
1792  $counter = 0;
1793  $solution_values_text = array(); // for identical scoring checks
1794  $solution_values_select = array(); // for identical scoring checks
1795  $solution_values_numeric = array(); // for identical scoring checks
1796  foreach ($user_result as $gap_id => $value) {
1797  if (is_string($value)) {
1798  $value = array("value" => $value);
1799  }
1800 
1801  if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1802  switch ($this->gaps[$gap_id]->getType()) {
1803  case CLOZE_TEXT:
1804  $gappoints = 0;
1805  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1806  $answer = $this->gaps[$gap_id]->getItem($order);
1807  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1808  if ($gotpoints > $gappoints) {
1809  $gappoints = $gotpoints;
1810  }
1811  }
1812  if (!$this->getIdenticalScoring()) {
1813  // check if the same solution text was already entered
1814  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0)) {
1815  $gappoints = 0;
1816  }
1817  }
1818  $points += $gappoints;
1819  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1820  array_push($solution_values_text, $value["value"]);
1821  break;
1822  case CLOZE_NUMERIC:
1823  $gappoints = 0;
1824  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1825  $answer = $this->gaps[$gap_id]->getItem($order);
1826  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1827  if ($gotpoints > $gappoints) {
1828  $gappoints = $gotpoints;
1829  }
1830  }
1831  if (!$this->getIdenticalScoring()) {
1832  // check if the same solution value was already entered
1833  include_once "./Services/Math/classes/class.EvalMath.php";
1834  $eval = new EvalMath();
1835  $eval->suppress_errors = true;
1836  $found_value = false;
1837  foreach ($solution_values_numeric as $solval) {
1838  if ($eval->e($solval) == $eval->e($value["value"])) {
1839  $found_value = true;
1840  }
1841  }
1842  if ($found_value && ($gappoints > 0)) {
1843  $gappoints = 0;
1844  }
1845  }
1846  $points += $gappoints;
1847  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1848  array_push($solution_values_numeric, $value["value"]);
1849  break;
1850  case CLOZE_SELECT:
1851  if ($value["value"] >= 0) {
1852  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1853  $answer = $this->gaps[$gap_id]->getItem($order);
1854  if ($value["value"] == $answer->getOrder()) {
1855  $answerpoints = $answer->getPoints();
1856  if (!$this->getIdenticalScoring()) {
1857  // check if the same solution value was already entered
1858  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0)) {
1859  $answerpoints = 0;
1860  }
1861  }
1862  $points += $answerpoints;
1863  $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? true : false, "positive" => ($answerpoints > 0) ? true : false);
1864  array_push($solution_values_select, $answer->getAnswertext());
1865  }
1866  }
1867  }
1868  break;
1869  }
1870  }
1871  }
1872 
1873  return $points;
1874  }
1875 
1877  {
1878  $userSolution = array();
1879 
1880  foreach ($previewSession->getParticipantsSolution() as $key => $val) {
1881  // fau: fixEmptyGapPreview - add the gap_id as array index
1882  $userSolution[$key] = array('gap_id' => $key, 'value' => $val);
1883  // fau.
1884  }
1885 
1886  $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
1887  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
1888 
1889  return $this->ensureNonNegativePoints($reachedPoints);
1890  }
1891 }
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
getId()
Gets the id of the assQuestion object.
Add rich text string
fetchSolutionSubmit($submit)
toJSON()
Returns a JSON representation of the question.
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
getGapCount()
Returns the number of gaps.
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
const TEXTGAP_RATING_LEVENSHTEIN5
Class for cloze question numeric answers.
$worksheet
$size
Definition: RandomTest.php:84
getMaximumGapPoints($gap_index)
Returns the maximum points for a gap.
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getQuestionType()
Returns the question type of the question.
Class iQuestionCondition.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
setEndTag($end_tag="[/gap]")
Sets the end tag of a cloze gap.
clearGapAnswers()
Removes all answers from the gaps.
Class for cloze tests.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
saveAnswerSpecificDataToDb()
Save all gaps to the database.
getAnswerTableName()
Returns the name of the answer table in the database.
$result
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
const TEXTGAP_RATING_LEVENSHTEIN2
const TEXTGAP_RATING_LEVENSHTEIN1
Abstract basic class which is to be extended by the concrete assessment question type classes...
const CLOZE_TEXT
Cloze question constants.
setGapSize($gap_index, $order, $size)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
calculateCombinationResult($user_result)
getOperators($expression)
Get all available operations for a specific question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
ensureNonNegativePoints($points)
getSolutionValues($active_id, $pass=null, $authorized=true)
Loads solutions of a given user from the database an returns it.
setId($id=-1)
Sets the id of the assQuestion object.
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second. ...
copyGapCombination($orgID, $newID)
getGap($gap_index=0)
Returns the gap at a given index.
setGapAnswerPoints($gap_index, $order, $points)
Sets the points of a gap with a given index and an answer with a given order.
getTextgapRating()
Returns the rating option for text gaps.
Class for cloze question gaps.
saveToDb($original_id="")
Saves a assClozeTest object to a database.
static strToLower($a_string)
Definition: class.ilStr.php:87
$index
Definition: metadata.php:60
setClozeTextValue($cloze_text="")
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
checkForValidFormula($value)
setNrOfTries($a_nr_of_tries)
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
{}
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
createGapsFromQuestiontext()
Create gap entries by parsing the question text.
getObjId()
Get the object id of the container object.
getShuffle()
Gets the shuffle flag.
getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
Returns the points for a text gap and compares the given solution with the entered solution using the...
getStartTag()
Returns the start tag of a cloze gap.
$counter
getTextgapPoints($a_original, $a_entered, $max_points)
Returns the points for a text gap and compares the given solution with the entered solution using the...
setGapType($gap_index, $gap_type)
Set the type of a gap with a given index.
setGapCombinationsExists($value)
static _getLogLanguage()
retrieve the log language for assessment logging
getGaps()
Returns the array of gaps.
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
setAuthor($author="")
Sets the authors name of the assQuestion object.
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates an assClozeTest.
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
const CLOZE_SELECT
getAuthor()
Gets the authors name of the assQuestion object.
const TEXTGAP_RATING_LEVENSHTEIN3
$mobs
setGapAnswerLowerBound($gap_index, $order, $bound)
Sets the lower bound of a gap with a given index and an answer with a given order.
Class ilUserQuestionResult.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
setFixedTextLength($a_text_len)
Sets a fixed text length for all text fields in the cloze question.
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
isValidNumericSubmitValue($submittedValue)
copyObject($target_questionpool_id, $title="")
Copies an assClozeTest object.
const TEXTGAP_RATING_CASESENSITIVE
$text
Definition: errorreport.php:18
Interface ilObjAnswerScoringAdjustable.
reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
{}
getQuestion()
Gets the question string of the question object.
$ilUser
Definition: imgupload.php:18
calculateReachedPoints($active_id, $pass=null, $authorized=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
$query
addGapAtIndex($gap, $index)
Adds a ClozeGap object at a given index.
static stripSlashes($a_str, $a_strip_html=true, $a_allow="")
strip slashes if magic qoutes is enabled
setGapAnswerUpperBound($gap_index, $order, $bound)
Sets the upper bound of a gap with a given index and an answer with a given order.
saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
addGapAnswer($gap_index, $order, $answer)
Sets the answer text of a gap with a given index.
setGapShuffle($gap_index=0, $shuffle=1)
Sets the shuffle state of a gap with a given index.
Create styles array
The data for the language used.
setClozeText($cloze_text="")
Evaluates the text gap solutions from the cloze text.
cleanQuestiontext($text)
Cleans cloze question text to remove attributes or tags from older ILIAS versions.
getExpressionTypes()
Get all available expression types for a specific question.
flushGaps()
Deletes all gaps without changing the cloze text.
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
setPoints($a_points)
Sets the maximum available points for the question.
saveQuestionDataToDb($original_id="")
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
deleteGap($gap_index)
Deletes a gap with a given index.
updateClozeTextFromGaps()
Updates the gap parameters in the cloze text from the form input.
setTextgapRating($a_textgap_rating)
Sets the rating option for text gaps.
setQuestion($question="")
Sets the question string of the question object.
Interface ilObjQuestionScoringAdjustable.
getEndTag()
Returns the end tag of a cloze gap.
removeCurrentSolution($active_id, $pass, $authorized=true)
setStartTag($start_tag="[gap]")
Sets the start tag of a cloze gap.
addGapText($gap_index)
Adds a new answer text value to a text gap with a given index.
prepareTextareaOutput($txt_output, $prepare_for_latex_output=false, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
deleteAnswerText($gap_index, $answer_index)
Deletes the answer text of a gap with a given index and an answer with a given order.
getFixedTextLength()
Gets the fixed text length for all text fields in the cloze question.
const TEXTGAP_RATING_LEVENSHTEIN4
global $ilDB
setOriginalId($original_id)
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass...
$i
Definition: disco.tpl.php:19
saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
getClozeText()
Returns the cloze text.
getTitle()
Gets the title string of the assQuestion object.
__construct( $title="", $comment="", $author="", $owner=-1, $question="")
assClozeTest constructor
loadFromDb($question_id)
Loads a assClozeTest object from a database.
const CLOZE_NUMERIC
isComplete()
Returns TRUE, if a cloze test is complete for use.
getIdenticalScoring()
Returns the identical scoring status of the question.
setTitle($title="")
Sets the title string of the assQuestion object.
setObjId($obj_id=0)
Set the object id of the container object.
setIdenticalScoring($a_identical_scoring)
Sets the identical scoring option for cloze questions.
$key
Definition: croninfo.php:18
setComment($comment="")
Sets the comment string of the assQuestion object.
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
$_POST["username"]
saveClozeGapItemsToDb($gap, $key)
Save all items belonging to one cloze gap to the db.
calculateReachedPointsForSolution($user_result, &$detailed=null)
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
const TEXTGAP_RATING_CASEINSENSITIVE