ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assClozeTest.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
5 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
6 include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
7 
20 {
28  var $gaps;
29 
38 
46  var $end_tag;
47 
59 
70 
77 
90  function __construct(
91  $title = "",
92  $comment = "",
93  $author = "",
94  $owner = -1,
95  $question = ""
96  )
97  {
99  $this->start_tag = "[gap]";
100  $this->end_tag = "[/gap]";
101  $this->gaps = array();
102  $this->setClozeText($cloze_text);
103  $this->fixedTextLength = "";
104  $this->identical_scoring = 1;
105  }
106 
113  function isComplete()
114  {
115  if (strlen($this->getTitle()) and ($this->getAuthor()) and ($this->getClozeText()) and (count($this->getGaps())) and ($this->getMaximumPoints() > 0))
116  {
117  return TRUE;
118  }
119  else
120  {
121  return FALSE;
122  }
123  }
124 
132  function cleanQuestiontext($text)
133  {
134  $text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
135  $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
136  $text = str_replace("</gap>", "[/gap]", $text);
137  return $text;
138  }
139 
147  function loadFromDb($question_id)
148  {
149  global $ilDB;
150  $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",
151  array("integer"),
152  array($question_id)
153  );
154  if ($result->numRows() == 1)
155  {
156  $data = $ilDB->fetchAssoc($result);
157  $this->setId($question_id);
158  $this->setNrOfTries($data['nr_of_tries']);
159  $this->setObjId($data["obj_fi"]);
160  $this->setTitle($data["title"]);
161  $this->setComment($data["description"]);
162  $this->setOriginalId($data["original_id"]);
163  $this->setAuthor($data["author"]);
164  $this->setPoints($data["points"]);
165  $this->setOwner($data["owner"]);
166  $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
167  $this->setFixedTextLength($data["fixed_textlen"]);
168  $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
169  // replacement of old syntax with new syntax
170  include_once("./Services/RTE/classes/class.ilRTE.php");
171  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
172  $this->setTextgapRating($data["textgap_rating"]);
173  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
174 
175  // open the cloze gaps with all answers
176  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
177  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
178  $result = $ilDB->queryF("SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
179  array("integer"),
180  array($question_id)
181  );
182  if ($result->numRows() > 0)
183  {
184  $this->gaps = array();
185  while ($data = $ilDB->fetchAssoc($result))
186  {
187  switch ($data["cloze_type"])
188  {
189  case CLOZE_TEXT:
190  if (!array_key_exists($data["gap_id"], $this->gaps))
191  {
192  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
193  }
194  $answer = new assAnswerCloze(
195  $data["answertext"],
196  $data["points"],
197  $data["aorder"]
198  );
199  $this->gaps[$data["gap_id"]]->addItem($answer);
200  break;
201  case CLOZE_SELECT:
202  if (!array_key_exists($data["gap_id"], $this->gaps))
203  {
204  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
205  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
206  }
207  $answer = new assAnswerCloze(
208  $data["answertext"],
209  $data["points"],
210  $data["aorder"]
211  );
212  $this->gaps[$data["gap_id"]]->addItem($answer);
213  break;
214  case CLOZE_NUMERIC:
215  if (!array_key_exists($data["gap_id"], $this->gaps))
216  {
217  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
218  }
219  $answer = new assAnswerCloze(
220  $data["answertext"],
221  $data["points"],
222  $data["aorder"]
223  );
224  $answer->setLowerBound($data["lowerlimit"]);
225  $answer->setUpperBound($data["upperlimit"]);
226  $this->gaps[$data["gap_id"]]->addItem($answer);
227  break;
228  }
229  }
230  }
231  }
232  parent::loadFromDb($question_id);
233  }
234 
241  function saveToDb($original_id = "")
242  {
243  global $ilDB;
244 
246 
247  include_once "./Services/Math/classes/class.EvalMath.php";
248  $eval = new EvalMath();
249  $eval->suppress_errors = TRUE;
250 
251  // save additional data
252  $affectedRows = $ilDB->manipulateF("DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
253  array("integer"),
254  array($this->getId())
255  );
256 
257 
258  $affectedRows = $ilDB->manipulateF("INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, textgap_rating, identical_scoring, fixed_textlen) VALUES (%s, %s, %s, %s)",
259  array(
260  "integer",
261  "text",
262  "text",
263  "integer"
264  ),
265  array(
266  $this->getId(),
267  $this->getTextgapRating(),
268  $this->getIdenticalScoring(),
269  $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL
270  )
271  );
272 
273  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_a_cloze WHERE question_fi = %s",
274  array("integer"),
275  array($this->getId())
276  );
277 
278  foreach ($this->gaps as $key => $gap)
279  {
280  foreach ($gap->getItems() as $item)
281  {
282  $query = "";
283  switch ($gap->getType())
284  {
285  case CLOZE_TEXT:
286  $next_id = $ilDB->nextId('qpl_a_cloze');
287  $affectedRows = $ilDB->manipulateF("INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type) VALUES (%s, %s, %s, %s, %s, %s, %s)",
288  array(
289  "integer",
290  "integer",
291  "integer",
292  "text",
293  "float",
294  "integer",
295  "text"
296  ),
297  array(
298  $next_id,
299  $this->getId(),
300  $key,
301  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
302  $item->getPoints(),
303  $item->getOrder(),
304  $gap->getType()
305  )
306  );
307  break;
308  case CLOZE_SELECT:
309  $next_id = $ilDB->nextId('qpl_a_cloze');
310  $affectedRows = $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)",
311  array(
312  "integer",
313  "integer",
314  "integer",
315  "text",
316  "float",
317  "integer",
318  "text",
319  "text"
320  ),
321  array(
322  $next_id,
323  $this->getId(),
324  $key,
325  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
326  $item->getPoints(),
327  $item->getOrder(),
328  $gap->getType(),
329  ($gap->getShuffle()) ? "1" : "0"
330  )
331  );
332  break;
333  case CLOZE_NUMERIC:
334  $next_id = $ilDB->nextId('qpl_a_cloze');
335  $affectedRows = $ilDB->manipulateF("INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, lowerlimit, upperlimit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
336  array(
337  "integer",
338  "integer",
339  "integer",
340  "text",
341  "float",
342  "integer",
343  "text",
344  "text",
345  "text"
346  ),
347  array(
348  $next_id,
349  $this->getId(),
350  $key,
351  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
352  $item->getPoints(),
353  $item->getOrder(),
354  $gap->getType(),
355  ($eval->e($item->getLowerBound() !== FALSE) && strlen($item->getLowerBound()) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
356  ($eval->e($item->getUpperBound() !== FALSE) && strlen($item->getUpperBound()) > 0) ? $item->getUpperBound() : $item->getAnswertext()
357  )
358  );
359  break;
360  }
361  }
362  }
364  }
365 
372  function getGaps()
373  {
374  return $this->gaps;
375  }
376 
377 
384  function flushGaps()
385  {
386  $this->gaps = array();
387  }
388 
398  function setClozeText($cloze_text = "")
399  {
400  $this->gaps = array();
401  $cloze_text = $this->cleanQuestiontext($cloze_text);
402  $this->question = $cloze_text;
404  }
405 
413  function getClozeText()
414  {
415  return $this->question;
416  }
417 
425  function getStartTag()
426  {
427  return $this->start_tag;
428  }
429 
437  function setStartTag($start_tag = "[gap]")
438  {
439  $this->start_tag = $start_tag;
440  }
441 
449  function getEndTag()
450  {
451  return $this->end_tag;
452  }
453 
461  function setEndTag($end_tag = "[/gap]")
462  {
463  $this->end_tag = $end_tag;
464  }
465 
473  {
474  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
475  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
476  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
477  preg_match_all($search_pattern, $this->getClozeText(), $found);
478  $this->gaps = array();
479  if (count($found[0]))
480  {
481  foreach ($found[1] as $gap_index => $answers)
482  {
483  // create text gaps by default
484  $gap = new assClozeGap(CLOZE_TEXT);
485  $textparams = preg_split("/(?<!\\\\),/", $answers);
486  foreach ($textparams as $key => $value)
487  {
488  $answer = new assAnswerCloze($value, 0, $key);
489  $gap->addItem($answer);
490  }
491  $this->gaps[$gap_index] = $gap;
492  }
493  }
494  }
495 
501  function setGapType($gap_index, $gap_type)
502  {
503  if (array_key_exists($gap_index, $this->gaps))
504  {
505  $this->gaps[$gap_index]->setType($gap_type);
506  }
507  }
508 
518  function setGapShuffle($gap_index = 0, $shuffle = 1)
519  {
520  if (array_key_exists($gap_index, $this->gaps))
521  {
522  $this->gaps[$gap_index]->setShuffle($shuffle);
523  }
524  }
525 
532  function clearGapAnswers()
533  {
534  foreach ($this->gaps as $gap_index => $gap)
535  {
536  $this->gaps[$gap_index]->clearItems();
537  }
538  }
539 
547  function getGapCount()
548  {
549  if (is_array($this->gaps))
550  {
551  return count($this->gaps);
552  }
553  else
554  {
555  return 0;
556  }
557  }
558 
569  function addGapAnswer($gap_index, $order, $answer)
570  {
571  if (array_key_exists($gap_index, $this->gaps))
572  {
573  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC)
574  {
575  // only allow notation with "." for real numbers
576  $answer = str_replace(",", ".", $answer);
577  }
578  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
579  }
580  }
581 
590  function getGap($gap_index = 0)
591  {
592  if (array_key_exists($gap_index, $this->gaps))
593  {
594  return $this->gaps[$gap_index];
595  }
596  else
597  {
598  return NULL;
599  }
600  }
601 
612  function setGapAnswerPoints($gap_index, $order, $points)
613  {
614  if (array_key_exists($gap_index, $this->gaps))
615  {
616  $this->gaps[$gap_index]->setItemPoints($order, $points);
617  }
618  }
619 
628  function addGapText($gap_index)
629  {
630  if (array_key_exists($gap_index, $this->gaps))
631  {
632  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
633  $answer = new assAnswerCloze(
634  "",
635  0,
636  $this->gaps[$gap_index]->getItemCount()
637  );
638  $this->gaps[$gap_index]->addItem($answer);
639  }
640  }
641 
650  function addGapAtIndex($gap, $index)
651  {
652  $this->gaps[$index] = $gap;
653  }
654 
665  function setGapAnswerLowerBound($gap_index, $order, $bound)
666  {
667  if (array_key_exists($gap_index, $this->gaps))
668  {
669  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
670  }
671  }
672 
683  function setGapAnswerUpperBound($gap_index, $order, $bound)
684  {
685  if (array_key_exists($gap_index, $this->gaps))
686  {
687  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
688  }
689  }
690 
697  function getMaximumPoints()
698  {
699  $points = 0;
700  foreach ($this->gaps as $gap_index => $gap)
701  {
702  if ($gap->getType() == CLOZE_TEXT)
703  {
704  $gap_max_points = 0;
705  foreach ($gap->getItems() as $item)
706  {
707  if ($item->getPoints() > $gap_max_points)
708  {
709  $gap_max_points = $item->getPoints();
710  }
711  }
712  $points += $gap_max_points;
713  }
714  else if ($gap->getType() == CLOZE_SELECT)
715  {
716  $srpoints = 0;
717  foreach ($gap->getItems() as $item)
718  {
719  if ($item->getPoints() > $srpoints)
720  {
721  $srpoints = $item->getPoints();
722  }
723  }
724  $points += $srpoints;
725  }
726  else if ($gap->getType() == CLOZE_NUMERIC)
727  {
728  $numpoints = 0;
729  foreach ($gap->getItems() as $item)
730  {
731  if ($item->getPoints() > $numpoints)
732  {
733  $numpoints = $item->getPoints();
734  }
735  }
736  $points += $numpoints;
737  }
738  }
739  return $points;
740  }
741 
747  function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
748  {
749  if ($this->id <= 0)
750  {
751  // The question has not been saved. It cannot be duplicated
752  return;
753  }
754  // duplicate the question in database
755  $this_id = $this->getId();
756 
757  if( (int)$testObjId > 0 )
758  {
759  $thisObjId = $this->getObjId();
760  }
761 
762  $clone = $this;
763  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
765  $clone->id = -1;
766 
767  if( (int)$testObjId > 0 )
768  {
769  $clone->setObjId($testObjId);
770  }
771 
772  if ($title)
773  {
774  $clone->setTitle($title);
775  }
776  if ($author)
777  {
778  $clone->setAuthor($author);
779  }
780  if ($owner)
781  {
782  $clone->setOwner($owner);
783  }
784  if ($for_test)
785  {
786  $clone->saveToDb($original_id);
787  }
788  else
789  {
790  $clone->saveToDb();
791  }
792 
793  // copy question page content
794  $clone->copyPageOfQuestion($this_id);
795  // copy XHTML media objects
796  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
797  // duplicate the generic feedback
798  $clone->duplicateGenericFeedback($this_id);
799  // duplicate the specific feedback
800  $clone->duplicateSpecificFeedback($this_id);
801 
802  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
803 
804  return $clone->getId();
805  }
806 
813  function duplicateSpecificFeedback($original_id)
814  {
815  global $ilDB;
816 
817  $result = $ilDB->queryF("SELECT * FROM qpl_fb_cloze WHERE question_fi = %s",
818  array('integer'),
819  array($original_id)
820  );
821  if ($result->numRows())
822  {
823  while ($row = $ilDB->fetchAssoc($result))
824  {
825  $next_id = $ilDB->nextId('qpl_fb_cloze');
826 
828  $ilDB->insert('qpl_fb_cloze', array(
829  'feedback_id' => array( 'integer', $next_id ),
830  'question_fi' => array( 'integer', $this->getId() ),
831  'answer' => array( 'integer', $row["answer"] ),
832  'feedback' => array( 'clob', $row["feedback"] ),
833  'tstamp' => array( 'integer', time() ),
834  )
835  );
836  }
837  }
838  }
839 
845  function copyObject($target_questionpool, $title = "")
846  {
847  if ($this->getId() <= 0)
848  {
849  // The question has not been saved. It cannot be duplicated
850  return;
851  }
852  $clone = $this;
853  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
855  $clone->id = -1;
856  $source_questionpool = $this->getObjId();
857  $clone->setObjId($target_questionpool);
858  if ($title)
859  {
860  $clone->setTitle($title);
861  }
862  $clone->saveToDb();
863 
864  // copy question page content
865  $clone->copyPageOfQuestion($original_id);
866  // copy XHTML media objects
867  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
868  // duplicate the generic feedback
869  $clone->duplicateGenericFeedback($original_id);
870  // duplicate specific feedback
871  $clone->duplicateSpecificFeedback($original_id);
872 
873  $clone->onCopy($this->getObjId(), $this->getId());
874  return $clone->getId();
875  }
876 
883  {
884  $output = $this->getClozeText();
885  foreach ($this->getGaps() as $gap_index => $gap)
886  {
887  $answers = array();
888  foreach ($gap->getItemsRaw() as $item)
889  {
890  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
891  }
892  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]", $output, 1);
893  }
894  $output = str_replace("_gap]", "gap]", $output);
895  $this->question = $output;
896  }
897 
907  function deleteAnswerText($gap_index, $answer_index)
908  {
909  if (array_key_exists($gap_index, $this->gaps))
910  {
911  if ($this->gaps[$gap_index]->getItemCount() == 1)
912  {
913  // this is the last answer text => remove the gap
914  $this->deleteGap($gap_index);
915  }
916  else
917  {
918  // remove the answer text
919  $this->gaps[$gap_index]->deleteItem($answer_index);
920  $this->updateClozeTextFromGaps();
921  }
922  }
923  }
924 
933  function deleteGap($gap_index)
934  {
935  if (array_key_exists($gap_index, $this->gaps))
936  {
937  $output = $this->getClozeText();
938  foreach ($this->getGaps() as $replace_gap_index => $gap)
939  {
940  $answers = array();
941  foreach ($gap->getItemsRaw() as $item)
942  {
943  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
944  }
945  if ($replace_gap_index == $gap_index)
946  {
947  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1);
948  }
949  else
950  {
951  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1);
952  }
953  }
954  $output = str_replace("_gap]", "gap]", $output);
955  $this->question = $output;
956  unset($this->gaps[$gap_index]);
957  $this->gaps = array_values($this->gaps);
958  }
959  }
960 
970  function getTextgapPoints($a_original, $a_entered, $max_points)
971  {
972  include_once "./Services/Utilities/classes/class.ilStr.php";
973  $result = 0;
974  $gaprating = $this->getTextgapRating();
975  switch ($gaprating)
976  {
978  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) $result = $max_points;
979  break;
981  if (strcmp($a_original, $a_entered) == 0) $result = $max_points;
982  break;
984  if (levenshtein($a_original, $a_entered) <= 1) $result = $max_points;
985  break;
987  if (levenshtein($a_original, $a_entered) <= 2) $result = $max_points;
988  break;
990  if (levenshtein($a_original, $a_entered) <= 3) $result = $max_points;
991  break;
993  if (levenshtein($a_original, $a_entered) <= 4) $result = $max_points;
994  break;
996  if (levenshtein($a_original, $a_entered) <= 5) $result = $max_points;
997  break;
998  }
999  return $result;
1000  }
1001 
1011  function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1012  {
1013  if( !is_numeric($a_entered) )
1014  {
1015  return 0;
1016  }
1017 
1018  include_once "./Services/Math/classes/class.EvalMath.php";
1019  $eval = new EvalMath();
1020  $eval->suppress_errors = TRUE;
1021  $result = 0;
1022  if (($eval->e($lowerBound) !== FALSE) && ($eval->e($upperBound) !== FALSE))
1023  {
1024  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1025  }
1026  else if ($eval->e($lowerBound) !== FALSE)
1027  {
1028  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) $result = $max_points;
1029  }
1030  else if ($eval->e($upperBound) !== FALSE)
1031  {
1032  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1033  }
1034  else
1035  {
1036  if ($eval->e($a_entered) == $eval->e($a_original)) $result = $max_points;
1037  }
1038  return $result;
1039  }
1040 
1051  public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
1052  {
1053  global $ilDB;
1054 
1055  $found_value1 = array();
1056  $found_value2 = array();
1057  $detailed = array();
1058  if (is_null($pass))
1059  {
1060  $pass = $this->getSolutionMaxPass($active_id);
1061  }
1062  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1063  array(
1064  "integer",
1065  "integer",
1066  "integer"
1067  ),
1068  array(
1069  $active_id,
1070  $this->getId(),
1071  $pass
1072  )
1073  );
1074  $user_result = array();
1075  while ($data = $ilDB->fetchAssoc($result))
1076  {
1077  if (strcmp($data["value2"], "") != 0)
1078  {
1079  $user_result[$data["value1"]] = array(
1080  "gap_id" => $data["value1"],
1081  "value" => $data["value2"]
1082  );
1083  }
1084  }
1085 
1086  ksort($user_result); // this is required when identical scoring for same solutions is disabled
1087 
1088  $points = 0;
1089  $counter = 0;
1090  $solution_values_text = array(); // for identical scoring checks
1091  $solution_values_select = array(); // for identical scoring checks
1092  $solution_values_numeric = array(); // for identical scoring checks
1093  foreach ($user_result as $gap_id => $value)
1094  {
1095  if (array_key_exists($gap_id, $this->gaps))
1096  {
1097  switch ($this->gaps[$gap_id]->getType())
1098  {
1099  case CLOZE_TEXT:
1100  $gappoints = 0;
1101  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1102  {
1103  $answer = $this->gaps[$gap_id]->getItem($order);
1104  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1105  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1106  }
1107  if (!$this->getIdenticalScoring())
1108  {
1109  // check if the same solution text was already entered
1110  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0))
1111  {
1112  $gappoints = 0;
1113  }
1114  }
1115  $points += $gappoints;
1116  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1117  array_push($solution_values_text, $value["value"]);
1118  break;
1119  case CLOZE_NUMERIC:
1120  $gappoints = 0;
1121  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1122  {
1123  $answer = $this->gaps[$gap_id]->getItem($order);
1124  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1125  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1126  }
1127  if (!$this->getIdenticalScoring())
1128  {
1129  // check if the same solution value was already entered
1130  include_once "./Services/Math/classes/class.EvalMath.php";
1131  $eval = new EvalMath();
1132  $eval->suppress_errors = TRUE;
1133  $found_value = FALSE;
1134  foreach ($solution_values_numeric as $solval)
1135  {
1136  if ($eval->e($solval) == $eval->e($value["value"]))
1137  {
1138  $found_value = TRUE;
1139  }
1140  }
1141  if ($found_value && ($gappoints > 0))
1142  {
1143  $gappoints = 0;
1144  }
1145  }
1146  $points += $gappoints;
1147  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1148  array_push($solution_values_numeric, $value["value"]);
1149  break;
1150  case CLOZE_SELECT:
1151  if ($value["value"] >= 0)
1152  {
1153  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1154  {
1155  $answer = $this->gaps[$gap_id]->getItem($order);
1156  if ($value["value"] == $answer->getOrder())
1157  {
1158  $answerpoints = $answer->getPoints();
1159  if (!$this->getIdenticalScoring())
1160  {
1161  // check if the same solution value was already entered
1162  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0))
1163  {
1164  $answerpoints = 0;
1165  }
1166  }
1167  $points += $answerpoints;
1168  $detailed[$gap_id] = array("points" =>$answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? TRUE : FALSE, "positive" => ($answerpoints > 0) ? TRUE : FALSE);
1169  array_push($solution_values_select, $answer->getAnswertext());
1170  }
1171  }
1172  }
1173  break;
1174  }
1175  }
1176  }
1177  if ($returndetails)
1178  {
1179  return $detailed;
1180  }
1181  else
1182  {
1183  return $points;
1184  }
1185  }
1186 
1195  public function saveWorkingData($active_id, $pass = NULL)
1196  {
1197  global $ilDB;
1198  global $ilUser;
1199  if (is_null($pass))
1200  {
1201  include_once "./Modules/Test/classes/class.ilObjTest.php";
1202  $pass = ilObjTest::_getPass($active_id);
1203  }
1204 
1205  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1206  array(
1207  "integer",
1208  "integer",
1209  "integer"
1210  ),
1211  array(
1212  $active_id,
1213  $this->getId(),
1214  $pass
1215  )
1216  );
1217 
1218  $entered_values = 0;
1219  foreach ($_POST as $key => $value)
1220  {
1221  if (preg_match("/^gap_(\d+)/", $key, $matches))
1222  {
1223  $value = ilUtil::stripSlashes($value, FALSE);
1224  if (strlen($value))
1225  {
1226  $gap = $this->getGap($matches[1]);
1227  if (is_object($gap))
1228  {
1229  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1)))
1230  {
1231  if ($gap->getType() == CLOZE_NUMERIC)
1232  {
1233  $value = str_replace(",", ".", $value);
1234  }
1235  $next_id = $ilDB->nextId("tst_solutions");
1236  $affectedRows = $ilDB->insert("tst_solutions", array(
1237  "solution_id" => array("integer", $next_id),
1238  "active_fi" => array("integer", $active_id),
1239  "question_fi" => array("integer", $this->getId()),
1240  "value1" => array("clob", trim($matches[1])),
1241  "value2" => array("clob", trim($value)),
1242  "pass" => array("integer", $pass),
1243  "tstamp" => array("integer", time())
1244  ));
1245  $entered_values++;
1246  }
1247  }
1248  }
1249  }
1250  }
1251  if ($entered_values)
1252  {
1253  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1255  {
1256  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1257  }
1258  }
1259  else
1260  {
1261  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1263  {
1264  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1265  }
1266  }
1267 
1268  return TRUE;
1269  }
1270 
1279  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered)
1280  {
1281  // nothing to rework!
1282  }
1283 
1290  function getQuestionType()
1291  {
1292  return "assClozeTest";
1293  }
1294 
1302  function getTextgapRating()
1303  {
1304  return $this->textgap_rating;
1305  }
1306 
1314  function setTextgapRating($a_textgap_rating)
1315  {
1316  switch ($a_textgap_rating)
1317  {
1325  $this->textgap_rating = $a_textgap_rating;
1326  break;
1327  default:
1328  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1329  break;
1330  }
1331  }
1332 
1341  {
1342  return ($this->identical_scoring) ? 1 : 0;
1343  }
1344 
1352  function setIdenticalScoring($a_identical_scoring)
1353  {
1354  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1355  }
1356 
1364  {
1365  return "qpl_qst_cloze";
1366  }
1367 
1375  {
1376  return "qpl_a_cloze";
1377  }
1378 
1385  function setFixedTextLength($a_text_len)
1386  {
1387  $this->fixedTextLength = $a_text_len;
1388  }
1389 
1397  {
1398  return $this->fixedTextLength;
1399  }
1400 
1409  function getMaximumGapPoints($gap_index)
1410  {
1411  $points = 0;
1412  if (array_key_exists($gap_index, $this->gaps))
1413  {
1414  $gap =& $this->gaps[$gap_index];
1415  foreach ($gap->getItems() as $answer)
1416  {
1417  if ($answer->getPoints() > $gap_max_points)
1418  {
1419  $gap_max_points = $answer->getPoints();
1420  }
1421  }
1422  $points += $gap_max_points;
1423  }
1424  return $points;
1425  }
1426 
1432  {
1434  }
1435 
1448  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
1449  {
1450  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
1451  $solution = $this->getSolutionValues($active_id, $pass);
1452  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
1453  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
1454  $i = 1;
1455  foreach ($this->getGaps() as $gap_index => $gap)
1456  {
1457  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($this->lng->txt("gap") . " $i"), $format_bold);
1458  $checked = FALSE;
1459  foreach ($solution as $solutionvalue)
1460  {
1461  if ($gap_index == $solutionvalue["value1"])
1462  {
1463  switch ($gap->getType())
1464  {
1465  case CLOZE_SELECT:
1466  $worksheet->writeString($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1467  break;
1468  case CLOZE_NUMERIC:
1469  case CLOZE_TEXT:
1470  $worksheet->writeString($startrow + $i, 1, $solutionvalue["value2"]);
1471  break;
1472  }
1473  }
1474  }
1475  $i++;
1476  }
1477  return $startrow + $i + 1;
1478  }
1479 
1483  public function toJSON()
1484  {
1485  include_once("./Services/RTE/classes/class.ilRTE.php");
1486  $result = array();
1487  $result['id'] = (int) $this->getId();
1488  $result['type'] = (string) $this->getQuestionType();
1489  $result['title'] = (string) $this->getTitle();
1490  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1491  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1492  $result['shuffle'] = (bool) $this->getShuffle();
1493  $result['feedback'] = array(
1494  "onenotcorrect" => nl2br(ilRTE::_replaceMediaObjectImageSrc($this->getFeedbackGeneric(0), 0)),
1495  "allcorrect" => nl2br(ilRTE::_replaceMediaObjectImageSrc($this->getFeedbackGeneric(1), 0))
1496  );
1497  $gaps = array();
1498  foreach ($this->getGaps() as $key => $gap)
1499  {
1500  $items = array();
1501  foreach ($gap->getItems() as $item)
1502  {
1503  $jitem = array();
1504  $jitem['points'] = $item->getPoints();
1505  $jitem['value'] = $item->getAnswertext();
1506  $jitem['order'] = $item->getOrder();
1507  if ($gap->getType() == CLOZE_NUMERIC)
1508  {
1509  $jitem['lowerbound'] = $item->getLowerBound();
1510  $jitem['upperbound'] = $item->getUpperBound();
1511  }
1512  array_push($items, $jitem);
1513  }
1514  $jgap['shuffle'] = $gap->getShuffle();
1515  $jgap['type'] = $gap->getType();
1516  $jgap['item'] = $items;
1517  array_push($gaps, $jgap);
1518 
1519  }
1520  $result['gaps'] = $gaps;
1521  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1522  $result['mobs'] = $mobs;
1523  return json_encode($result);
1524  }
1525 
1533  function saveFeedbackSingleAnswer($answer_index, $feedback)
1534  {
1535  global $ilDB;
1536 
1537  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_fb_cloze WHERE question_fi = %s AND answer = %s",
1538  array('integer','integer'),
1539  array($this->getId(), $answer_index)
1540  );
1541  if (strlen($feedback))
1542  {
1543  include_once("./Services/RTE/classes/class.ilRTE.php");
1544  $next_id = $ilDB->nextId('qpl_fb_cloze');
1546  $ilDB->insert('qpl_fb_cloze', array(
1547  'feedback_id' => array( 'integer', $next_id ),
1548  'question_fi' => array( 'integer', $this->getId() ),
1549  'answer' => array( 'integer', $answer_index ),
1550  'feedback' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc($feedback, 0) ),
1551  'tstamp' => array( 'integer', time() ),
1552  )
1553  );
1554  }
1555  }
1556 
1564  function getFeedbackSingleAnswer($answer_index)
1565  {
1566  global $ilDB;
1567 
1568  $feedback = "";
1569  $result = $ilDB->queryF("SELECT * FROM qpl_fb_cloze WHERE question_fi = %s AND answer = %s",
1570  array('integer','integer'),
1571  array($this->getId(), $answer_index)
1572  );
1573  if ($result->numRows())
1574  {
1575  $row = $ilDB->fetchAssoc($result);
1576  include_once("./Services/RTE/classes/class.ilRTE.php");
1577  $feedback = ilRTE::_replaceMediaObjectImageSrc($row["feedback"], 1);
1578  }
1579  return $feedback;
1580  }
1581 
1582  protected function deleteFeedbackSpecific($question_id)
1583  {
1584  global $ilDB;
1585  $ilDB->manipulateF(
1586  'DELETE
1587  FROM qpl_fb_cloze
1588  WHERE question_fi = %s',
1589  array('integer'),
1590  array($question_id)
1591  );
1592  }
1593 }