ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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 require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php';
12 
25 {
33  public $gaps;
34 
43 
44 
46 
54  public $start_tag;
55 
63  public $end_tag;
64 
76 
87 
94 
95  public $cloze_text;
96 
100  public $feedbackOBJ;
101 
103 
117  public function __construct(
118  $title = "",
119  $comment = "",
120  $author = "",
121  $owner = -1,
122  $question = ""
123  ) {
124  parent::__construct($title, $comment, $author, $owner, $question);
125  $this->start_tag = "[gap]";
126  $this->end_tag = "[/gap]";
127  $this->gaps = array();
128  $this->setQuestion($question); // @TODO: Should this be $question?? See setter for why this is not trivial.
129  $this->fixedTextLength = "";
130  $this->identical_scoring = 1;
131  $this->gap_combinations_exists = false;
132  $this->gap_combinations = array();
133  }
134 
140  public function isComplete()
141  {
142  if (strlen($this->getTitle())
143  && $this->getAuthor()
144  && $this->getClozeText()
145  && count($this->getGaps())
146  && $this->getMaximumPoints() > 0) {
147  return true;
148  }
149  return false;
150  }
151 
159  public function cleanQuestiontext($text)
160  {
161  // fau: fixGapReplace - mask dollars for replacement
162  $text = str_replace('$','GAPMASKEDDOLLAR', $text);$text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
163  $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
164  $text = str_replace("</gap>", "[/gap]", $text);$text = str_replace('GAPMASKEDDOLLAR', '$', $text);
165 // fau.
166  return $text;
167  }
168 
169  // fau: fixGapReplace - add function replaceFirstGap()
176  public function replaceFirstGap($gaptext, $content)
177  {
178  $content = str_replace('$','GAPMASKEDDOLLAR', $content);
179  $output = preg_replace("/\[gap\].*?\[\/gap\]/", $content, $gaptext, 1);
180  $output = str_replace('GAPMASKEDDOLLAR', '$', $output);
181 
182  return $output;
183  }
184 // fau.
191  public function loadFromDb($question_id)
192  {
193  global $DIC;
194  $ilDB = $DIC['ilDB'];
195  $result = $ilDB->queryF(
196  "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",
197  array("integer"),
198  array($question_id)
199  );
200  if ($result->numRows() == 1) {
201  $data = $ilDB->fetchAssoc($result);
202  $this->setId($question_id);
203  $this->setNrOfTries($data['nr_of_tries']);
204  $this->setObjId($data["obj_fi"]);
205  $this->setTitle($data["title"]);
206  $this->setComment($data["description"]);
207  $this->setOriginalId($data["original_id"]);
208  $this->setAuthor($data["author"]);
209  $this->setPoints($data["points"]);
210  $this->setOwner($data["owner"]);
211  $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
212  $this->setClozeText($data['cloze_text']);
213  $this->setFixedTextLength($data["fixed_textlen"]);
214  $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
215  $this->setFeedbackMode($data['feedback_mode'] === null ? ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION : $data['feedback_mode']);
216  // replacement of old syntax with new syntax
217  include_once("./Services/RTE/classes/class.ilRTE.php");
218  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
219  $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
220  $this->setTextgapRating($data["textgap_rating"]);
221  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
222 
223  try {
224  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
225  } catch (ilTestQuestionPoolException $e) {
226  }
227 
228  // open the cloze gaps with all answers
229  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
230  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
231  $result = $ilDB->queryF(
232  "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
233  array("integer"),
234  array($question_id)
235  );
236  if ($result->numRows() > 0) {
237  $this->gaps = array();
238  while ($data = $ilDB->fetchAssoc($result)) {
239  switch ($data["cloze_type"]) {
240  case CLOZE_TEXT:
241  if (!array_key_exists($data["gap_id"], $this->gaps)) {
242  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
243  }
244  $answer = new assAnswerCloze(
245  $data["answertext"],
246  $data["points"],
247  $data["aorder"]
248  );
249  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
250 
251  $this->gaps[$data["gap_id"]]->addItem($answer);
252  break;
253  case CLOZE_SELECT:
254  if (!array_key_exists($data["gap_id"], $this->gaps)) {
255  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
256  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
257  }
258  $answer = new assAnswerCloze(
259  $data["answertext"],
260  $data["points"],
261  $data["aorder"]
262  );
263  $this->gaps[$data["gap_id"]]->addItem($answer);
264  break;
265  case CLOZE_NUMERIC:
266  if (!array_key_exists($data["gap_id"], $this->gaps)) {
267  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
268  }
269  $answer = new assAnswerCloze(
270  $data["answertext"],
271  $data["points"],
272  $data["aorder"]
273  );
274  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
275  $answer->setLowerBound($data["lowerlimit"]);
276  $answer->setUpperBound($data["upperlimit"]);
277  $this->gaps[$data["gap_id"]]->addItem($answer);
278  break;
279  }
280  }
281  }
282  }
283  $assClozeGapCombinationObj = new assClozeGapCombination();
284  $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
285  if (count($check_for_gap_combinations) != 0) {
286  $this->setGapCombinationsExists(true);
287  $this->setGapCombinations($check_for_gap_combinations);
288  }
289  parent::loadFromDb($question_id);
290  }
291 
292  #region Save question to db
293 
303  public function saveToDb($original_id = "")
304  {
308 
309  parent::saveToDb($original_id);
310  }
311 
315  public function saveAnswerSpecificDataToDb()
316  {
317  global $DIC;
318  $ilDB = $DIC['ilDB'];
319 
320  $ilDB->manipulateF(
321  "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
322  array( "integer" ),
323  array( $this->getId() )
324  );
325 
326  foreach ($this->gaps as $key => $gap) {
327  $this->saveClozeGapItemsToDb($gap, $key);
328  }
329  }
330 
337  {
338  global $DIC; /* @var ILIAS\DI\Container $DIC */
339 
340 
341  $DIC->database()->manipulateF(
342  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
343  array( "integer" ),
344  array( $this->getId() )
345  );
346 
347  $DIC->database()->insert($this->getAdditionalTableName(), array(
348  'question_fi' => array('integer', $this->getId()),
349  'textgap_rating' => array('text', $this->getTextgapRating()),
350  'identical_scoring' => array('text', $this->getIdenticalScoring()),
351  'fixed_textlen' => array('integer', $this->getFixedTextLength() ? $this->getFixedTextLength() : null),
352  'cloze_text' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0)),
353  'feedback_mode' => array('text', $this->getFeedbackMode())
354  ));
355  }
356 
363  protected function saveClozeGapItemsToDb($gap, $key)
364  {
365  global $DIC;
366  $ilDB = $DIC['ilDB'];
367  foreach ($gap->getItems($this->getShuffler()) as $item) {
368  $query = "";
369  $next_id = $ilDB->nextId('qpl_a_cloze');
370  switch ($gap->getType()) {
371  case CLOZE_TEXT:
372  $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
373  break;
374  case CLOZE_SELECT:
375  $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
376  break;
377  case CLOZE_NUMERIC:
378  $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
379  break;
380  }
381  }
382  }
383 
392  protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
393  {
394  global $DIC;
395  $ilDB = $DIC['ilDB'];
396  $ilDB->manipulateF(
397  "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)",
398  array(
399  "integer",
400  "integer",
401  "integer",
402  "text",
403  "float",
404  "integer",
405  "text",
406  "integer"
407  ),
408  array(
409  $next_id,
410  $this->getId(),
411  $key,
412  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
413  $item->getPoints(),
414  $item->getOrder(),
415  $gap->getType(),
416  (int) $gap->getGapSize()
417  )
418  );
419  }
420 
429  protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
430  {
431  global $DIC;
432  $ilDB = $DIC['ilDB'];
433  $ilDB->manipulateF(
434  "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)",
435  array(
436  "integer",
437  "integer",
438  "integer",
439  "text",
440  "float",
441  "integer",
442  "text",
443  "text"
444  ),
445  array(
446  $next_id,
447  $this->getId(),
448  $key,
449  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
450  $item->getPoints(),
451  $item->getOrder(),
452  $gap->getType(),
453  ($gap->getShuffle()) ? "1" : "0"
454  )
455  );
456  }
457 
466  protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
467  {
468  global $DIC;
469  $ilDB = $DIC['ilDB'];
470 
471  include_once "./Services/Math/classes/class.EvalMath.php";
472  $eval = new EvalMath();
473  $eval->suppress_errors = true;
474  $ilDB->manipulateF(
475  "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)",
476  array(
477  "integer",
478  "integer",
479  "integer",
480  "text",
481  "float",
482  "integer",
483  "text",
484  "text",
485  "text",
486  "integer"
487  ),
488  array(
489  $next_id,
490  $this->getId(),
491  $key,
492  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
493  $item->getPoints(),
494  $item->getOrder(),
495  $gap->getType(),
496  ($eval->e($item->getLowerBound() !== false) && strlen(
497  $item->getLowerBound()
498  ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
499  ($eval->e($item->getUpperBound() !== false) && strlen(
500  $item->getUpperBound()
501  ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
502  (int) $gap->getGapSize()
503  )
504  );
505  }
506 
507 
508 
509  #endregion Save question to db
510 
517  public function getGaps()
518  {
519  return $this->gaps;
520  }
521 
522 
529  public function flushGaps()
530  {
531  $this->gaps = array();
532  }
533 
543  public function setClozeText($cloze_text = "")
544  {
545  $this->gaps = array();
547  $this->cloze_text = $cloze_text;
549  }
550 
551  public function setClozeTextValue($cloze_text = "")
552  {
553  $this->cloze_text = $cloze_text;
554  }
555 
563  public function getClozeText()
564  {
565  return $this->cloze_text;
566  }
567 
575  public function getStartTag()
576  {
577  return $this->start_tag;
578  }
579 
587  public function setStartTag($start_tag = "[gap]")
588  {
589  $this->start_tag = $start_tag;
590  }
591 
599  public function getEndTag()
600  {
601  return $this->end_tag;
602  }
603 
611  public function setEndTag($end_tag = "[/gap]")
612  {
613  $this->end_tag = $end_tag;
614  }
615 
619  public function getFeedbackMode()
620  {
621  return $this->feedbackMode;
622  }
623 
628  {
629  $this->feedbackMode = $feedbackMode;
630  }
631 
638  public function createGapsFromQuestiontext()
639  {
640  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
641  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
642  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
643  preg_match_all($search_pattern, $this->getClozeText(), $found);
644  $this->gaps = array();
645  if (count($found[0])) {
646  foreach ($found[1] as $gap_index => $answers) {
647  // create text gaps by default
648  $gap = new assClozeGap(CLOZE_TEXT);
649  $textparams = preg_split("/(?<!\\\\),/", $answers);
650  foreach ($textparams as $key => $value) {
651  $answer = new assAnswerCloze($value, 0, $key);
652  $gap->addItem($answer);
653  }
654  $this->gaps[$gap_index] = $gap;
655  }
656  }
657  }
658 
664  public function setGapType($gap_index, $gap_type)
665  {
666  if (array_key_exists($gap_index, $this->gaps)) {
667  $this->gaps[$gap_index]->setType($gap_type);
668  }
669  }
670 
680  public function setGapShuffle($gap_index = 0, $shuffle = 1)
681  {
682  if (array_key_exists($gap_index, $this->gaps)) {
683  $this->gaps[$gap_index]->setShuffle($shuffle);
684  }
685  }
686 
693  public function clearGapAnswers()
694  {
695  foreach ($this->gaps as $gap_index => $gap) {
696  $this->gaps[$gap_index]->clearItems();
697  }
698  }
699 
707  public function getGapCount()
708  {
709  if (is_array($this->gaps)) {
710  return count($this->gaps);
711  } else {
712  return 0;
713  }
714  }
715 
726  public function addGapAnswer($gap_index, $order, $answer)
727  {
728  if (array_key_exists($gap_index, $this->gaps)) {
729  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC) {
730  // only allow notation with "." for real numbers
731  $answer = str_replace(",", ".", $answer);
732  }
733  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
734  }
735  }
736 
745  public function getGap($gap_index = 0)
746  {
747  if (array_key_exists($gap_index, $this->gaps)) {
748  return $this->gaps[$gap_index];
749  } else {
750  return null;
751  }
752  }
753 
754  public function setGapSize($gap_index, $order, $size)
755  {
756  if (array_key_exists($gap_index, $this->gaps)) {
757  $this->gaps[$gap_index]->setGapSize($size);
758  }
759  }
760 
771  public function setGapAnswerPoints($gap_index, $order, $points)
772  {
773  if (array_key_exists($gap_index, $this->gaps)) {
774  $this->gaps[$gap_index]->setItemPoints($order, $points);
775  }
776  }
777 
786  public function addGapText($gap_index)
787  {
788  if (array_key_exists($gap_index, $this->gaps)) {
789  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
790  $answer = new assAnswerCloze(
791  "",
792  0,
793  $this->gaps[$gap_index]->getItemCount()
794  );
795  $this->gaps[$gap_index]->addItem($answer);
796  }
797  }
798 
807  public function addGapAtIndex($gap, $index)
808  {
809  $this->gaps[$index] = $gap;
810  }
811 
822  public function setGapAnswerLowerBound($gap_index, $order, $bound)
823  {
824  if (array_key_exists($gap_index, $this->gaps)) {
825  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
826  }
827  }
828 
839  public function setGapAnswerUpperBound($gap_index, $order, $bound)
840  {
841  if (array_key_exists($gap_index, $this->gaps)) {
842  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
843  }
844  }
845 
852  public function getMaximumPoints()
853  {
854  $assClozeGapCombinationObj = new assClozeGapCombination();
855  $points = 0;
856  $gaps_used_in_combination = array();
857  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
858  $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
859  $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
860  }
861  foreach ($this->gaps as $gap_index => $gap) {
862  if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
863  if ($gap->getType() == CLOZE_TEXT) {
864  $gap_max_points = 0;
865  foreach ($gap->getItems($this->getShuffler()) as $item) {
866  if ($item->getPoints() > $gap_max_points) {
867  $gap_max_points = $item->getPoints();
868  }
869  }
870  $points += $gap_max_points;
871  } elseif ($gap->getType() == CLOZE_SELECT) {
872  $srpoints = 0;
873  foreach ($gap->getItems($this->getShuffler()) as $item) {
874  if ($item->getPoints() > $srpoints) {
875  $srpoints = $item->getPoints();
876  }
877  }
878  $points += $srpoints;
879  } elseif ($gap->getType() == CLOZE_NUMERIC) {
880  $numpoints = 0;
881  foreach ($gap->getItems($this->getShuffler()) as $item) {
882  if ($item->getPoints() > $numpoints) {
883  $numpoints = $item->getPoints();
884  }
885  }
886  $points += $numpoints;
887  }
888  }
889  }
890 
891  return $points;
892  }
893 
899  public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
900  {
901  if ($this->id <= 0) {
902  // The question has not been saved. It cannot be duplicated
903  return;
904  }
905  // duplicate the question in database
906  $this_id = $this->getId();
907  $thisObjId = $this->getObjId();
908 
909  $clone = $this;
910  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
912  $clone->id = -1;
913 
914  if ((int) $testObjId > 0) {
915  $clone->setObjId($testObjId);
916  }
917 
918  if ($title) {
919  $clone->setTitle($title);
920  }
921  if ($author) {
922  $clone->setAuthor($author);
923  }
924  if ($owner) {
925  $clone->setOwner($owner);
926  }
927  if ($for_test) {
928  $clone->saveToDb($original_id);
929  } else {
930  $clone->saveToDb();
931  }
932  if ($this->gap_combinations_exists) {
933  $this->copyGapCombination($this_id, $clone->getId());
934  }
935  if ($for_test) {
936  $clone->saveToDb($original_id);
937  } else {
938  $clone->saveToDb();
939  }
940  // copy question page content
941  $clone->copyPageOfQuestion($this_id);
942  // copy XHTML media objects
943  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
944 
945  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
946 
947  return $clone->getId();
948  }
949 
955  public function copyObject($target_questionpool_id, $title = "")
956  {
957  if ($this->getId() <= 0) {
958  // The question has not been saved. It cannot be duplicated
959  return;
960  }
961 
962  $thisId = $this->getId();
963  $thisObjId = $this->getObjId();
964 
965  $clone = $this;
966  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
968  $clone->id = -1;
969  $clone->setObjId($target_questionpool_id);
970  if ($title) {
971  $clone->setTitle($title);
972  }
973 
974  $clone->saveToDb();
975 
976  if ($this->gap_combinations_exists) {
977  $this->copyGapCombination($original_id, $clone->getId());
978  $clone->saveToDb();
979  }
980 
981  // copy question page content
982  $clone->copyPageOfQuestion($original_id);
983  // copy XHTML media objects
984  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
985 
986  $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
987 
988  return $clone->getId();
989  }
990 
991  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
992  {
993  if ($this->id <= 0) {
994  // The question has not been saved. It cannot be duplicated
995  return;
996  }
997 
998  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
999 
1000  $sourceQuestionId = $this->id;
1001  $sourceParentId = $this->getObjId();
1002 
1003  // duplicate the question in database
1004  $clone = $this;
1005  $clone->id = -1;
1006 
1007  $clone->setObjId($targetParentId);
1008 
1009  if ($targetQuestionTitle) {
1010  $clone->setTitle($targetQuestionTitle);
1011  }
1012 
1013  $clone->saveToDb();
1014 
1015  if ($this->gap_combinations_exists) {
1016  $this->copyGapCombination($sourceQuestionId, $clone->getId());
1017  $clone->saveToDb();
1018  }
1019  // copy question page content
1020  $clone->copyPageOfQuestion($sourceQuestionId);
1021  // copy XHTML media objects
1022  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
1023 
1024  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
1025 
1026  return $clone->id;
1027  }
1028 
1029  public function copyGapCombination($orgID, $newID)
1030  {
1031  $assClozeGapCombinationObj = new assClozeGapCombination();
1032  $array = $assClozeGapCombinationObj->loadFromDb($orgID);
1033  $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array);
1034  }
1035 
1041  public function updateClozeTextFromGaps()
1042  {
1043  $output = $this->getClozeText();
1044  foreach ($this->getGaps() as $gap_index => $gap) {
1045  $answers = array();
1046  foreach ($gap->getItemsRaw() as $item) {
1047  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1048  }
1049  // fau: fixGapReplace - use replace function
1050  $output = $this->replaceFirstGap($output, "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]");
1051 // fau.
1052  }
1053  $output = str_replace("_gap]", "gap]", $output);
1054  $this->cloze_text = $output;
1055  }
1056 
1066  public function deleteAnswerText($gap_index, $answer_index)
1067  {
1068  if (array_key_exists($gap_index, $this->gaps)) {
1069  if ($this->gaps[$gap_index]->getItemCount() == 1) {
1070  // this is the last answer text => remove the gap
1071  $this->deleteGap($gap_index);
1072  } else {
1073  // remove the answer text
1074  $this->gaps[$gap_index]->deleteItem($answer_index);
1075  $this->updateClozeTextFromGaps();
1076  }
1077  }
1078  }
1079 
1088  public function deleteGap($gap_index)
1089  {
1090  if (array_key_exists($gap_index, $this->gaps)) {
1091  $output = $this->getClozeText();
1092  foreach ($this->getGaps() as $replace_gap_index => $gap) {
1093  $answers = array();
1094  foreach ($gap->getItemsRaw() as $item) {
1095  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1096  }
1097  if ($replace_gap_index == $gap_index) {
1098  // fau: fixGapReplace - use replace function
1099  $output = $this->replaceFirstGap($output, '');
1100 // fau.
1101  } else {
1102  // fau: fixGapReplace - use replace function
1103  $output = $this->replaceFirstGap($output, "[_gap]" . join(",", $answers) . "[/_gap]");
1104 // fau.
1105  }
1106  }
1107  $output = str_replace("_gap]", "gap]", $output);
1108  $this->cloze_text = $output;
1109  unset($this->gaps[$gap_index]);
1110  $this->gaps = array_values($this->gaps);
1111  }
1112  }
1113 
1123  public function getTextgapPoints($a_original, $a_entered, $max_points)
1124  {
1125  include_once "./Services/Utilities/classes/class.ilStr.php";
1126  $result = 0;
1127  $gaprating = $this->getTextgapRating();
1128  switch ($gaprating) {
1130  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) {
1131  $result = $max_points;
1132  }
1133  break;
1135  if (strcmp($a_original, $a_entered) == 0) {
1136  $result = $max_points;
1137  }
1138  break;
1140  if (levenshtein($a_original, $a_entered) <= 1) {
1141  $result = $max_points;
1142  }
1143  break;
1145  if (levenshtein($a_original, $a_entered) <= 2) {
1146  $result = $max_points;
1147  }
1148  break;
1150  if (levenshtein($a_original, $a_entered) <= 3) {
1151  $result = $max_points;
1152  }
1153  break;
1155  if (levenshtein($a_original, $a_entered) <= 4) {
1156  $result = $max_points;
1157  }
1158  break;
1160  if (levenshtein($a_original, $a_entered) <= 5) {
1161  $result = $max_points;
1162  }
1163  break;
1164  }
1165  return $result;
1166  }
1167 
1177  public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1178  {
1179  // fau: fixGapFormula - check entered value by evalMath
1180  // if( ! $this->checkForValidFormula($a_entered) )
1181  // {
1182  // return 0;
1183  // }
1184 
1185  include_once "./Services/Math/classes/class.EvalMath.php";
1186  $eval = new EvalMath();
1187  $eval->suppress_errors = true;
1188  $result = 0;
1189 
1190  if ($eval->e($a_entered) === false) {
1191  return 0;
1192  } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) {
1193  // fau.
1194  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1195  $result = $max_points;
1196  }
1197  } elseif ($eval->e($lowerBound) !== false) {
1198  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
1199  $result = $max_points;
1200  }
1201  } elseif ($eval->e($upperBound) !== false) {
1202  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1203  $result = $max_points;
1204  }
1205  } else {
1206  if ($eval->e($a_entered) == $eval->e($a_original)) {
1207  $result = $max_points;
1208  }
1209  }
1210  return $result;
1211  }
1212 
1217  public function checkForValidFormula($value)
1218  {
1219  return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1220  }
1231  public function calculateReachedPoints($active_id, $pass = null, $authorized = true, $returndetails = false)
1232  {
1233  global $DIC;
1234  $ilDB = $DIC['ilDB'];
1235 
1236  if (is_null($pass)) {
1237  $pass = $this->getSolutionMaxPass($active_id);
1238  }
1239 
1240  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1241  $user_result = array();
1242  while ($data = $ilDB->fetchAssoc($result)) {
1243  if (strcmp($data["value2"], "") != 0) {
1244  $user_result[$data["value1"]] = array(
1245  "gap_id" => $data["value1"],
1246  "value" => $data["value2"]
1247  );
1248  }
1249  }
1250 
1251  ksort($user_result); // this is required when identical scoring for same solutions is disabled
1252 
1253  if ($returndetails) {
1254  $detailed = array();
1255  $this->calculateReachedPointsForSolution($user_result, $detailed);
1256  return $detailed;
1257  }
1258 
1259  return $this->calculateReachedPointsForSolution($user_result);
1260  }
1261 
1262  protected function isValidNumericSubmitValue($submittedValue)
1263  {
1264  if (is_numeric($submittedValue)) {
1265  return true;
1266  }
1267 
1268  if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1269  return true;
1270  }
1271 
1272  return false;
1273  }
1274 
1275  public function validateSolutionSubmit()
1276  {
1277  foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) {
1278  $gap = $this->getGap($gapIndex);
1279 
1280  if ($gap->getType() != CLOZE_NUMERIC) {
1281  continue;
1282  }
1283 
1284  if (strlen($value) && !$this->isValidNumericSubmitValue($value)) {
1285  ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1286  return false;
1287  }
1288  }
1289 
1290  return true;
1291  }
1292 
1293  public function fetchSolutionSubmit($submit)
1294  {
1295  $solutionSubmit = array();
1296 
1297  foreach ($submit as $key => $value) {
1298  if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1299  $value = ilUtil::stripSlashes($value, false);
1300  if (strlen($value)) {
1301  $gap = $this->getGap($matches[1]);
1302  if (is_object($gap)) {
1303  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1304  if ($gap->getType() == CLOZE_NUMERIC && !is_numeric(str_replace(",", ".", $value))) {
1305  $value = null;
1306  } else if ($gap->getType() == CLOZE_NUMERIC) {
1307  $value = str_replace(",", ".", $value);
1308  }
1309  $solutionSubmit[trim($matches[1])] = $value;
1310  }
1311  }
1312  }
1313  }
1314  }
1315 
1316  return $solutionSubmit;
1317  }
1318 
1320  {
1321  $submit = $_POST;
1322  $solutionSubmit = array();
1323 
1324  foreach ($submit as $key => $value) {
1325  if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1326  $value = ilUtil::stripSlashes($value, false);
1327  if (strlen($value)) {
1328  $gap = $this->getGap($matches[1]);
1329  if (is_object($gap)) {
1330  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1331  if ($gap->getType() == CLOZE_NUMERIC) {
1332  $value = str_replace(",", ".", $value);
1333  }
1334  $solutionSubmit[trim($matches[1])] = $value;
1335  }
1336  }
1337  }
1338  }
1339  }
1340 
1341  return $solutionSubmit;
1342  }
1343 
1344  public function getSolutionSubmit()
1345  {
1346  return $this->fetchSolutionSubmit($_POST);
1347  }
1348 
1357  public function saveWorkingData($active_id, $pass = null, $authorized = true)
1358  {
1359  global $DIC;
1360  $ilDB = $DIC['ilDB'];
1361  $ilUser = $DIC['ilUser'];
1362  if (is_null($pass)) {
1363  include_once "./Modules/Test/classes/class.ilObjTest.php";
1364  $pass = ilObjTest::_getPass($active_id);
1365  }
1366 
1367  $entered_values = 0;
1368 
1369  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
1370  $this->removeCurrentSolution($active_id, $pass, $authorized);
1371 
1372  foreach ($this->getSolutionSubmit() as $val1 => $val2) {
1373  $value = trim(ilUtil::stripSlashes($val2, false));
1374  if (strlen($value)) {
1375  $gap = $this->getGap(trim(ilUtil::stripSlashes($val1)));
1376  if (is_object($gap)) {
1377  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1378  $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized);
1379  $entered_values++;
1380  }
1381  }
1382  }
1383  }
1384  });
1385 
1386  if ($entered_values) {
1387  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1389  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1390  }
1391  } else {
1392  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1394  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1395  }
1396  }
1397 
1398  return true;
1399  }
1400 
1407  public function getQuestionType()
1408  {
1409  return "assClozeTest";
1410  }
1411 
1419  public function getTextgapRating()
1420  {
1421  return $this->textgap_rating;
1422  }
1423 
1431  public function setTextgapRating($a_textgap_rating)
1432  {
1433  switch ($a_textgap_rating) {
1441  $this->textgap_rating = $a_textgap_rating;
1442  break;
1443  default:
1444  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1445  break;
1446  }
1447  }
1448 
1456  public function getIdenticalScoring()
1457  {
1458  return ($this->identical_scoring) ? 1 : 0;
1459  }
1460 
1468  public function setIdenticalScoring($a_identical_scoring)
1469  {
1470  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1471  }
1472 
1479  public function getAdditionalTableName()
1480  {
1481  return "qpl_qst_cloze";
1482  }
1483 
1490  public function getAnswerTableName()
1491  {
1492  return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1493  }
1494 
1501  public function setFixedTextLength($a_text_len)
1502  {
1503  $this->fixedTextLength = $a_text_len;
1504  }
1505 
1512  public function getFixedTextLength()
1513  {
1514  return $this->fixedTextLength;
1515  }
1516 
1525  public function getMaximumGapPoints($gap_index)
1526  {
1527  $points = 0;
1528  $gap_max_points = 0;
1529  if (array_key_exists($gap_index, $this->gaps)) {
1530  $gap = &$this->gaps[$gap_index];
1531  foreach ($gap->getItems($this->getShuffler()) as $answer) {
1532  if ($answer->getPoints() > $gap_max_points) {
1533  $gap_max_points = $answer->getPoints();
1534  }
1535  }
1536  $points += $gap_max_points;
1537  }
1538  return $points;
1539  }
1540 
1545  public function getRTETextWithMediaObjects()
1546  {
1547  return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1548  }
1549  public function getGapCombinationsExists()
1550  {
1552  }
1553 
1554  public function getGapCombinations()
1555  {
1556  return $this->gap_combinations;
1557  }
1558 
1559  public function setGapCombinationsExists($value)
1560  {
1561  $this->gap_combinations_exists = $value;
1562  }
1563 
1564  public function setGapCombinations($value)
1565  {
1566  $this->gap_combinations = $value;
1567  }
1568 
1572  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1573  {
1574  parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1575 
1576  $solution = $this->getSolutionValues($active_id, $pass);
1577  $i = 1;
1578  foreach ($this->getGaps() as $gap_index => $gap) {
1579  $worksheet->setCell($startrow + $i, 0, $this->lng->txt("gap") . " $i");
1580  $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1581  $checked = false;
1582  foreach ($solution as $solutionvalue) {
1583  if ($gap_index == $solutionvalue["value1"]) {
1584  $string_escaping_org_value = $worksheet->getStringEscaping();
1585  try {
1586  $worksheet->setStringEscaping(false);
1587 
1588  switch ($gap->getType()) {
1589  case CLOZE_SELECT:
1590  $worksheet->setCell($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1591  break;
1592  case CLOZE_NUMERIC:
1593  case CLOZE_TEXT:
1594  $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1595  break;
1596  }
1597  } finally {
1598  $worksheet->setStringEscaping($string_escaping_org_value);
1599  }
1600  }
1601  }
1602  $i++;
1603  }
1604 
1605  return $startrow + $i + 1;
1606  }
1607 
1612  {
1613  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1614  //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1615  $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1616  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1617  }
1618 
1622  public function toJSON()
1623  {
1624  include_once("./Services/RTE/classes/class.ilRTE.php");
1625  $result = array();
1626  $result['id'] = (int) $this->getId();
1627  $result['type'] = (string) $this->getQuestionType();
1628  $result['title'] = (string) $this->getTitle();
1629  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1630  $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1631  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1632  $result['shuffle'] = (bool) $this->getShuffle();
1633  $result['feedback'] = array(
1634  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1635  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1636  );
1637 
1638  $gaps = array();
1639  foreach ($this->getGaps() as $key => $gap) {
1640  $items = array();
1641  foreach ($gap->getItems($this->getShuffler()) as $item) {
1642  $jitem = array();
1643  $jitem['points'] = $item->getPoints();
1644  $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1645  $jitem['order'] = $item->getOrder();
1646  if ($gap->getType() == CLOZE_NUMERIC) {
1647  $jitem['lowerbound'] = $item->getLowerBound();
1648  $jitem['upperbound'] = $item->getUpperBound();
1649  } else {
1650  $jitem['value'] = trim($jitem['value']);
1651  }
1652  array_push($items, $jitem);
1653  }
1654 
1655  if ($gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC)) {
1656  $jgap['size'] = $gap->getGapSize();
1657  }
1658 
1659  $jgap['shuffle'] = $gap->getShuffle();
1660  $jgap['type'] = $gap->getType();
1661  $jgap['item'] = $items;
1662 
1663  array_push($gaps, $jgap);
1664  }
1665  $result['gaps'] = $gaps;
1666  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1667  $result['mobs'] = $mobs;
1668  return json_encode($result);
1669  }
1670 
1679  public function getOperators($expression)
1680  {
1681  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1683  }
1684 
1689  public function getExpressionTypes()
1690  {
1691  return array(
1697  );
1698  }
1699 
1708  public function getUserQuestionResult($active_id, $pass)
1709  {
1711  global $DIC;
1712  $ilDB = $DIC['ilDB'];
1713  $result = new ilUserQuestionResult($this, $active_id, $pass);
1714 
1715  $maxStep = $this->lookupMaxStep($active_id, $pass);
1716 
1717  if ($maxStep !== null) {
1718  $data = $ilDB->queryF(
1719  "
1720  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1721  FROM tst_solutions sol
1722  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1723  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1724  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1725  ",
1726  array("integer", "integer", "integer","integer"),
1727  array($active_id, $pass, $this->getId(), $maxStep)
1728  );
1729  } else {
1730  $data = $ilDB->queryF(
1731  "
1732  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1733  FROM tst_solutions sol
1734  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1735  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1736  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1737  ",
1738  array("integer", "integer", "integer"),
1739  array($active_id, $pass, $this->getId())
1740  );
1741  }
1742 
1743  while ($row = $ilDB->fetchAssoc($data)) {
1744  if ($row["cloze_type"] == 1) {
1745  $row["value2"]++;
1746  }
1747  $result->addKeyValue($row["val"], $row["value2"]);
1748  }
1749 
1750  $points = $this->calculateReachedPoints($active_id, $pass);
1751  $max_points = $this->getMaximumPoints();
1752 
1753  $result->setReachedPercentage(($points / $max_points) * 100);
1754 
1755  return $result;
1756  }
1757 
1766  public function getAvailableAnswerOptions($index = null)
1767  {
1768  if ($index !== null) {
1769  return $this->getGap($index);
1770  } else {
1771  return $this->getGaps();
1772  }
1773  }
1774 
1775  public function calculateCombinationResult($user_result)
1776  {
1777  $points = 0;
1778 
1779  $assClozeGapCombinationObj = new assClozeGapCombination();
1780 
1781  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1782  $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1783  $gap_answers = array();
1784  $gap_used_in_combination = array();
1785  foreach ($user_result as $user_result_build_list) {
1786  if (is_array($user_result_build_list)) {
1787  $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1788  }
1789  }
1790 
1791  foreach ($combinations_for_question as $combination) {
1792  foreach ($combination as $row_key => $row_answers) {
1793  $combination_fulfilled = true;
1794  $points_for_combination = $row_answers['points'];
1795  foreach ($row_answers as $gap_key => $combination_gap_answer) {
1796  if ($gap_key !== 'points') {
1797  $gap_used_in_combination[$gap_key] = $gap_key;
1798  }
1799  if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1800  switch ($combination_gap_answer['type']) {
1801  case CLOZE_TEXT:
1802  $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1803  if ($is_text_gap_correct != 1) {
1804  $combination_fulfilled = false;
1805  }
1806  break;
1807  case CLOZE_SELECT:
1808  $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1809  $answertext = $answer->getAnswertext();
1810  if ($answertext != $combination_gap_answer['answer']) {
1811  $combination_fulfilled = false;
1812  }
1813  break;
1814  case CLOZE_NUMERIC:
1815  $answer = $this->gaps[$gap_key]->getItem(0);
1816  if ($combination_gap_answer['answer'] != 'out_of_bound') {
1817  $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1818  if ($is_numeric_gap_correct != 1) {
1819  $combination_fulfilled = false;
1820  }
1821  } else {
1822  $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1823  if ($wrong_is_the_new_right == 1) {
1824  $combination_fulfilled = false;
1825  }
1826  }
1827  break;
1828  }
1829  } else {
1830  if ($gap_key !== 'points') {
1831  $combination_fulfilled = false;
1832  }
1833  }
1834  }
1835  if ($combination_fulfilled) {
1836  $points += $points_for_combination;
1837  }
1838  }
1839  }
1840  }
1841  return array($points, $gap_used_in_combination);
1842  }
1848  protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1849  {
1850  if ($detailed === null) {
1851  $detailed = array();
1852  }
1853 
1854  $assClozeGapCombinationObj = new assClozeGapCombination();
1855  $combinations[1] = array();
1856  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1857  $combinations = $this->calculateCombinationResult($user_result);
1858  $points = $combinations[0];
1859  }
1860  $counter = 0;
1861  $solution_values_text = array(); // for identical scoring checks
1862  $solution_values_select = array(); // for identical scoring checks
1863  $solution_values_numeric = array(); // for identical scoring checks
1864  foreach ($user_result as $gap_id => $value) {
1865  if (is_string($value)) {
1866  $value = array("value" => $value);
1867  }
1868 
1869  if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1870  switch ($this->gaps[$gap_id]->getType()) {
1871  case CLOZE_TEXT:
1872  $gappoints = 0;
1873  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1874  $answer = $this->gaps[$gap_id]->getItem($order);
1875  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1876  if ($gotpoints > $gappoints) {
1877  $gappoints = $gotpoints;
1878  }
1879  }
1880  if (!$this->getIdenticalScoring()) {
1881  // check if the same solution text was already entered
1882  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0)) {
1883  $gappoints = 0;
1884  }
1885  }
1886  $points += $gappoints;
1887  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1888  array_push($solution_values_text, $value["value"]);
1889  break;
1890  case CLOZE_NUMERIC:
1891  $gappoints = 0;
1892  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1893  $answer = $this->gaps[$gap_id]->getItem($order);
1894  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1895  if ($gotpoints > $gappoints) {
1896  $gappoints = $gotpoints;
1897  }
1898  }
1899  if (!$this->getIdenticalScoring()) {
1900  // check if the same solution value was already entered
1901  include_once "./Services/Math/classes/class.EvalMath.php";
1902  $eval = new EvalMath();
1903  $eval->suppress_errors = true;
1904  $found_value = false;
1905  foreach ($solution_values_numeric as $solval) {
1906  if ($eval->e($solval) == $eval->e($value["value"])) {
1907  $found_value = true;
1908  }
1909  }
1910  if ($found_value && ($gappoints > 0)) {
1911  $gappoints = 0;
1912  }
1913  }
1914  $points += $gappoints;
1915  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1916  array_push($solution_values_numeric, $value["value"]);
1917  break;
1918  case CLOZE_SELECT:
1919  if ($value["value"] >= 0) {
1920  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1921  $answer = $this->gaps[$gap_id]->getItem($order);
1922  if ($value["value"] == $answer->getOrder()) {
1923  $answerpoints = $answer->getPoints();
1924  if (!$this->getIdenticalScoring()) {
1925  // check if the same solution value was already entered
1926  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0)) {
1927  $answerpoints = 0;
1928  }
1929  }
1930  $points += $answerpoints;
1931  $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? true : false, "positive" => ($answerpoints > 0) ? true : false);
1932  array_push($solution_values_select, $answer->getAnswertext());
1933  }
1934  }
1935  }
1936  break;
1937  }
1938  }
1939  }
1940 
1941  return $points;
1942  }
1943 
1945  {
1946  $userSolution = array();
1947 
1948  foreach ($previewSession->getParticipantsSolution() as $key => $val) {
1949  // fau: fixEmptyGapPreview - add the gap_id as array index
1950  $userSolution[$key] = array('gap_id' => $key, 'value' => $val);
1951  // fau.
1952  }
1953 
1954  $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
1955  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
1956 
1957  return $this->ensureNonNegativePoints($reachedPoints);
1958  }
1959 
1960  public function fetchAnswerValueForGap($userSolution, $gapIndex)
1961  {
1962  $answerValue = '';
1963 
1964  foreach ($userSolution as $value1 => $value2) {
1965  if ($value1 == $gapIndex) {
1966  $answerValue = $value2;
1967  break;
1968  }
1969  }
1970 
1971  return $answerValue;
1972  }
1973 
1974  public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
1975  {
1976  $gap = $this->getGap($qIndex);
1977 
1978  if ($gap->getType() != CLOZE_TEXT) {
1979  return false;
1980  }
1981 
1982  foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) {
1983  if ($item->getAnswertext() == $answerOptionValue) {
1984  return false;
1985  }
1986  }
1987 
1988  return true;
1989  }
1990 
1991  public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
1992  {
1993  $gap = $this->getGap($qIndex); /* @var assClozeGap $gap */
1994 
1995  $item = new assAnswerCloze($answerOptionValue, $points);
1996  $item->setOrder($gap->getItemCount());
1997 
1998  $gap->addItem($item);
1999  }
2000 
2001  public function savePartial()
2002  {
2003  return true;
2004  }
2005 }
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
getId()
Gets the id of the assQuestion object.
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.
$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
global $DIC
Definition: saml.php:7
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...
addAnswerOptionValue($qIndex, $answerOptionValue, $points)
getStartTag()
Returns the start tag of a cloze gap.
isAddableAnswerOptionValue($qIndex, $answerOptionValue)
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.
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.
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.
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.
fetchAnswerValueForGap($userSolution, $gapIndex)
flushGaps()
Deletes all gaps without changing the cloze text.
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
$row
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.
replaceFirstGap($gaptext, $content)
Replace the first gap in a string without treating backreferences.
updateClozeTextFromGaps()
Updates the gap parameters in the cloze text from the form input.
setFeedbackMode($feedbackMode)
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"]
const FB_MODE_GAP_QUESTION
constants for different feedback modes (per gap or per gap-answers/options)
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
$data
Definition: bench.php:6