ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
All Data Structures Namespaces Files Functions Variables Modules Pages
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  ) {
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 
217  try {
218  $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
221  }
222 
223  // replacement of old syntax with new syntax
224  include_once("./Services/RTE/classes/class.ilRTE.php");
225  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
226  $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
227  $this->setTextgapRating($data["textgap_rating"]);
228  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
229 
230  try {
231  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
232  } catch (ilTestQuestionPoolException $e) {
233  }
234 
235  // open the cloze gaps with all answers
236  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
237  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
238  $result = $ilDB->queryF(
239  "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
240  array("integer"),
241  array($question_id)
242  );
243  if ($result->numRows() > 0) {
244  $this->gaps = array();
245  while ($data = $ilDB->fetchAssoc($result)) {
246  switch ($data["cloze_type"]) {
247  case CLOZE_TEXT:
248  if (!array_key_exists($data["gap_id"], $this->gaps)) {
249  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
250  }
251  $answer = new assAnswerCloze(
252  $data["answertext"],
253  $data["points"],
254  $data["aorder"]
255  );
256  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
257 
258  $this->gaps[$data["gap_id"]]->addItem($answer);
259  break;
260  case CLOZE_SELECT:
261  if (!array_key_exists($data["gap_id"], $this->gaps)) {
262  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
263  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
264  }
265  $answer = new assAnswerCloze(
266  $data["answertext"],
267  $data["points"],
268  $data["aorder"]
269  );
270  $this->gaps[$data["gap_id"]]->addItem($answer);
271  break;
272  case CLOZE_NUMERIC:
273  if (!array_key_exists($data["gap_id"], $this->gaps)) {
274  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
275  }
276  $answer = new assAnswerCloze(
277  $data["answertext"],
278  $data["points"],
279  $data["aorder"]
280  );
281  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
282  $answer->setLowerBound($data["lowerlimit"]);
283  $answer->setUpperBound($data["upperlimit"]);
284  $this->gaps[$data["gap_id"]]->addItem($answer);
285  break;
286  }
287  }
288  }
289  }
290  $assClozeGapCombinationObj = new assClozeGapCombination();
291  $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
292  if (count($check_for_gap_combinations) != 0) {
293  $this->setGapCombinationsExists(true);
294  $this->setGapCombinations($check_for_gap_combinations);
295  }
296  parent::loadFromDb($question_id);
297  }
298 
299  #region Save question to db
300 
310  public function saveToDb($original_id = "")
311  {
315 
316  parent::saveToDb($original_id);
317  }
318 
322  public function saveAnswerSpecificDataToDb()
323  {
324  global $DIC;
325  $ilDB = $DIC['ilDB'];
326 
327  $ilDB->manipulateF(
328  "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
329  array( "integer" ),
330  array( $this->getId() )
331  );
332 
333  foreach ($this->gaps as $key => $gap) {
334  $this->saveClozeGapItemsToDb($gap, $key);
335  }
336  }
337 
344  {
345  global $DIC; /* @var ILIAS\DI\Container $DIC */
346 
347 
348  $DIC->database()->manipulateF(
349  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
350  array( "integer" ),
351  array( $this->getId() )
352  );
353 
354  $DIC->database()->insert($this->getAdditionalTableName(), array(
355  'question_fi' => array('integer', $this->getId()),
356  'textgap_rating' => array('text', $this->getTextgapRating()),
357  'identical_scoring' => array('text', $this->getIdenticalScoring()),
358  'fixed_textlen' => array('integer', $this->getFixedTextLength() ? $this->getFixedTextLength() : null),
359  'cloze_text' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0)),
360  'feedback_mode' => array('text', $this->getFeedbackMode())
361  ));
362  }
363 
370  protected function saveClozeGapItemsToDb($gap, $key)
371  {
372  global $DIC;
373  $ilDB = $DIC['ilDB'];
374  foreach ($gap->getItems($this->getShuffler()) as $item) {
375  $query = "";
376  $next_id = $ilDB->nextId('qpl_a_cloze');
377  switch ($gap->getType()) {
378  case CLOZE_TEXT:
379  $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
380  break;
381  case CLOZE_SELECT:
382  $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
383  break;
384  case CLOZE_NUMERIC:
385  $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
386  break;
387  }
388  }
389  }
390 
399  protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
400  {
401  global $DIC;
402  $ilDB = $DIC['ilDB'];
403  $ilDB->manipulateF(
404  "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)",
405  array(
406  "integer",
407  "integer",
408  "integer",
409  "text",
410  "float",
411  "integer",
412  "text",
413  "integer"
414  ),
415  array(
416  $next_id,
417  $this->getId(),
418  $key,
419  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
420  $item->getPoints(),
421  $item->getOrder(),
422  $gap->getType(),
423  (int) $gap->getGapSize()
424  )
425  );
426  }
427 
436  protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
437  {
438  global $DIC;
439  $ilDB = $DIC['ilDB'];
440  $ilDB->manipulateF(
441  "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)",
442  array(
443  "integer",
444  "integer",
445  "integer",
446  "text",
447  "float",
448  "integer",
449  "text",
450  "text"
451  ),
452  array(
453  $next_id,
454  $this->getId(),
455  $key,
456  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
457  $item->getPoints(),
458  $item->getOrder(),
459  $gap->getType(),
460  ($gap->getShuffle()) ? "1" : "0"
461  )
462  );
463  }
464 
473  protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
474  {
475  global $DIC;
476  $ilDB = $DIC['ilDB'];
477 
478  include_once "./Services/Math/classes/class.EvalMath.php";
479  $eval = new EvalMath();
480  $eval->suppress_errors = true;
481  $ilDB->manipulateF(
482  "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)",
483  array(
484  "integer",
485  "integer",
486  "integer",
487  "text",
488  "float",
489  "integer",
490  "text",
491  "text",
492  "text",
493  "integer"
494  ),
495  array(
496  $next_id,
497  $this->getId(),
498  $key,
499  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
500  $item->getPoints(),
501  $item->getOrder(),
502  $gap->getType(),
503  ($eval->e($item->getLowerBound() !== false) && strlen(
504  $item->getLowerBound()
505  ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
506  ($eval->e($item->getUpperBound() !== false) && strlen(
507  $item->getUpperBound()
508  ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
509  (int) $gap->getGapSize()
510  )
511  );
512  }
513 
514 
515 
516  #endregion Save question to db
517 
524  public function getGaps()
525  {
526  return $this->gaps;
527  }
528 
529 
536  public function flushGaps()
537  {
538  $this->gaps = array();
539  }
540 
550  public function setClozeText($cloze_text = "")
551  {
552  $this->gaps = array();
554  $this->cloze_text = $cloze_text;
556  }
557 
558  public function setClozeTextValue($cloze_text = "")
559  {
560  $this->cloze_text = $cloze_text;
561  }
562 
570  public function getClozeText()
571  {
572  return $this->cloze_text;
573  }
574 
582  public function getStartTag()
583  {
584  return $this->start_tag;
585  }
586 
594  public function setStartTag($start_tag = "[gap]")
595  {
596  $this->start_tag = $start_tag;
597  }
598 
606  public function getEndTag()
607  {
608  return $this->end_tag;
609  }
610 
618  public function setEndTag($end_tag = "[/gap]")
619  {
620  $this->end_tag = $end_tag;
621  }
622 
626  public function getFeedbackMode()
627  {
628  return $this->feedbackMode;
629  }
630 
635  {
636  $this->feedbackMode = $feedbackMode;
637  }
638 
645  public function createGapsFromQuestiontext()
646  {
647  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
648  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
649  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
650  preg_match_all($search_pattern, $this->getClozeText(), $found);
651  $this->gaps = array();
652  if (count($found[0])) {
653  foreach ($found[1] as $gap_index => $answers) {
654  // create text gaps by default
655  $gap = new assClozeGap(CLOZE_TEXT);
656  $textparams = preg_split("/(?<!\\\\),/", $answers);
657  foreach ($textparams as $key => $value) {
658  $answer = new assAnswerCloze($value, 0, $key);
659  $gap->addItem($answer);
660  }
661  $this->gaps[$gap_index] = $gap;
662  }
663  }
664  }
665 
671  public function setGapType($gap_index, $gap_type)
672  {
673  if (array_key_exists($gap_index, $this->gaps)) {
674  $this->gaps[$gap_index]->setType($gap_type);
675  }
676  }
677 
687  public function setGapShuffle($gap_index = 0, $shuffle = 1)
688  {
689  if (array_key_exists($gap_index, $this->gaps)) {
690  $this->gaps[$gap_index]->setShuffle($shuffle);
691  }
692  }
693 
700  public function clearGapAnswers()
701  {
702  foreach ($this->gaps as $gap_index => $gap) {
703  $this->gaps[$gap_index]->clearItems();
704  }
705  }
706 
714  public function getGapCount()
715  {
716  if (is_array($this->gaps)) {
717  return count($this->gaps);
718  } else {
719  return 0;
720  }
721  }
722 
733  public function addGapAnswer($gap_index, $order, $answer)
734  {
735  if (array_key_exists($gap_index, $this->gaps)) {
736  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC) {
737  // only allow notation with "." for real numbers
738  $answer = str_replace(",", ".", $answer);
739  }
740  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
741  }
742  }
743 
752  public function getGap($gap_index = 0)
753  {
754  if (array_key_exists($gap_index, $this->gaps)) {
755  return $this->gaps[$gap_index];
756  } else {
757  return null;
758  }
759  }
760 
761  public function setGapSize($gap_index, $order, $size)
762  {
763  if (array_key_exists($gap_index, $this->gaps)) {
764  $this->gaps[$gap_index]->setGapSize($size);
765  }
766  }
767 
778  public function setGapAnswerPoints($gap_index, $order, $points)
779  {
780  if (array_key_exists($gap_index, $this->gaps)) {
781  $this->gaps[$gap_index]->setItemPoints($order, $points);
782  }
783  }
784 
793  public function addGapText($gap_index)
794  {
795  if (array_key_exists($gap_index, $this->gaps)) {
796  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
797  $answer = new assAnswerCloze(
798  "",
799  0,
800  $this->gaps[$gap_index]->getItemCount()
801  );
802  $this->gaps[$gap_index]->addItem($answer);
803  }
804  }
805 
814  public function addGapAtIndex($gap, $index)
815  {
816  $this->gaps[$index] = $gap;
817  }
818 
829  public function setGapAnswerLowerBound($gap_index, $order, $bound)
830  {
831  if (array_key_exists($gap_index, $this->gaps)) {
832  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
833  }
834  }
835 
846  public function setGapAnswerUpperBound($gap_index, $order, $bound)
847  {
848  if (array_key_exists($gap_index, $this->gaps)) {
849  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
850  }
851  }
852 
859  public function getMaximumPoints()
860  {
861  $assClozeGapCombinationObj = new assClozeGapCombination();
862  $points = 0;
863  $gaps_used_in_combination = array();
864  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
865  $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
866  $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
867  }
868  foreach ($this->gaps as $gap_index => $gap) {
869  if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
870  if ($gap->getType() == CLOZE_TEXT) {
871  $gap_max_points = 0;
872  foreach ($gap->getItems($this->getShuffler()) as $item) {
873  if ($item->getPoints() > $gap_max_points) {
874  $gap_max_points = $item->getPoints();
875  }
876  }
877  $points += $gap_max_points;
878  } elseif ($gap->getType() == CLOZE_SELECT) {
879  $srpoints = 0;
880  foreach ($gap->getItems($this->getShuffler()) as $item) {
881  if ($item->getPoints() > $srpoints) {
882  $srpoints = $item->getPoints();
883  }
884  }
885  $points += $srpoints;
886  } elseif ($gap->getType() == CLOZE_NUMERIC) {
887  $numpoints = 0;
888  foreach ($gap->getItems($this->getShuffler()) as $item) {
889  if ($item->getPoints() > $numpoints) {
890  $numpoints = $item->getPoints();
891  }
892  }
893  $points += $numpoints;
894  }
895  }
896  }
897 
898  return $points;
899  }
900 
906  public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
907  {
908  if ($this->id <= 0) {
909  // The question has not been saved. It cannot be duplicated
910  return;
911  }
912  // duplicate the question in database
913  $this_id = $this->getId();
914  $thisObjId = $this->getObjId();
915 
916  $clone = $this;
917  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
919  $clone->id = -1;
920 
921  if ((int) $testObjId > 0) {
922  $clone->setObjId($testObjId);
923  }
924 
925  if ($title) {
926  $clone->setTitle($title);
927  }
928  if ($author) {
929  $clone->setAuthor($author);
930  }
931  if ($owner) {
932  $clone->setOwner($owner);
933  }
934  if ($for_test) {
935  $clone->saveToDb($original_id);
936  } else {
937  $clone->saveToDb();
938  }
939  if ($this->gap_combinations_exists) {
940  $this->copyGapCombination($this_id, $clone->getId());
941  }
942  if ($for_test) {
943  $clone->saveToDb($original_id);
944  } else {
945  $clone->saveToDb();
946  }
947  // copy question page content
948  $clone->copyPageOfQuestion($this_id);
949  // copy XHTML media objects
950  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
951 
952  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
953 
954  return $clone->getId();
955  }
956 
962  public function copyObject($target_questionpool_id, $title = "")
963  {
964  if ($this->getId() <= 0) {
965  // The question has not been saved. It cannot be duplicated
966  return;
967  }
968 
969  $thisId = $this->getId();
970  $thisObjId = $this->getObjId();
971 
972  $clone = $this;
973  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
975  $clone->id = -1;
976  $clone->setObjId($target_questionpool_id);
977  if ($title) {
978  $clone->setTitle($title);
979  }
980 
981  $clone->saveToDb();
982 
983  if ($this->gap_combinations_exists) {
984  $this->copyGapCombination($original_id, $clone->getId());
985  $clone->saveToDb();
986  }
987 
988  // copy question page content
989  $clone->copyPageOfQuestion($original_id);
990  // copy XHTML media objects
991  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
992 
993  $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
994 
995  return $clone->getId();
996  }
997 
998  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
999  {
1000  if ($this->id <= 0) {
1001  // The question has not been saved. It cannot be duplicated
1002  return;
1003  }
1004 
1005  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
1006 
1007  $sourceQuestionId = $this->id;
1008  $sourceParentId = $this->getObjId();
1009 
1010  // duplicate the question in database
1011  $clone = $this;
1012  $clone->id = -1;
1013 
1014  $clone->setObjId($targetParentId);
1015 
1016  if ($targetQuestionTitle) {
1017  $clone->setTitle($targetQuestionTitle);
1018  }
1019 
1020  $clone->saveToDb();
1021 
1022  if ($this->gap_combinations_exists) {
1023  $this->copyGapCombination($sourceQuestionId, $clone->getId());
1024  $clone->saveToDb();
1025  }
1026  // copy question page content
1027  $clone->copyPageOfQuestion($sourceQuestionId);
1028  // copy XHTML media objects
1029  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
1030 
1031  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
1032 
1033  return $clone->id;
1034  }
1035 
1036  public function copyGapCombination($orgID, $newID)
1037  {
1038  $assClozeGapCombinationObj = new assClozeGapCombination();
1039  $array = $assClozeGapCombinationObj->loadFromDb($orgID);
1040  $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array);
1041  }
1042 
1048  public function updateClozeTextFromGaps()
1049  {
1050  $output = $this->getClozeText();
1051  foreach ($this->getGaps() as $gap_index => $gap) {
1052  $answers = array();
1053  foreach ($gap->getItemsRaw() as $item) {
1054  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1055  }
1056  // fau: fixGapReplace - use replace function
1057  $output = $this->replaceFirstGap($output, "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]");
1058 // fau.
1059  }
1060  $output = str_replace("_gap]", "gap]", $output);
1061  $this->cloze_text = $output;
1062  }
1063 
1073  public function deleteAnswerText($gap_index, $answer_index)
1074  {
1075  if (array_key_exists($gap_index, $this->gaps)) {
1076  if ($this->gaps[$gap_index]->getItemCount() == 1) {
1077  // this is the last answer text => remove the gap
1078  $this->deleteGap($gap_index);
1079  } else {
1080  // remove the answer text
1081  $this->gaps[$gap_index]->deleteItem($answer_index);
1082  $this->updateClozeTextFromGaps();
1083  }
1084  }
1085  }
1086 
1095  public function deleteGap($gap_index)
1096  {
1097  if (array_key_exists($gap_index, $this->gaps)) {
1098  $output = $this->getClozeText();
1099  foreach ($this->getGaps() as $replace_gap_index => $gap) {
1100  $answers = array();
1101  foreach ($gap->getItemsRaw() as $item) {
1102  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1103  }
1104  if ($replace_gap_index == $gap_index) {
1105  // fau: fixGapReplace - use replace function
1106  $output = $this->replaceFirstGap($output, '');
1107 // fau.
1108  } else {
1109  // fau: fixGapReplace - use replace function
1110  $output = $this->replaceFirstGap($output, "[_gap]" . join(",", $answers) . "[/_gap]");
1111 // fau.
1112  }
1113  }
1114  $output = str_replace("_gap]", "gap]", $output);
1115  $this->cloze_text = $output;
1116  unset($this->gaps[$gap_index]);
1117  $this->gaps = array_values($this->gaps);
1118  }
1119  }
1120 
1130  public function getTextgapPoints($a_original, $a_entered, $max_points)
1131  {
1132  include_once "./Services/Utilities/classes/class.ilStr.php";
1133  $result = 0;
1134  $gaprating = $this->getTextgapRating();
1135  switch ($gaprating) {
1137  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) {
1138  $result = $max_points;
1139  }
1140  break;
1142  if (strcmp($a_original, $a_entered) == 0) {
1143  $result = $max_points;
1144  }
1145  break;
1147  if (levenshtein($a_original, $a_entered) <= 1) {
1148  $result = $max_points;
1149  }
1150  break;
1152  if (levenshtein($a_original, $a_entered) <= 2) {
1153  $result = $max_points;
1154  }
1155  break;
1157  if (levenshtein($a_original, $a_entered) <= 3) {
1158  $result = $max_points;
1159  }
1160  break;
1162  if (levenshtein($a_original, $a_entered) <= 4) {
1163  $result = $max_points;
1164  }
1165  break;
1167  if (levenshtein($a_original, $a_entered) <= 5) {
1168  $result = $max_points;
1169  }
1170  break;
1171  }
1172  return $result;
1173  }
1174 
1184  public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1185  {
1186  // fau: fixGapFormula - check entered value by evalMath
1187  // if( ! $this->checkForValidFormula($a_entered) )
1188  // {
1189  // return 0;
1190  // }
1191 
1192  include_once "./Services/Math/classes/class.EvalMath.php";
1193  $eval = new EvalMath();
1194  $eval->suppress_errors = true;
1195  $result = 0;
1196 
1197  if ($eval->e($a_entered) === false) {
1198  return 0;
1199  } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) {
1200  // fau.
1201  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1202  $result = $max_points;
1203  }
1204  } elseif ($eval->e($lowerBound) !== false) {
1205  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
1206  $result = $max_points;
1207  }
1208  } elseif ($eval->e($upperBound) !== false) {
1209  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1210  $result = $max_points;
1211  }
1212  } else {
1213  if ($eval->e($a_entered) == $eval->e($a_original)) {
1214  $result = $max_points;
1215  }
1216  }
1217  return $result;
1218  }
1219 
1224  public function checkForValidFormula($value)
1225  {
1226  return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1227  }
1238  public function calculateReachedPoints($active_id, $pass = null, $authorized = true, $returndetails = false)
1239  {
1240  global $DIC;
1241  $ilDB = $DIC['ilDB'];
1242 
1243  if (is_null($pass)) {
1244  $pass = $this->getSolutionMaxPass($active_id);
1245  }
1246 
1247  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1248  $user_result = array();
1249  while ($data = $ilDB->fetchAssoc($result)) {
1250  if (strcmp($data["value2"], "") != 0) {
1251  $user_result[$data["value1"]] = array(
1252  "gap_id" => $data["value1"],
1253  "value" => $data["value2"]
1254  );
1255  }
1256  }
1257 
1258  ksort($user_result); // this is required when identical scoring for same solutions is disabled
1259 
1260  if ($returndetails) {
1261  $detailed = array();
1262  $this->calculateReachedPointsForSolution($user_result, $detailed);
1263  return $detailed;
1264  }
1265 
1266  return $this->calculateReachedPointsForSolution($user_result);
1267  }
1268 
1269  protected function isValidNumericSubmitValue($submittedValue)
1270  {
1271  if (is_numeric($submittedValue)) {
1272  return true;
1273  }
1274 
1275  if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1276  return true;
1277  }
1278 
1279  return false;
1280  }
1281 
1282  public function validateSolutionSubmit()
1283  {
1284  foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) {
1285  $gap = $this->getGap($gapIndex);
1286 
1287  if ($gap->getType() != CLOZE_NUMERIC) {
1288  continue;
1289  }
1290 
1291  if (strlen($value) && !$this->isValidNumericSubmitValue($value)) {
1292  ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1293  return false;
1294  }
1295  }
1296 
1297  return true;
1298  }
1299 
1300  public function fetchSolutionSubmit($submit)
1301  {
1302  $solutionSubmit = array();
1303 
1304  foreach ($submit as $key => $value) {
1305  if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1306  $value = ilUtil::stripSlashes($value, false);
1307  if (strlen($value)) {
1308  $gap = $this->getGap($matches[1]);
1309  if (is_object($gap)) {
1310  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1311  if ($gap->getType() == CLOZE_NUMERIC && !is_numeric(str_replace(",", ".", $value))) {
1312  $value = null;
1313  } else if ($gap->getType() == CLOZE_NUMERIC) {
1314  $value = str_replace(",", ".", $value);
1315  }
1316  $solutionSubmit[trim($matches[1])] = $value;
1317  }
1318  }
1319  }
1320  }
1321  }
1322 
1323  return $solutionSubmit;
1324  }
1325 
1327  {
1328  $submit = $_POST;
1329  $solutionSubmit = array();
1330 
1331  foreach ($submit as $key => $value) {
1332  if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1333  $value = ilUtil::stripSlashes($value, false);
1334  if (strlen($value)) {
1335  $gap = $this->getGap($matches[1]);
1336  if (is_object($gap)) {
1337  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1338  if ($gap->getType() == CLOZE_NUMERIC) {
1339  $value = str_replace(",", ".", $value);
1340  }
1341  $solutionSubmit[trim($matches[1])] = $value;
1342  }
1343  }
1344  }
1345  }
1346  }
1347 
1348  return $solutionSubmit;
1349  }
1350 
1351  public function getSolutionSubmit()
1352  {
1353  return $this->fetchSolutionSubmit($_POST);
1354  }
1355 
1364  public function saveWorkingData($active_id, $pass = null, $authorized = true)
1365  {
1366  global $DIC;
1367  $ilDB = $DIC['ilDB'];
1368  $ilUser = $DIC['ilUser'];
1369  if (is_null($pass)) {
1370  include_once "./Modules/Test/classes/class.ilObjTest.php";
1371  $pass = ilObjTest::_getPass($active_id);
1372  }
1373 
1374  $entered_values = 0;
1375 
1376  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
1377  $this->removeCurrentSolution($active_id, $pass, $authorized);
1378 
1379  foreach ($this->getSolutionSubmit() as $val1 => $val2) {
1380  $value = trim(ilUtil::stripSlashes($val2, false));
1381  if (strlen($value)) {
1382  $gap = $this->getGap(trim(ilUtil::stripSlashes($val1)));
1383  if (is_object($gap)) {
1384  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1385  $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized);
1386  $entered_values++;
1387  }
1388  }
1389  }
1390  }
1391  });
1392 
1393  if ($entered_values) {
1394  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1396  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1397  }
1398  } else {
1399  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1401  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1402  }
1403  }
1404 
1405  return true;
1406  }
1407 
1414  public function getQuestionType()
1415  {
1416  return "assClozeTest";
1417  }
1418 
1426  public function getTextgapRating()
1427  {
1428  return $this->textgap_rating;
1429  }
1430 
1438  public function setTextgapRating($a_textgap_rating)
1439  {
1440  switch ($a_textgap_rating) {
1448  $this->textgap_rating = $a_textgap_rating;
1449  break;
1450  default:
1451  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1452  break;
1453  }
1454  }
1455 
1463  public function getIdenticalScoring()
1464  {
1465  return ($this->identical_scoring) ? 1 : 0;
1466  }
1467 
1475  public function setIdenticalScoring($a_identical_scoring)
1476  {
1477  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1478  }
1479 
1486  public function getAdditionalTableName()
1487  {
1488  return "qpl_qst_cloze";
1489  }
1490 
1497  public function getAnswerTableName()
1498  {
1499  return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1500  }
1501 
1508  public function setFixedTextLength($a_text_len)
1509  {
1510  $this->fixedTextLength = $a_text_len;
1511  }
1512 
1519  public function getFixedTextLength()
1520  {
1521  return $this->fixedTextLength;
1522  }
1523 
1532  public function getMaximumGapPoints($gap_index)
1533  {
1534  $points = 0;
1535  $gap_max_points = 0;
1536  if (array_key_exists($gap_index, $this->gaps)) {
1537  $gap = &$this->gaps[$gap_index];
1538  foreach ($gap->getItems($this->getShuffler()) as $answer) {
1539  if ($answer->getPoints() > $gap_max_points) {
1540  $gap_max_points = $answer->getPoints();
1541  }
1542  }
1543  $points += $gap_max_points;
1544  }
1545  return $points;
1546  }
1547 
1552  public function getRTETextWithMediaObjects()
1553  {
1554  return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1555  }
1556  public function getGapCombinationsExists()
1557  {
1559  }
1560 
1561  public function getGapCombinations()
1562  {
1563  return $this->gap_combinations;
1564  }
1565 
1566  public function setGapCombinationsExists($value)
1567  {
1568  $this->gap_combinations_exists = $value;
1569  }
1570 
1571  public function setGapCombinations($value)
1572  {
1573  $this->gap_combinations = $value;
1574  }
1575 
1579  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1580  {
1581  parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1582 
1583  $solution = $this->getSolutionValues($active_id, $pass);
1584  $i = 1;
1585  foreach ($this->getGaps() as $gap_index => $gap) {
1586  $worksheet->setCell($startrow + $i, 0, $this->lng->txt("gap") . " $i");
1587  $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1588  $checked = false;
1589  foreach ($solution as $solutionvalue) {
1590  if ($gap_index == $solutionvalue["value1"]) {
1591  $string_escaping_org_value = $worksheet->getStringEscaping();
1592  try {
1593  $worksheet->setStringEscaping(false);
1594 
1595  switch ($gap->getType()) {
1596  case CLOZE_SELECT:
1597  $worksheet->setCell($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1598  break;
1599  case CLOZE_NUMERIC:
1600  case CLOZE_TEXT:
1601  $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1602  break;
1603  }
1604  } finally {
1605  $worksheet->setStringEscaping($string_escaping_org_value);
1606  }
1607  }
1608  }
1609  $i++;
1610  }
1611 
1612  return $startrow + $i + 1;
1613  }
1614 
1619  {
1620  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1621  //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1622  $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1623  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1624  }
1625 
1629  public function toJSON()
1630  {
1631  include_once("./Services/RTE/classes/class.ilRTE.php");
1632  $result = array();
1633  $result['id'] = (int) $this->getId();
1634  $result['type'] = (string) $this->getQuestionType();
1635  $result['title'] = (string) $this->getTitle();
1636  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1637  $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1638  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1639  $result['shuffle'] = (bool) $this->getShuffle();
1640  $result['feedback'] = array(
1641  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1642  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1643  );
1644 
1645  $gaps = array();
1646  foreach ($this->getGaps() as $key => $gap) {
1647  $items = array();
1648  foreach ($gap->getItems($this->getShuffler()) as $item) {
1649  $jitem = array();
1650  $jitem['points'] = $item->getPoints();
1651  $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1652  $jitem['order'] = $item->getOrder();
1653  if ($gap->getType() == CLOZE_NUMERIC) {
1654  $jitem['lowerbound'] = $item->getLowerBound();
1655  $jitem['upperbound'] = $item->getUpperBound();
1656  } else {
1657  $jitem['value'] = trim($jitem['value']);
1658  }
1659  array_push($items, $jitem);
1660  }
1661 
1662  if ($gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC)) {
1663  $jgap['size'] = $gap->getGapSize();
1664  }
1665 
1666  $jgap['shuffle'] = $gap->getShuffle();
1667  $jgap['type'] = $gap->getType();
1668  $jgap['item'] = $items;
1669 
1670  array_push($gaps, $jgap);
1671  }
1672  $result['gaps'] = $gaps;
1673  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1674  $result['mobs'] = $mobs;
1675  return json_encode($result);
1676  }
1677 
1686  public function getOperators($expression)
1687  {
1688  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1690  }
1691 
1696  public function getExpressionTypes()
1697  {
1698  return array(
1704  );
1705  }
1706 
1715  public function getUserQuestionResult($active_id, $pass)
1716  {
1718  global $DIC;
1719  $ilDB = $DIC['ilDB'];
1720  $result = new ilUserQuestionResult($this, $active_id, $pass);
1721 
1722  $maxStep = $this->lookupMaxStep($active_id, $pass);
1723 
1724  if ($maxStep !== null) {
1725  $data = $ilDB->queryF(
1726  "
1727  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1728  FROM tst_solutions sol
1729  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1730  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1731  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1732  ",
1733  array("integer", "integer", "integer","integer"),
1734  array($active_id, $pass, $this->getId(), $maxStep)
1735  );
1736  } else {
1737  $data = $ilDB->queryF(
1738  "
1739  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1740  FROM tst_solutions sol
1741  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1742  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1743  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1744  ",
1745  array("integer", "integer", "integer"),
1746  array($active_id, $pass, $this->getId())
1747  );
1748  }
1749 
1750  while ($row = $ilDB->fetchAssoc($data)) {
1751  if ($row["cloze_type"] == 1) {
1752  $row["value2"]++;
1753  }
1754  $result->addKeyValue($row["val"], $row["value2"]);
1755  }
1756 
1757  $points = $this->calculateReachedPoints($active_id, $pass);
1758  $max_points = $this->getMaximumPoints();
1759 
1760  $result->setReachedPercentage(($points / $max_points) * 100);
1761 
1762  return $result;
1763  }
1764 
1773  public function getAvailableAnswerOptions($index = null)
1774  {
1775  if ($index !== null) {
1776  return $this->getGap($index);
1777  } else {
1778  return $this->getGaps();
1779  }
1780  }
1781 
1782  public function calculateCombinationResult($user_result)
1783  {
1784  $points = 0;
1785 
1786  $assClozeGapCombinationObj = new assClozeGapCombination();
1787 
1788  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1789  $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1790  $gap_answers = array();
1791  $gap_used_in_combination = array();
1792  foreach ($user_result as $user_result_build_list) {
1793  if (is_array($user_result_build_list)) {
1794  $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1795  }
1796  }
1797 
1798  foreach ($combinations_for_question as $combination) {
1799  foreach ($combination as $row_key => $row_answers) {
1800  $combination_fulfilled = true;
1801  $points_for_combination = $row_answers['points'];
1802  foreach ($row_answers as $gap_key => $combination_gap_answer) {
1803  if ($gap_key !== 'points') {
1804  $gap_used_in_combination[$gap_key] = $gap_key;
1805  }
1806  if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1807  switch ($combination_gap_answer['type']) {
1808  case CLOZE_TEXT:
1809  $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1810  if ($is_text_gap_correct != 1) {
1811  $combination_fulfilled = false;
1812  }
1813  break;
1814  case CLOZE_SELECT:
1815  $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1816  $answertext = $answer->getAnswertext();
1817  if ($answertext != $combination_gap_answer['answer']) {
1818  $combination_fulfilled = false;
1819  }
1820  break;
1821  case CLOZE_NUMERIC:
1822  $answer = $this->gaps[$gap_key]->getItem(0);
1823  if ($combination_gap_answer['answer'] != 'out_of_bound') {
1824  $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1825  if ($is_numeric_gap_correct != 1) {
1826  $combination_fulfilled = false;
1827  }
1828  } else {
1829  $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1830  if ($wrong_is_the_new_right == 1) {
1831  $combination_fulfilled = false;
1832  }
1833  }
1834  break;
1835  }
1836  } else {
1837  if ($gap_key !== 'points') {
1838  $combination_fulfilled = false;
1839  }
1840  }
1841  }
1842  if ($combination_fulfilled) {
1843  $points += $points_for_combination;
1844  }
1845  }
1846  }
1847  }
1848  return array($points, $gap_used_in_combination);
1849  }
1855  protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1856  {
1857  if ($detailed === null) {
1858  $detailed = array();
1859  }
1860 
1861  $assClozeGapCombinationObj = new assClozeGapCombination();
1862  $combinations[1] = array();
1863  if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1864  $combinations = $this->calculateCombinationResult($user_result);
1865  $points = $combinations[0];
1866  }
1867  $counter = 0;
1868  $solution_values_text = array(); // for identical scoring checks
1869  $solution_values_select = array(); // for identical scoring checks
1870  $solution_values_numeric = array(); // for identical scoring checks
1871  foreach ($user_result as $gap_id => $value) {
1872  if (is_string($value)) {
1873  $value = array("value" => $value);
1874  }
1875 
1876  if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1877  switch ($this->gaps[$gap_id]->getType()) {
1878  case CLOZE_TEXT:
1879  $gappoints = 0;
1880  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1881  $answer = $this->gaps[$gap_id]->getItem($order);
1882  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1883  if ($gotpoints > $gappoints) {
1884  $gappoints = $gotpoints;
1885  }
1886  }
1887  if (!$this->getIdenticalScoring()) {
1888  // check if the same solution text was already entered
1889  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0)) {
1890  $gappoints = 0;
1891  }
1892  }
1893  $points += $gappoints;
1894  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1895  array_push($solution_values_text, $value["value"]);
1896  break;
1897  case CLOZE_NUMERIC:
1898  $gappoints = 0;
1899  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1900  $answer = $this->gaps[$gap_id]->getItem($order);
1901  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1902  if ($gotpoints > $gappoints) {
1903  $gappoints = $gotpoints;
1904  }
1905  }
1906  if (!$this->getIdenticalScoring()) {
1907  // check if the same solution value was already entered
1908  include_once "./Services/Math/classes/class.EvalMath.php";
1909  $eval = new EvalMath();
1910  $eval->suppress_errors = true;
1911  $found_value = false;
1912  foreach ($solution_values_numeric as $solval) {
1913  if ($eval->e($solval) == $eval->e($value["value"])) {
1914  $found_value = true;
1915  }
1916  }
1917  if ($found_value && ($gappoints > 0)) {
1918  $gappoints = 0;
1919  }
1920  }
1921  $points += $gappoints;
1922  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1923  array_push($solution_values_numeric, $value["value"]);
1924  break;
1925  case CLOZE_SELECT:
1926  if ($value["value"] >= 0) {
1927  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1928  $answer = $this->gaps[$gap_id]->getItem($order);
1929  if ($value["value"] == $answer->getOrder()) {
1930  $answerpoints = $answer->getPoints();
1931  if (!$this->getIdenticalScoring()) {
1932  // check if the same solution value was already entered
1933  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0)) {
1934  $answerpoints = 0;
1935  }
1936  }
1937  $points += $answerpoints;
1938  $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? true : false, "positive" => ($answerpoints > 0) ? true : false);
1939  array_push($solution_values_select, $answer->getAnswertext());
1940  }
1941  }
1942  }
1943  break;
1944  }
1945  }
1946  }
1947 
1948  return $points;
1949  }
1950 
1952  {
1953  $userSolution = array();
1954 
1955  foreach ($previewSession->getParticipantsSolution() as $key => $val) {
1956  $userSolution[$key] = array('gap_id' => $key, 'value' => $val);
1957  }
1958 
1959  $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
1960  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
1961 
1962  return $this->ensureNonNegativePoints($reachedPoints);
1963  }
1964 
1965  public function fetchAnswerValueForGap($userSolution, $gapIndex)
1966  {
1967  $answerValue = '';
1968 
1969  foreach ($userSolution as $value1 => $value2) {
1970  if ($value1 == $gapIndex) {
1971  $answerValue = $value2;
1972  break;
1973  }
1974  }
1975 
1976  return $answerValue;
1977  }
1978 
1979  public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
1980  {
1981  $gap = $this->getGap($qIndex);
1982 
1983  if ($gap->getType() != CLOZE_TEXT) {
1984  return false;
1985  }
1986 
1987  foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) {
1988  if ($item->getAnswertext() == $answerOptionValue) {
1989  return false;
1990  }
1991  }
1992 
1993  return true;
1994  }
1995 
1996  public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
1997  {
1998  $gap = $this->getGap($qIndex); /* @var assClozeGap $gap */
1999 
2000  $item = new assAnswerCloze($answerOptionValue, $points);
2001  $item->setOrder($gap->getItemCount());
2002 
2003  $gap->addItem($item);
2004  }
2005 
2006  public function savePartial()
2007  {
2008  return true;
2009  }
2010 }
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.
$data
Definition: storeScorm.php:23
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
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)
$index
Definition: metadata.php:128
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
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.
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.
__construct(Container $dic, ilPlugin $plugin)
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)
$DIC
Definition: xapitoken.php:46
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass...
setLifecycle(ilAssQuestionLifecycle $lifecycle)
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.
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)
$i
Definition: metadata.php:24
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
const TEXTGAP_RATING_CASEINSENSITIVE