ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.assClozeTest.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6 require_once './Modules/TestQuestionPool/classes/class.assClozeGapCombination.php';
7 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
8 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
9 require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
10 require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
11 
24 {
32  var $gaps;
33 
42 
43 
45 
54 
62  var $end_tag;
63 
75 
86 
93 
94  public $cloze_text;
95 
109  function __construct(
110  $title = "",
111  $comment = "",
112  $author = "",
113  $owner = -1,
114  $question = ""
115  )
116  {
117  parent::__construct($title, $comment, $author, $owner, $question);
118  $this->start_tag = "[gap]";
119  $this->end_tag = "[/gap]";
120  $this->gaps = array();
121  $this->setQuestion($question); // @TODO: Should this be $question?? See setter for why this is not trivial.
122  $this->fixedTextLength = "";
123  $this->identical_scoring = 1;
124  $this->gap_combinations_exists = false;
125  $this->gap_combinations = array();
126  }
127 
133  public function isComplete()
134  {
135  if (strlen($this->getTitle())
136  && $this->getAuthor()
137  && $this->getClozeText()
138  && count($this->getGaps())
139  && $this->getMaximumPoints() > 0)
140  {
141  return true;
142  }
143  return false;
144  }
145 
153  public function cleanQuestiontext($text)
154  {
155  $text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
156  $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
157  $text = str_replace("</gap>", "[/gap]", $text);
158  return $text;
159  }
160 
167  public function loadFromDb($question_id)
168  {
169  global $ilDB;
170  $result = $ilDB->queryF("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",
171  array("integer"),
172  array($question_id)
173  );
174  if ($result->numRows() == 1)
175  {
176  $data = $ilDB->fetchAssoc($result);
177  $this->setId($question_id);
178  $this->setNrOfTries($data['nr_of_tries']);
179  $this->setObjId($data["obj_fi"]);
180  $this->setTitle($data["title"]);
181  $this->setComment($data["description"]);
182  $this->setOriginalId($data["original_id"]);
183  $this->setAuthor($data["author"]);
184  $this->setPoints($data["points"]);
185  $this->setOwner($data["owner"]);
186  $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
187  $this->setClozeText($data['cloze_text']);
188  $this->setFixedTextLength($data["fixed_textlen"]);
189  $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
190  // replacement of old syntax with new syntax
191  include_once("./Services/RTE/classes/class.ilRTE.php");
192  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
193  $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
194  $this->setTextgapRating($data["textgap_rating"]);
195  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
196 
197  try
198  {
199  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
200  }
202  {
203  }
204 
205  // open the cloze gaps with all answers
206  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
207  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
208  $result = $ilDB->queryF("SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
209  array("integer"),
210  array($question_id)
211  );
212  if ($result->numRows() > 0)
213  {
214  $this->gaps = array();
215  while ($data = $ilDB->fetchAssoc($result))
216  {
217  switch ($data["cloze_type"])
218  {
219  case CLOZE_TEXT:
220  if (!array_key_exists($data["gap_id"], $this->gaps))
221  {
222  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
223  }
224  $answer = new assAnswerCloze(
225  $data["answertext"],
226  $data["points"],
227  $data["aorder"]
228  );
229  $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
230 
231  $this->gaps[$data["gap_id"]]->addItem($answer);
232  break;
233  case CLOZE_SELECT:
234  if (!array_key_exists($data["gap_id"], $this->gaps))
235  {
236  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
237  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
238  }
239  $answer = new assAnswerCloze(
240  $data["answertext"],
241  $data["points"],
242  $data["aorder"]
243  );
244  $this->gaps[$data["gap_id"]]->addItem($answer);
245  break;
246  case CLOZE_NUMERIC:
247  if (!array_key_exists($data["gap_id"], $this->gaps))
248  {
249  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
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  $answer->setLowerBound($data["lowerlimit"]);
258  $answer->setUpperBound($data["upperlimit"]);
259  $this->gaps[$data["gap_id"]]->addItem($answer);
260  break;
261  }
262  }
263  }
264  }
265  $assClozeGapCombinationObj = new assClozeGapCombination();
266  $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
267  if(count($check_for_gap_combinations) != 0)
268  {
269  $this->setGapCombinationsExists(true);
270  $this->setGapCombinations($check_for_gap_combinations);
271  }
272  parent::loadFromDb($question_id);
273  }
274 
275  #region Save question to db
276 
286  public function saveToDb($original_id = "")
287  {
291 
292  parent::saveToDb($original_id);
293  }
294 
298  public function saveAnswerSpecificDataToDb()
299  {
300  global $ilDB;
301 
302  $ilDB->manipulateF( "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
303  array( "integer" ),
304  array( $this->getId() )
305  );
306 
307  foreach ($this->gaps as $key => $gap)
308  {
309  $this->saveClozeGapItemsToDb( $gap, $key );
310  }
311  }
312 
319  {
320  global $ilDB;
321 
322  $ilDB->manipulateF( "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
323  array( "integer" ),
324  array( $this->getId() )
325  );
326 
327  $ilDB->manipulateF( "INSERT INTO " . $this->getAdditionalTableName()
328  . " (question_fi, textgap_rating, identical_scoring, fixed_textlen, cloze_text) VALUES (%s, %s, %s, %s, %s)",
329  array(
330  "integer",
331  "text",
332  "text",
333  "integer",
334  "text"
335  ),
336  array(
337  $this->getId(),
338  $this->getTextgapRating(),
339  $this->getIdenticalScoring(),
340  $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL,
342  )
343  );
344  }
345 
352  protected function saveClozeGapItemsToDb($gap, $key)
353  {
354  global $ilDB;
355  foreach ($gap->getItems($this->getShuffler()) as $item)
356  {
357  $query = "";
358  $next_id = $ilDB->nextId( 'qpl_a_cloze' );
359  switch ($gap->getType())
360  {
361  case CLOZE_TEXT:
362  $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap );
363  break;
364  case CLOZE_SELECT:
365  $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap );
366  break;
367  case CLOZE_NUMERIC:
368  $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap );
369  break;
370  }
371  }
372  }
373 
382  protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
383  {
384  global $ilDB;
385  $ilDB->manipulateF( "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)",
386  array(
387  "integer",
388  "integer",
389  "integer",
390  "text",
391  "float",
392  "integer",
393  "text",
394  "integer"
395  ),
396  array(
397  $next_id,
398  $this->getId(),
399  $key,
400  strlen( $item->getAnswertext() ) ? $item->getAnswertext() : "",
401  $item->getPoints(),
402  $item->getOrder(),
403  $gap->getType(),
404  (int)$gap->getGapSize()
405  )
406  );
407  }
408 
417  protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
418  {
419  global $ilDB;
420  $ilDB->manipulateF( "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)",
421  array(
422  "integer",
423  "integer",
424  "integer",
425  "text",
426  "float",
427  "integer",
428  "text",
429  "text"
430  ),
431  array(
432  $next_id,
433  $this->getId(),
434  $key,
435  strlen( $item->getAnswertext() ) ? $item->getAnswertext() : "",
436  $item->getPoints(),
437  $item->getOrder(),
438  $gap->getType(),
439  ($gap->getShuffle()) ? "1" : "0"
440  )
441  );
442  }
443 
452  protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
453  {
454  global $ilDB;
455 
456  include_once "./Services/Math/classes/class.EvalMath.php";
457  $eval = new EvalMath();
458  $eval->suppress_errors = TRUE;
459  $ilDB->manipulateF( "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)",
460  array(
461  "integer",
462  "integer",
463  "integer",
464  "text",
465  "float",
466  "integer",
467  "text",
468  "text",
469  "text",
470  "integer"
471  ),
472  array(
473  $next_id,
474  $this->getId(),
475  $key,
476  strlen( $item->getAnswertext() ) ? $item->getAnswertext() : "",
477  $item->getPoints(),
478  $item->getOrder(),
479  $gap->getType(),
480  ($eval->e( $item->getLowerBound() !== FALSE ) && strlen( $item->getLowerBound()
481  ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
482  ($eval->e( $item->getUpperBound() !== FALSE ) && strlen( $item->getUpperBound()
483  ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
484  (int)$gap->getGapSize()
485  )
486  );
487  }
488 
489 
490 
491  #endregion Save question to db
492 
499  function getGaps()
500  {
501  return $this->gaps;
502  }
503 
504 
511  function flushGaps()
512  {
513  $this->gaps = array();
514  }
515 
525  function setClozeText($cloze_text = "")
526  {
527  $this->gaps = array();
529  $this->cloze_text = $cloze_text;
531  }
532 
534  {
535  $this->cloze_text = $cloze_text;
536  }
537 
545  function getClozeText()
546  {
547  return $this->cloze_text;
548  }
549 
557  function getStartTag()
558  {
559  return $this->start_tag;
560  }
561 
569  function setStartTag($start_tag = "[gap]")
570  {
571  $this->start_tag = $start_tag;
572  }
573 
581  function getEndTag()
582  {
583  return $this->end_tag;
584  }
585 
593  function setEndTag($end_tag = "[/gap]")
594  {
595  $this->end_tag = $end_tag;
596  }
597 
605  {
606  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
607  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
608  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
609  preg_match_all($search_pattern, $this->getClozeText(), $found);
610  $this->gaps = array();
611  if (count($found[0]))
612  {
613  foreach ($found[1] as $gap_index => $answers)
614  {
615  // create text gaps by default
616  $gap = new assClozeGap(CLOZE_TEXT);
617  $textparams = preg_split("/(?<!\\\\),/", $answers);
618  foreach ($textparams as $key => $value)
619  {
620  $answer = new assAnswerCloze($value, 0, $key);
621  $gap->addItem($answer);
622  }
623  $this->gaps[$gap_index] = $gap;
624  }
625  }
626  }
627 
633  function setGapType($gap_index, $gap_type)
634  {
635  if (array_key_exists($gap_index, $this->gaps))
636  {
637  $this->gaps[$gap_index]->setType($gap_type);
638  }
639  }
640 
650  function setGapShuffle($gap_index = 0, $shuffle = 1)
651  {
652  if (array_key_exists($gap_index, $this->gaps))
653  {
654  $this->gaps[$gap_index]->setShuffle($shuffle);
655  }
656  }
657 
664  function clearGapAnswers()
665  {
666  foreach ($this->gaps as $gap_index => $gap)
667  {
668  $this->gaps[$gap_index]->clearItems();
669  }
670  }
671 
679  function getGapCount()
680  {
681  if (is_array($this->gaps))
682  {
683  return count($this->gaps);
684  }
685  else
686  {
687  return 0;
688  }
689  }
690 
701  function addGapAnswer($gap_index, $order, $answer)
702  {
703  if (array_key_exists($gap_index, $this->gaps))
704  {
705  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC)
706  {
707  // only allow notation with "." for real numbers
708  $answer = str_replace(",", ".", $answer);
709  }
710  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
711  }
712  }
713 
722  function getGap($gap_index = 0)
723  {
724  if (array_key_exists($gap_index, $this->gaps))
725  {
726  return $this->gaps[$gap_index];
727  }
728  else
729  {
730  return NULL;
731  }
732  }
733 
734  public function setGapSize($gap_index, $order, $size)
735  {
736  if (array_key_exists($gap_index, $this->gaps))
737  {
738  $this->gaps[$gap_index]->setGapSize( $size);
739  }
740  }
741 
752  function setGapAnswerPoints($gap_index, $order, $points)
753  {
754  if (array_key_exists($gap_index, $this->gaps))
755  {
756  $this->gaps[$gap_index]->setItemPoints($order, $points);
757  }
758  }
759 
768  function addGapText($gap_index)
769  {
770  if (array_key_exists($gap_index, $this->gaps))
771  {
772  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
773  $answer = new assAnswerCloze(
774  "",
775  0,
776  $this->gaps[$gap_index]->getItemCount()
777  );
778  $this->gaps[$gap_index]->addItem($answer);
779  }
780  }
781 
790  function addGapAtIndex($gap, $index)
791  {
792  $this->gaps[$index] = $gap;
793  }
794 
805  function setGapAnswerLowerBound($gap_index, $order, $bound)
806  {
807  if (array_key_exists($gap_index, $this->gaps))
808  {
809  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
810  }
811  }
812 
823  function setGapAnswerUpperBound($gap_index, $order, $bound)
824  {
825  if (array_key_exists($gap_index, $this->gaps))
826  {
827  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
828  }
829  }
830 
837  function getMaximumPoints()
838  {
839  $assClozeGapCombinationObj = new assClozeGapCombination();
840  $points = 0;
841  $gaps_used_in_combination = array();
842  if($assClozeGapCombinationObj->combinationExistsForQid($this->getId()))
843  {
844  $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
845  $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
846  }
847  foreach ($this->gaps as $gap_index => $gap)
848  {
849  if(! array_key_exists($gap_index, $gaps_used_in_combination))
850  {
851  if ($gap->getType() == CLOZE_TEXT)
852  {
853  $gap_max_points = 0;
854  foreach ($gap->getItems($this->getShuffler()) as $item)
855  {
856  if ($item->getPoints() > $gap_max_points)
857  {
858  $gap_max_points = $item->getPoints();
859  }
860  }
861  $points += $gap_max_points;
862  }
863  else if ($gap->getType() == CLOZE_SELECT)
864  {
865  $srpoints = 0;
866  foreach ($gap->getItems($this->getShuffler()) as $item)
867  {
868  if ($item->getPoints() > $srpoints)
869  {
870  $srpoints = $item->getPoints();
871  }
872  }
873  $points += $srpoints;
874  }
875  else if ($gap->getType() == CLOZE_NUMERIC)
876  {
877  $numpoints = 0;
878  foreach ($gap->getItems($this->getShuffler()) as $item)
879  {
880  if ($item->getPoints() > $numpoints)
881  {
882  $numpoints = $item->getPoints();
883  }
884  }
885  $points += $numpoints;
886  }
887  }
888  }
889 
890  return $points;
891  }
892 
898  function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
899  {
900  if ($this->id <= 0)
901  {
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  {
916  $clone->setObjId($testObjId);
917  }
918 
919  if ($title)
920  {
921  $clone->setTitle($title);
922  }
923  if ($author)
924  {
925  $clone->setAuthor($author);
926  }
927  if ($owner)
928  {
929  $clone->setOwner($owner);
930  }
931  if ($for_test)
932  {
933  $clone->saveToDb($original_id);
934  }
935  else
936  {
937  $clone->saveToDb();
938  }
939  if($this->gap_combinations_exists)
940  {
941  $this->copyGapCombination($this_id, $clone->getId());
942  }
943  if ($for_test)
944  {
945  $clone->saveToDb($original_id);
946  }
947  else
948  {
949  $clone->saveToDb();
950  }
951  // copy question page content
952  $clone->copyPageOfQuestion($this_id);
953  // copy XHTML media objects
954  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
955 
956  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
957 
958  return $clone->getId();
959  }
960 
966  function copyObject($target_questionpool_id, $title = "")
967  {
968  if ($this->getId() <= 0)
969  {
970  // The question has not been saved. It cannot be duplicated
971  return;
972  }
973 
974  $thisId = $this->getId();
975  $thisObjId = $this->getObjId();
976 
977  $clone = $this;
978  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
980  $clone->id = -1;
981  $clone->setObjId($target_questionpool_id);
982  if ($title)
983  {
984  $clone->setTitle($title);
985  }
986 
987  $clone->saveToDb();
988 
989  if($this->gap_combinations_exists)
990  {
991  $this->copyGapCombination($original_id, $clone->getId());
992  $clone->saveToDb();
993  }
994 
995  // copy question page content
996  $clone->copyPageOfQuestion($original_id);
997  // copy XHTML media objects
998  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
999 
1000  $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
1001 
1002  return $clone->getId();
1003  }
1004 
1005  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
1006  {
1007  if ($this->id <= 0)
1008  {
1009  // The question has not been saved. It cannot be duplicated
1010  return;
1011  }
1012 
1013  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
1014 
1015  $sourceQuestionId = $this->id;
1016  $sourceParentId = $this->getObjId();
1017 
1018  // duplicate the question in database
1019  $clone = $this;
1020  $clone->id = -1;
1021 
1022  $clone->setObjId($targetParentId);
1023 
1024  if ($targetQuestionTitle)
1025  {
1026  $clone->setTitle($targetQuestionTitle);
1027  }
1028 
1029  $clone->saveToDb();
1030 
1031  if($this->gap_combinations_exists)
1032  {
1033  $this->copyGapCombination($sourceQuestionId, $clone->getId());
1034  }
1035  // copy question page content
1036  $clone->copyPageOfQuestion($sourceQuestionId);
1037  // copy XHTML media objects
1038  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
1039 
1040  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
1041 
1042  return $clone->id;
1043  }
1044 
1045  function copyGapCombination($orgID, $newID)
1046  {
1047  $assClozeGapCombinationObj = new assClozeGapCombination();
1048  $array = $assClozeGapCombinationObj->loadFromDb($orgID);
1049  $assClozeGapCombinationObj->importGapCombinationToDb($newID , $array);
1050  }
1051 
1058  {
1059  $output = $this->getClozeText();
1060  foreach ($this->getGaps() as $gap_index => $gap)
1061  {
1062  $answers = array();
1063  foreach ($gap->getItemsRaw() as $item)
1064  {
1065  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1066  }
1067  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]", $output, 1);
1068  }
1069  $output = str_replace("_gap]", "gap]", $output);
1070  $this->cloze_text = $output;
1071  }
1072 
1082  function deleteAnswerText($gap_index, $answer_index)
1083  {
1084  if (array_key_exists($gap_index, $this->gaps))
1085  {
1086  if ($this->gaps[$gap_index]->getItemCount() == 1)
1087  {
1088  // this is the last answer text => remove the gap
1089  $this->deleteGap($gap_index);
1090  }
1091  else
1092  {
1093  // remove the answer text
1094  $this->gaps[$gap_index]->deleteItem($answer_index);
1095  $this->updateClozeTextFromGaps();
1096  }
1097  }
1098  }
1099 
1108  function deleteGap($gap_index)
1109  {
1110  if (array_key_exists($gap_index, $this->gaps))
1111  {
1112  $output = $this->getClozeText();
1113  foreach ($this->getGaps() as $replace_gap_index => $gap)
1114  {
1115  $answers = array();
1116  foreach ($gap->getItemsRaw() as $item)
1117  {
1118  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1119  }
1120  if ($replace_gap_index == $gap_index)
1121  {
1122  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1);
1123  }
1124  else
1125  {
1126  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1);
1127  }
1128  }
1129  $output = str_replace("_gap]", "gap]", $output);
1130  $this->cloze_text = $output;
1131  unset($this->gaps[$gap_index]);
1132  $this->gaps = array_values($this->gaps);
1133  }
1134  }
1135 
1145  function getTextgapPoints($a_original, $a_entered, $max_points)
1146  {
1147  include_once "./Services/Utilities/classes/class.ilStr.php";
1148  $result = 0;
1149  $gaprating = $this->getTextgapRating();
1150  switch ($gaprating)
1151  {
1153  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) $result = $max_points;
1154  break;
1156  if (strcmp($a_original, $a_entered) == 0) $result = $max_points;
1157  break;
1159  if (levenshtein($a_original, $a_entered) <= 1) $result = $max_points;
1160  break;
1162  if (levenshtein($a_original, $a_entered) <= 2) $result = $max_points;
1163  break;
1165  if (levenshtein($a_original, $a_entered) <= 3) $result = $max_points;
1166  break;
1168  if (levenshtein($a_original, $a_entered) <= 4) $result = $max_points;
1169  break;
1171  if (levenshtein($a_original, $a_entered) <= 5) $result = $max_points;
1172  break;
1173  }
1174  return $result;
1175  }
1176 
1186  function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1187  {
1188 // fau: fixGapFormula - check entered value by evalMath
1189 // if( ! $this->checkForValidFormula($a_entered) )
1190 // {
1191 // return 0;
1192 // }
1193 
1194  include_once "./Services/Math/classes/class.EvalMath.php";
1195  $eval = new EvalMath();
1196  $eval->suppress_errors = TRUE;
1197  $result = 0;
1198 
1199  if ($eval->e($a_entered) === FALSE)
1200  {
1201  return 0;
1202  }
1203  elseif (($eval->e($lowerBound) !== FALSE) && ($eval->e($upperBound) !== FALSE))
1204 // fau.
1205  {
1206  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1207  }
1208  else if ($eval->e($lowerBound) !== FALSE)
1209  {
1210  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) $result = $max_points;
1211  }
1212  else if ($eval->e($upperBound) !== FALSE)
1213  {
1214  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1215  }
1216  else
1217  {
1218  if ($eval->e($a_entered) == $eval->e($a_original)) $result = $max_points;
1219  }
1220  return $result;
1221  }
1222 
1227  public function checkForValidFormula($value)
1228  {
1229  return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1230  }
1241  public function calculateReachedPoints($active_id, $pass = NULL, $authorized = true, $returndetails = FALSE)
1242  {
1243  global $ilDB;
1244 
1245  if (is_null($pass))
1246  {
1247  $pass = $this->getSolutionMaxPass($active_id);
1248  }
1249 
1250  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1251  $user_result = array();
1252  while ($data = $ilDB->fetchAssoc($result))
1253  {
1254  if (strcmp($data["value2"], "") != 0)
1255  {
1256  $user_result[$data["value1"]] = array(
1257  "gap_id" => $data["value1"],
1258  "value" => $data["value2"]
1259  );
1260  }
1261  }
1262 
1263  ksort($user_result); // this is required when identical scoring for same solutions is disabled
1264 
1265  if ($returndetails)
1266  {
1267  $detailed = array();
1268  $this->calculateReachedPointsForSolution($user_result, $detailed);
1269  return $detailed;
1270  }
1271 
1272  return $this->calculateReachedPointsForSolution($user_result);
1273  }
1274 
1275  protected function isValidNumericSubmitValue($submittedValue)
1276  {
1277  if( is_numeric($submittedValue) )
1278  {
1279  return true;
1280  }
1281 
1282  if( preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue) )
1283  {
1284  return true;
1285  }
1286 
1287  return false;
1288  }
1289 
1290  public function validateSolutionSubmit()
1291  {
1292  foreach($this->getSolutionSubmit() as $gapIndex => $value)
1293  {
1294  $gap = $this->getGap($gapIndex);
1295 
1296  if($gap->getType() != CLOZE_NUMERIC)
1297  {
1298  continue;
1299  }
1300 
1301  if( strlen($value) && !$this->isValidNumericSubmitValue($value) )
1302  {
1303  ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1304  return false;
1305  }
1306  }
1307 
1308  return true;
1309  }
1310 
1311  public function fetchSolutionSubmit($submit)
1312  {
1313  $solutionSubmit = array();
1314 
1315  foreach ($submit as $key => $value)
1316  {
1317  if (preg_match("/^gap_(\d+)/", $key, $matches))
1318  {
1319  $value = ilUtil::stripSlashes($value, FALSE);
1320  if (strlen($value))
1321  {
1322  $gap = $this->getGap($matches[1]);
1323  if (is_object($gap))
1324  {
1325  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1)))
1326  {
1327  if ($gap->getType() == CLOZE_NUMERIC)
1328  {
1329  $value = str_replace(",", ".", $value);
1330  }
1331  $solutionSubmit[trim($matches[1])] = $value;
1332  }
1333  }
1334  }
1335  }
1336  }
1337 
1338  return $solutionSubmit;
1339 
1340  }
1341 
1342  public function getSolutionSubmit()
1343  {
1344  return $this->fetchSolutionSubmit($_POST);
1345  }
1346 
1355  public function saveWorkingData($active_id, $pass = NULL, $authorized = true)
1356  {
1357  global $ilDB;
1358  global $ilUser;
1359  if (is_null($pass))
1360  {
1361  include_once "./Modules/Test/classes/class.ilObjTest.php";
1362  $pass = ilObjTest::_getPass($active_id);
1363  }
1364 
1365  $entered_values = 0;
1366 
1367  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function() use (&$entered_values, $active_id, $pass, $authorized) {
1368 
1369  $this->removeCurrentSolution($active_id, $pass, $authorized);
1370 
1371  foreach($this->getSolutionSubmit() as $val1 => $val2)
1372  {
1373  $value = trim(ilUtil::stripSlashes($val2, FALSE));
1374  if (strlen($value))
1375  {
1376  $gap = $this->getGap(trim(ilUtil::stripSlashes($val1)));
1377  if (is_object($gap))
1378  {
1379  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1)))
1380  {
1381  $this->saveCurrentSolution($active_id,$pass, $val1, $value, $authorized);
1382  $entered_values++;
1383  }
1384  }
1385  }
1386  }
1387 
1388  });
1389 
1390  if ($entered_values)
1391  {
1392  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1394  {
1395  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1396  }
1397  }
1398  else
1399  {
1400  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1402  {
1403  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1404  }
1405  }
1406 
1407  return TRUE;
1408  }
1409 
1413  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
1414  {
1415  // nothing to rework!
1416  }
1417 
1424  function getQuestionType()
1425  {
1426  return "assClozeTest";
1427  }
1428 
1436  function getTextgapRating()
1437  {
1438  return $this->textgap_rating;
1439  }
1440 
1448  function setTextgapRating($a_textgap_rating)
1449  {
1450  switch ($a_textgap_rating)
1451  {
1459  $this->textgap_rating = $a_textgap_rating;
1460  break;
1461  default:
1462  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1463  break;
1464  }
1465  }
1466 
1475  {
1476  return ($this->identical_scoring) ? 1 : 0;
1477  }
1478 
1486  function setIdenticalScoring($a_identical_scoring)
1487  {
1488  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1489  }
1490 
1498  {
1499  return "qpl_qst_cloze";
1500  }
1501 
1509  {
1510  return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1511  }
1512 
1519  function setFixedTextLength($a_text_len)
1520  {
1521  $this->fixedTextLength = $a_text_len;
1522  }
1523 
1531  {
1532  return $this->fixedTextLength;
1533  }
1534 
1543  function getMaximumGapPoints($gap_index)
1544  {
1545  $points = 0;
1546  $gap_max_points = 0;
1547  if (array_key_exists($gap_index, $this->gaps))
1548  {
1549  $gap =& $this->gaps[$gap_index];
1550  foreach ($gap->getItems($this->getShuffler()) as $answer)
1551  {
1552  if ($answer->getPoints() > $gap_max_points)
1553  {
1554  $gap_max_points = $answer->getPoints();
1555  }
1556  }
1557  $points += $gap_max_points;
1558  }
1559  return $points;
1560  }
1561 
1567  {
1568  return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1569  }
1571  {
1573  }
1574 
1576  {
1577  return $this->gap_combinations;
1578  }
1579 
1580  function setGapCombinationsExists($value)
1581  {
1582  $this->gap_combinations_exists = $value;
1583  }
1584 
1585  function setGapCombinations($value)
1586  {
1587  $this->gap_combinations = $value;
1588  }
1589 
1593  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1594  {
1595  parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1596 
1597  $solution = $this->getSolutionValues($active_id, $pass);
1598  $i = 1;
1599  foreach ($this->getGaps() as $gap_index => $gap)
1600  {
1601  $worksheet->setCell($startrow + $i, 0,$this->lng->txt("gap") . " $i");
1602  $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1603  $checked = FALSE;
1604  foreach ($solution as $solutionvalue)
1605  {
1606  if ($gap_index == $solutionvalue["value1"])
1607  {
1608  $string_escaping_org_value = $worksheet->getStringEscaping();
1609  try {
1610  $worksheet->setStringEscaping(false);
1611 
1612  switch ($gap->getType())
1613  {
1614  case CLOZE_SELECT:
1615  $worksheet->setCell($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1616  break;
1617  case CLOZE_NUMERIC:
1618  case CLOZE_TEXT:
1619  $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1620  break;
1621  }
1622 
1623  } finally {
1624  $worksheet->setStringEscaping($string_escaping_org_value);
1625  }
1626  }
1627  }
1628  $i++;
1629  }
1630 
1631  return $startrow + $i + 1;
1632  }
1633 
1638  {
1639  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1640  //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1641  $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1642  // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1643  }
1644 
1648  public function toJSON()
1649  {
1650  include_once("./Services/RTE/classes/class.ilRTE.php");
1651  $result = array();
1652  $result['id'] = (int) $this->getId();
1653  $result['type'] = (string) $this->getQuestionType();
1654  $result['title'] = (string) $this->getTitle();
1655  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1656  $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1657  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1658  $result['shuffle'] = (bool) $this->getShuffle();
1659  $result['feedback'] = array(
1660  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1661  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1662  );
1663 
1664  $gaps = array();
1665  foreach ($this->getGaps() as $key => $gap)
1666  {
1667  $items = array();
1668  foreach ($gap->getItems($this->getShuffler()) as $item)
1669  {
1670  $jitem = array();
1671  $jitem['points'] = $item->getPoints();
1672  $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1673  $jitem['order'] = $item->getOrder();
1674  if ($gap->getType() == CLOZE_NUMERIC)
1675  {
1676  $jitem['lowerbound'] = $item->getLowerBound();
1677  $jitem['upperbound'] = $item->getUpperBound();
1678  }
1679  else
1680  {
1681  $jitem['value'] = trim($jitem['value']);
1682  }
1683  array_push($items, $jitem);
1684  }
1685 
1686  if( $gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC) )
1687  {
1688  $jgap['size'] = $gap->getGapSize();
1689  }
1690 
1691  $jgap['shuffle'] = $gap->getShuffle();
1692  $jgap['type'] = $gap->getType();
1693  $jgap['item'] = $items;
1694 
1695  array_push($gaps, $jgap);
1696  }
1697  $result['gaps'] = $gaps;
1698  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1699  $result['mobs'] = $mobs;
1700  return json_encode($result);
1701  }
1702 
1711  public function getOperators($expression)
1712  {
1713  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1715  }
1716 
1721  public function getExpressionTypes()
1722  {
1723  return array(
1729  );
1730  }
1731 
1740  public function getUserQuestionResult($active_id, $pass)
1741  {
1743  global $ilDB;
1744  $result = new ilUserQuestionResult($this, $active_id, $pass);
1745 
1746  $maxStep = $this->lookupMaxStep($active_id, $pass);
1747 
1748  if( $maxStep !== null )
1749  {
1750  $data = $ilDB->queryF(
1751  "
1752  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1753  FROM tst_solutions sol
1754  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1755  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1756  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1757  ",
1758  array("integer", "integer", "integer","integer"),
1759  array($active_id, $pass, $this->getId(), $maxStep)
1760  );
1761  }
1762  else
1763  {
1764  $data = $ilDB->queryF(
1765  "
1766  SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1767  FROM tst_solutions sol
1768  INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1769  WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1770  GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1771  ",
1772  array("integer", "integer", "integer"),
1773  array($active_id, $pass, $this->getId())
1774  );
1775  }
1776 
1777  while($row = $ilDB->fetchAssoc($data))
1778  {
1779  if($row["cloze_type"] == 1)
1780  {
1781  $row["value2"]++;
1782  }
1783  $result->addKeyValue($row["val"], $row["value2"]);
1784  }
1785 
1786  $points = $this->calculateReachedPoints($active_id, $pass);
1787  $max_points = $this->getMaximumPoints();
1788 
1789  $result->setReachedPercentage(($points/$max_points) * 100);
1790 
1791  return $result;
1792  }
1793 
1802  public function getAvailableAnswerOptions($index = null)
1803  {
1804  if($index !== null)
1805  {
1806  return $this->getGap($index);
1807  }
1808  else
1809  {
1810  return $this->getGaps();
1811  }
1812  }
1813 
1814  public function calculateCombinationResult($user_result)
1815  {
1816  $points = 0;
1817 
1818  $assClozeGapCombinationObj = new assClozeGapCombination();
1819 
1820  if($assClozeGapCombinationObj->combinationExistsForQid($this->getId()))
1821  {
1822  $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1823  $gap_answers = array();
1824  $gap_used_in_combination = array();
1825  foreach($user_result as $user_result_build_list)
1826  {
1827  if(is_array($user_result_build_list))
1828  {
1829  $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1830  }
1831  }
1832 
1833  foreach($combinations_for_question as $combination)
1834  {
1835 
1836  foreach($combination as $row_key => $row_answers)
1837  {
1838  $combination_fulfilled = true;
1839  $points_for_combination = $row_answers['points'];
1840  foreach($row_answers as $gap_key => $combination_gap_answer)
1841  {
1842  if($gap_key !== 'points')
1843  {
1844  $gap_used_in_combination[$gap_key]= $gap_key;
1845  }
1846  if($combination_fulfilled && array_key_exists($gap_key, $gap_answers))
1847  {
1848  switch($combination_gap_answer['type'])
1849  {
1850  case CLOZE_TEXT:
1851  $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1852  if($is_text_gap_correct != 1)
1853  {
1854  $combination_fulfilled = false;
1855  }
1856  break;
1857  case CLOZE_SELECT:
1858  $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1859  $answertext = $answer->getAnswertext();
1860  if($answertext != $combination_gap_answer['answer'])
1861  {
1862  $combination_fulfilled = false;
1863  }
1864  break;
1865  case CLOZE_NUMERIC:
1866  $answer = $this->gaps[$gap_key]->getItem(0);
1867  if($combination_gap_answer['answer'] != 'out_of_bound')
1868  {
1869  $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1870  if($is_numeric_gap_correct != 1)
1871  {
1872  $combination_fulfilled = false;
1873  }
1874  }
1875  else
1876  {
1877  $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1878  if($wrong_is_the_new_right == 1)
1879  {
1880  $combination_fulfilled = false;
1881  }
1882  }
1883  break;
1884  }
1885  }
1886  else
1887  {
1888  if($gap_key !== 'points')
1889  {
1890  $combination_fulfilled = false;
1891  }
1892  }
1893  }
1894  if($combination_fulfilled)
1895  {
1896  $points += $points_for_combination;
1897  }
1898  }
1899  }
1900  }
1901  return array($points, $gap_used_in_combination);
1902  }
1908  protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1909  {
1910  if($detailed === null)
1911  {
1912  $detailed = array();
1913  }
1914 
1915  $assClozeGapCombinationObj = new assClozeGapCombination();
1916  $combinations[1] = array();
1917  if($assClozeGapCombinationObj->combinationExistsForQid($this->getId()))
1918  {
1919  $combinations = $this->calculateCombinationResult($user_result);
1920  $points = $combinations[0];
1921  }
1922  $counter = 0;
1923  $solution_values_text = array(); // for identical scoring checks
1924  $solution_values_select = array(); // for identical scoring checks
1925  $solution_values_numeric = array(); // for identical scoring checks
1926  foreach($user_result as $gap_id => $value)
1927  {
1928  if(is_string($value))
1929  {
1930  $value = array("value" => $value);
1931  }
1932 
1933  if(array_key_exists($gap_id, $this->gaps) && !array_key_exists ($gap_id, $combinations[1]))
1934  {
1935  switch($this->gaps[$gap_id]->getType())
1936  {
1937  case CLOZE_TEXT:
1938  $gappoints = 0;
1939  for($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1940  {
1941  $answer = $this->gaps[$gap_id]->getItem($order);
1942  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1943  if($gotpoints > $gappoints) $gappoints = $gotpoints;
1944  }
1945  if(!$this->getIdenticalScoring())
1946  {
1947  // check if the same solution text was already entered
1948  if((in_array($value["value"], $solution_values_text)) && ($gappoints > 0))
1949  {
1950  $gappoints = 0;
1951  }
1952  }
1953  $points += $gappoints;
1954  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1955  array_push($solution_values_text, $value["value"]);
1956  break;
1957  case CLOZE_NUMERIC:
1958  $gappoints = 0;
1959  for($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1960  {
1961  $answer = $this->gaps[$gap_id]->getItem($order);
1962  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1963  if($gotpoints > $gappoints) $gappoints = $gotpoints;
1964  }
1965  if(!$this->getIdenticalScoring())
1966  {
1967  // check if the same solution value was already entered
1968  include_once "./Services/Math/classes/class.EvalMath.php";
1969  $eval = new EvalMath();
1970  $eval->suppress_errors = TRUE;
1971  $found_value = FALSE;
1972  foreach($solution_values_numeric as $solval)
1973  {
1974  if($eval->e($solval) == $eval->e($value["value"]))
1975  {
1976  $found_value = TRUE;
1977  }
1978  }
1979  if($found_value && ($gappoints > 0))
1980  {
1981  $gappoints = 0;
1982  }
1983  }
1984  $points += $gappoints;
1985  $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1986  array_push($solution_values_numeric, $value["value"]);
1987  break;
1988  case CLOZE_SELECT:
1989  if($value["value"] >= 0)
1990  {
1991  for($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1992  {
1993  $answer = $this->gaps[$gap_id]->getItem($order);
1994  if($value["value"] == $answer->getOrder())
1995  {
1996  $answerpoints = $answer->getPoints();
1997  if(!$this->getIdenticalScoring())
1998  {
1999  // check if the same solution value was already entered
2000  if((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0))
2001  {
2002  $answerpoints = 0;
2003  }
2004  }
2005  $points += $answerpoints;
2006  $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? TRUE : FALSE, "positive" => ($answerpoints > 0) ? TRUE : FALSE);
2007  array_push($solution_values_select, $answer->getAnswertext());
2008  }
2009  }
2010  }
2011  break;
2012  }
2013  }
2014  }
2015 
2016  return $points;
2017  }
2018 
2020  {
2021  $userSolution = array();
2022 
2023  foreach($previewSession->getParticipantsSolution() as $key => $val)
2024  {
2025  $userSolution[] = array('gap_id' => $key, 'value' => $val);
2026  }
2027 
2028  $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
2029  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
2030 
2031  return $this->ensureNonNegativePoints($reachedPoints);
2032  }
2033 }
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.
$worksheet
$size
Definition: RandomTest.php:79
getMaximumGapPoints($gap_index)
Returns the maximum points for a gap.
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getQuestionType()
Returns the question type of the question.
saveWorkingData($active_id, $pass=NULL, $authorized=true)
Saves the learners input of the question to the database.
Class iQuestionCondition.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
setEndTag($end_tag="[/gap]")
Sets the end tag of a cloze gap.
clearGapAnswers()
Removes all answers from the gaps.
Class for cloze tests.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
saveAnswerSpecificDataToDb()
Save all gaps to the database.
getAnswerTableName()
Returns the name of the answer table in the database.
$result
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
const TEXTGAP_RATING_LEVENSHTEIN2
const TEXTGAP_RATING_LEVENSHTEIN1
Abstract basic class which is to be extended by the concrete assessment question type classes...
const CLOZE_TEXT
Cloze question constants.
setGapSize($gap_index, $order, $size)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
calculateCombinationResult($user_result)
getOperators($expression)
Get all available operations for a specific question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
ensureNonNegativePoints($points)
prepareTextareaOutput($txt_output, $prepare_for_latex_output=FALSE, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
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)
Add rich text string
The name of the decorator.
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)
setClozeTextValue($cloze_text="")
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
checkForValidFormula($value)
setNrOfTries($a_nr_of_tries)
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
{}
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
createGapsFromQuestiontext()
Create gap entries by parsing the question text.
getObjId()
Get the object id of the container object.
getShuffle()
Gets the shuffle flag.
getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
Returns the points for a text gap and compares the given solution with the entered solution using the...
getStartTag()
Returns the start tag of a cloze gap.
calculateReachedPoints($active_id, $pass=NULL, $authorized=true, $returndetails=FALSE)
Returns the points, a learner has reached answering the question.
$counter
getTextgapPoints($a_original, $a_entered, $max_points)
Returns the points for a text gap and compares the given solution with the entered solution using the...
setGapType($gap_index, $gap_type)
Set the type of a gap with a given index.
setGapCombinationsExists($value)
static _getLogLanguage()
retrieve the log language for assessment logging
getGaps()
Returns the array of gaps.
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
setAuthor($author="")
Sets the authors name of the assQuestion object.
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates an assClozeTest.
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
const CLOZE_SELECT
getAuthor()
Gets the authors name of the assQuestion object.
const TEXTGAP_RATING_LEVENSHTEIN3
$mobs
setGapAnswerLowerBound($gap_index, $order, $bound)
Sets the lower bound of a gap with a given index and an answer with a given order.
Class ilUserQuestionResult.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
setFixedTextLength($a_text_len)
Sets a fixed text length for all text fields in the cloze question.
isValidNumericSubmitValue($submittedValue)
copyObject($target_questionpool_id, $title="")
Copies an assClozeTest object.
const TEXTGAP_RATING_CASESENSITIVE
Interface ilObjAnswerScoringAdjustable.
reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
{}
getQuestion()
Gets the question string of the question object.
$ilUser
Definition: imgupload.php:18
addGapAtIndex($gap, $index)
Adds a ClozeGap object at a given index.
static stripSlashes($a_str, $a_strip_html=true, $a_allow="")
strip slashes if magic qoutes is enabled
setGapAnswerUpperBound($gap_index, $order, $bound)
Sets the upper bound of a gap with a given index and an answer with a given order.
saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
addGapAnswer($gap_index, $order, $answer)
Sets the answer text of a gap with a given index.
setGapShuffle($gap_index=0, $shuffle=1)
Sets the shuffle state of a gap with a given index.
Create styles array
The data for the language used.
setClozeText($cloze_text="")
Evaluates the text gap solutions from the cloze text.
cleanQuestiontext($text)
Cleans cloze question text to remove attributes or tags from older ILIAS versions.
getExpressionTypes()
Get all available expression types for a specific question.
flushGaps()
Deletes all gaps without changing the cloze text.
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
setPoints($a_points)
Sets the maximum available points for the question.
saveQuestionDataToDb($original_id="")
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
deleteGap($gap_index)
Deletes a gap with a given index.
updateClozeTextFromGaps()
Updates the gap parameters in the cloze text from the form input.
setTextgapRating($a_textgap_rating)
Sets the rating option for text gaps.
setQuestion($question="")
Sets the question string of the question object.
Interface ilObjQuestionScoringAdjustable.
getEndTag()
Returns the end tag of a cloze gap.
removeCurrentSolution($active_id, $pass, $authorized=true)
setStartTag($start_tag="[gap]")
Sets the start tag of a cloze gap.
addGapText($gap_index)
Adds a new answer text value to a text gap with a given index.
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...
$text
saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
getClozeText()
Returns the cloze text.
getSolutionValues($active_id, $pass=NULL, $authorized=true)
Loads solutions of a given user from the database an returns it.
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"]
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