ILIAS  Release_4_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assClozeTest.php
Go to the documentation of this file.
1 <?php
2  /*
3  +----------------------------------------------------------------------------+
4  | ILIAS open source |
5  +----------------------------------------------------------------------------+
6  | Copyright (c) 1998-2001 ILIAS open source, University of Cologne |
7  | |
8  | This program is free software; you can redistribute it and/or |
9  | modify it under the terms of the GNU General Public License |
10  | as published by the Free Software Foundation; either version 2 |
11  | of the License, or (at your option) any later version. |
12  | |
13  | This program is distributed in the hope that it will be useful, |
14  | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16  | GNU General Public License for more details. |
17  | |
18  | You should have received a copy of the GNU General Public License |
19  | along with this program; if not, write to the Free Software |
20  | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
21  +----------------------------------------------------------------------------+
22 */
23 
24 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
25 include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
26 
35 {
43  var $gaps;
44 
53 
61  var $end_tag;
62 
74 
85 
92 
105  function __construct(
106  $title = "",
107  $comment = "",
108  $author = "",
109  $owner = -1,
110  $question = ""
111  )
112  {
114  $this->start_tag = "[gap]";
115  $this->end_tag = "[/gap]";
116  $this->gaps = array();
117  $this->setClozeText($cloze_text);
118  $this->fixedTextLength = "";
119  $this->identical_scoring = 1;
120  }
121 
128  function isComplete()
129  {
130  if (($this->getTitle()) and ($this->getAuthor()) and ($this->getClozeText()) and (count($this->getGaps())) and ($this->getMaximumPoints() > 0))
131  {
132  return TRUE;
133  }
134  else
135  {
136  return FALSE;
137  }
138  }
139 
147  function cleanQuestiontext($text)
148  {
149  $text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
150  $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
151  $text = str_replace("</gap>", "[/gap]", $text);
152  return $text;
153  }
154 
162  function loadFromDb($question_id)
163  {
164  global $ilDB;
165  $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",
166  array("integer"),
167  array($question_id)
168  );
169  if ($result->numRows() == 1)
170  {
171  $data = $ilDB->fetchAssoc($result);
172  $this->setId($question_id);
173  $this->setNrOfTries($data['nr_of_tries']);
174  $this->setObjId($data["obj_fi"]);
175  $this->setTitle($data["title"]);
176  $this->setComment($data["description"]);
177  $this->setOriginalId($data["original_id"]);
178  $this->setAuthor($data["author"]);
179  $this->setPoints($data["points"]);
180  $this->setOwner($data["owner"]);
181  $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
182  $this->setFixedTextLength($data["fixed_textlen"]);
183  $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
184  // replacement of old syntax with new syntax
185  include_once("./Services/RTE/classes/class.ilRTE.php");
186  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
187  $this->setTextgapRating($data["textgap_rating"]);
188  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
189 
190  // open the cloze gaps with all answers
191  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
192  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
193  $result = $ilDB->queryF("SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
194  array("integer"),
195  array($question_id)
196  );
197  if ($result->numRows() > 0)
198  {
199  $this->gaps = array();
200  while ($data = $ilDB->fetchAssoc($result))
201  {
202  switch ($data["cloze_type"])
203  {
204  case CLOZE_TEXT:
205  if (!array_key_exists($data["gap_id"], $this->gaps))
206  {
207  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
208  }
209  $answer = new assAnswerCloze(
210  $data["answertext"],
211  $data["points"],
212  $data["aorder"]
213  );
214  $this->gaps[$data["gap_id"]]->addItem($answer);
215  break;
216  case CLOZE_SELECT:
217  if (!array_key_exists($data["gap_id"], $this->gaps))
218  {
219  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
220  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
221  }
222  $answer = new assAnswerCloze(
223  $data["answertext"],
224  $data["points"],
225  $data["aorder"]
226  );
227  $this->gaps[$data["gap_id"]]->addItem($answer);
228  break;
229  case CLOZE_NUMERIC:
230  if (!array_key_exists($data["gap_id"], $this->gaps))
231  {
232  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
233  }
234  $answer = new assAnswerCloze(
235  $data["answertext"],
236  $data["points"],
237  $data["aorder"]
238  );
239  $answer->setLowerBound($data["lowerlimit"]);
240  $answer->setUpperBound($data["upperlimit"]);
241  $this->gaps[$data["gap_id"]]->addItem($answer);
242  break;
243  }
244  }
245  }
246  }
247  parent::loadFromDb($question_id);
248  }
249 
256  function saveToDb($original_id = "")
257  {
258  global $ilDB;
259 
261 
262  include_once "./Services/Math/classes/class.EvalMath.php";
263  $eval = new EvalMath();
264  $eval->suppress_errors = TRUE;
265 
266  // save additional data
267  $affectedRows = $ilDB->manipulateF("DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
268  array("integer"),
269  array($this->getId())
270  );
271 
272 
273  $affectedRows = $ilDB->manipulateF("INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, textgap_rating, identical_scoring, fixed_textlen) VALUES (%s, %s, %s, %s)",
274  array(
275  "integer",
276  "text",
277  "text",
278  "integer"
279  ),
280  array(
281  $this->getId(),
282  $this->getTextgapRating(),
283  $this->getIdenticalScoring(),
284  $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL
285  )
286  );
287 
288  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_a_cloze WHERE question_fi = %s",
289  array("integer"),
290  array($this->getId())
291  );
292 
293  foreach ($this->gaps as $key => $gap)
294  {
295  foreach ($gap->getItems() as $item)
296  {
297  $query = "";
298  switch ($gap->getType())
299  {
300  case CLOZE_TEXT:
301  $next_id = $ilDB->nextId('qpl_a_cloze');
302  $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)",
303  array(
304  "integer",
305  "integer",
306  "integer",
307  "text",
308  "float",
309  "integer",
310  "text"
311  ),
312  array(
313  $next_id,
314  $this->getId(),
315  $key,
316  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
317  $item->getPoints(),
318  $item->getOrder(),
319  $gap->getType()
320  )
321  );
322  break;
323  case CLOZE_SELECT:
324  $next_id = $ilDB->nextId('qpl_a_cloze');
325  $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)",
326  array(
327  "integer",
328  "integer",
329  "integer",
330  "text",
331  "float",
332  "integer",
333  "text",
334  "text"
335  ),
336  array(
337  $next_id,
338  $this->getId(),
339  $key,
340  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
341  $item->getPoints(),
342  $item->getOrder(),
343  $gap->getType(),
344  ($gap->getShuffle()) ? "1" : "0"
345  )
346  );
347  break;
348  case CLOZE_NUMERIC:
349  $next_id = $ilDB->nextId('qpl_a_cloze');
350  $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)",
351  array(
352  "integer",
353  "integer",
354  "integer",
355  "text",
356  "float",
357  "integer",
358  "text",
359  "text",
360  "text"
361  ),
362  array(
363  $next_id,
364  $this->getId(),
365  $key,
366  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
367  $item->getPoints(),
368  $item->getOrder(),
369  $gap->getType(),
370  ($eval->e($item->getLowerBound() !== FALSE) && strlen($item->getLowerBound()) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
371  ($eval->e($item->getUpperBound() !== FALSE) && strlen($item->getUpperBound()) > 0) ? $item->getUpperBound() : $item->getAnswertext()
372  )
373  );
374  break;
375  }
376  }
377  }
379  }
380 
387  function getGaps()
388  {
389  return $this->gaps;
390  }
391 
392 
399  function flushGaps()
400  {
401  $this->gaps = array();
402  }
403 
413  function setClozeText($cloze_text = "")
414  {
415  $this->gaps = array();
416  $cloze_text = $this->cleanQuestiontext($cloze_text);
417  $this->question = $cloze_text;
419  }
420 
428  function getClozeText()
429  {
430  return $this->question;
431  }
432 
440  function getStartTag()
441  {
442  return $this->start_tag;
443  }
444 
452  function setStartTag($start_tag = "[gap]")
453  {
454  $this->start_tag = $start_tag;
455  }
456 
464  function getEndTag()
465  {
466  return $this->end_tag;
467  }
468 
476  function setEndTag($end_tag = "[/gap]")
477  {
478  $this->end_tag = $end_tag;
479  }
480 
488  {
489  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
490  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
491  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
492  preg_match_all($search_pattern, $this->getClozeText(), $found);
493  $this->gaps = array();
494  if (count($found[0]))
495  {
496  foreach ($found[1] as $gap_index => $answers)
497  {
498  // create text gaps by default
499  $gap = new assClozeGap(CLOZE_TEXT);
500  $textparams = preg_split("/(?<!\\\\),/", $answers);
501  foreach ($textparams as $key => $value)
502  {
503  $answer = new assAnswerCloze($value, 0, $key);
504  $gap->addItem($answer);
505  }
506  $this->gaps[$gap_index] = $gap;
507  }
508  }
509  }
510 
516  function setGapType($gap_index, $gap_type)
517  {
518  if (array_key_exists($gap_index, $this->gaps))
519  {
520  $this->gaps[$gap_index]->setType($gap_type);
521  }
522  }
523 
533  function setGapShuffle($gap_index = 0, $shuffle = 1)
534  {
535  if (array_key_exists($gap_index, $this->gaps))
536  {
537  $this->gaps[$gap_index]->setShuffle($shuffle);
538  }
539  }
540 
547  function clearGapAnswers()
548  {
549  foreach ($this->gaps as $gap_index => $gap)
550  {
551  $this->gaps[$gap_index]->clearItems();
552  }
553  }
554 
562  function getGapCount()
563  {
564  if (is_array($this->gaps))
565  {
566  return count($this->gaps);
567  }
568  else
569  {
570  return 0;
571  }
572  }
573 
584  function addGapAnswer($gap_index, $order, $answer)
585  {
586  if (array_key_exists($gap_index, $this->gaps))
587  {
588  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC)
589  {
590  // only allow notation with "." for real numbers
591  $answer = str_replace(",", ".", $answer);
592  }
593  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
594  }
595  }
596 
605  function getGap($gap_index = 0)
606  {
607  if (array_key_exists($gap_index, $this->gaps))
608  {
609  return $this->gaps[$gap_index];
610  }
611  else
612  {
613  return NULL;
614  }
615  }
616 
627  function setGapAnswerPoints($gap_index, $order, $points)
628  {
629  if (array_key_exists($gap_index, $this->gaps))
630  {
631  $this->gaps[$gap_index]->setItemPoints($order, $points);
632  }
633  }
634 
643  function addGapText($gap_index)
644  {
645  if (array_key_exists($gap_index, $this->gaps))
646  {
647  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
648  $answer = new assAnswerCloze(
649  "",
650  0,
651  $this->gaps[$gap_index]->getItemCount()
652  );
653  $this->gaps[$gap_index]->addItem($answer);
654  }
655  }
656 
665  function addGapAtIndex($gap, $index)
666  {
667  $this->gaps[$index] = $gap;
668  }
669 
680  function setGapAnswerLowerBound($gap_index, $order, $bound)
681  {
682  if (array_key_exists($gap_index, $this->gaps))
683  {
684  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
685  }
686  }
687 
698  function setGapAnswerUpperBound($gap_index, $order, $bound)
699  {
700  if (array_key_exists($gap_index, $this->gaps))
701  {
702  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
703  }
704  }
705 
712  function getMaximumPoints()
713  {
714  $points = 0;
715  foreach ($this->gaps as $gap_index => $gap)
716  {
717  if ($gap->getType() == CLOZE_TEXT)
718  {
719  $gap_max_points = 0;
720  foreach ($gap->getItems() as $item)
721  {
722  if ($item->getPoints() > $gap_max_points)
723  {
724  $gap_max_points = $item->getPoints();
725  }
726  }
727  $points += $gap_max_points;
728  }
729  else if ($gap->getType() == CLOZE_SELECT)
730  {
731  $srpoints = 0;
732  foreach ($gap->getItems() as $item)
733  {
734  if ($item->getPoints() > $srpoints)
735  {
736  $srpoints = $item->getPoints();
737  }
738  }
739  $points += $srpoints;
740  }
741  else if ($gap->getType() == CLOZE_NUMERIC)
742  {
743  $numpoints = 0;
744  foreach ($gap->getItems() as $item)
745  {
746  if ($item->getPoints() > $numpoints)
747  {
748  $numpoints = $item->getPoints();
749  }
750  }
751  $points += $numpoints;
752  }
753  }
754  return $points;
755  }
756 
762  function duplicate($for_test = true, $title = "", $author = "", $owner = "")
763  {
764  if ($this->id <= 0)
765  {
766  // The question has not been saved. It cannot be duplicated
767  return;
768  }
769  // duplicate the question in database
770  $this_id = $this->getId();
771  $clone = $this;
772  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
774  $clone->id = -1;
775  if ($title)
776  {
777  $clone->setTitle($title);
778  }
779  if ($author)
780  {
781  $clone->setAuthor($author);
782  }
783  if ($owner)
784  {
785  $clone->setOwner($owner);
786  }
787  if ($for_test)
788  {
789  $clone->saveToDb($original_id);
790  }
791  else
792  {
793  $clone->saveToDb();
794  }
795 
796  // copy question page content
797  $clone->copyPageOfQuestion($this_id);
798  // copy XHTML media objects
799  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
800  // duplicate the generic feedback
801  $clone->duplicateFeedbackGeneric($this_id);
802 
803  $clone->onDuplicate($this_id);
804 
805  return $clone->getId();
806  }
807 
813  function copyObject($target_questionpool, $title = "")
814  {
815  if ($this->getId() <= 0)
816  {
817  // The question has not been saved. It cannot be duplicated
818  return;
819  }
820  $clone = $this;
821  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
823  $clone->id = -1;
824  $source_questionpool = $this->getObjId();
825  $clone->setObjId($target_questionpool);
826  if ($title)
827  {
828  $clone->setTitle($title);
829  }
830  $clone->saveToDb();
831 
832  // copy question page content
833  $clone->copyPageOfQuestion($original_id);
834  // copy XHTML media objects
835  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
836  // duplicate the generic feedback
837  $clone->duplicateFeedbackGeneric($original_id);
838 
839  $clone->onCopy($this->getObjId(), $this->getId());
840  return $clone->getId();
841  }
842 
849  {
850  $output = $this->getClozeText();
851  foreach ($this->getGaps() as $gap_index => $gap)
852  {
853  $answers = array();
854  foreach ($gap->getItemsRaw() as $item)
855  {
856  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
857  }
858  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]", $output, 1);
859  }
860  $output = str_replace("_gap]", "gap]", $output);
861  $this->question = $output;
862  }
863 
873  function deleteAnswerText($gap_index, $answer_index)
874  {
875  if (array_key_exists($gap_index, $this->gaps))
876  {
877  if ($this->gaps[$gap_index]->getItemCount() == 1)
878  {
879  // this is the last answer text => remove the gap
880  $this->deleteGap($gap_index);
881  }
882  else
883  {
884  // remove the answer text
885  $this->gaps[$gap_index]->deleteItem($answer_index);
886  $this->updateClozeTextFromGaps();
887  }
888  }
889  }
890 
899  function deleteGap($gap_index)
900  {
901  if (array_key_exists($gap_index, $this->gaps))
902  {
903  $output = $this->getClozeText();
904  foreach ($this->getGaps() as $replace_gap_index => $gap)
905  {
906  $answers = array();
907  foreach ($gap->getItemsRaw() as $item)
908  {
909  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
910  }
911  if ($replace_gap_index == $gap_index)
912  {
913  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1);
914  }
915  else
916  {
917  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1);
918  }
919  }
920  $output = str_replace("_gap]", "gap]", $output);
921  $this->question = $output;
922  unset($this->gaps[$gap_index]);
923  $this->gaps = array_values($this->gaps);
924  }
925  }
926 
936  function getTextgapPoints($a_original, $a_entered, $max_points)
937  {
938  include_once "./Services/Utilities/classes/class.ilStr.php";
939  $result = 0;
940  $gaprating = $this->getTextgapRating();
941  switch ($gaprating)
942  {
944  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) $result = $max_points;
945  break;
947  if (strcmp($a_original, $a_entered) == 0) $result = $max_points;
948  break;
950  if (levenshtein($a_original, $a_entered) <= 1) $result = $max_points;
951  break;
953  if (levenshtein($a_original, $a_entered) <= 2) $result = $max_points;
954  break;
956  if (levenshtein($a_original, $a_entered) <= 3) $result = $max_points;
957  break;
959  if (levenshtein($a_original, $a_entered) <= 4) $result = $max_points;
960  break;
962  if (levenshtein($a_original, $a_entered) <= 5) $result = $max_points;
963  break;
964  }
965  return $result;
966  }
967 
977  function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
978  {
979  include_once "./Services/Math/classes/class.EvalMath.php";
980  $eval = new EvalMath();
981  $eval->suppress_errors = TRUE;
982  $result = 0;
983  if (($eval->e($lowerBound) !== FALSE) && ($eval->e($upperBound) !== FALSE))
984  {
985  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
986  }
987  else if ($eval->e($lowerBound) !== FALSE)
988  {
989  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) $result = $max_points;
990  }
991  else if ($eval->e($upperBound) !== FALSE)
992  {
993  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
994  }
995  else
996  {
997  if ($eval->e($a_entered) == $eval->e($a_original)) $result = $max_points;
998  }
999  return $result;
1000  }
1001 
1011  function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
1012  {
1013  global $ilDB;
1014 
1015  $found_value1 = array();
1016  $found_value2 = array();
1017  $detailed = array();
1018  if (is_null($pass))
1019  {
1020  $pass = $this->getSolutionMaxPass($active_id);
1021  }
1022  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1023  array(
1024  "integer",
1025  "integer",
1026  "integer"
1027  ),
1028  array(
1029  $active_id,
1030  $this->getId(),
1031  $pass
1032  )
1033  );
1034  $user_result = array();
1035  while ($data = $ilDB->fetchAssoc($result))
1036  {
1037  if (strcmp($data["value2"], "") != 0)
1038  {
1039  $user_result[$data["value1"]] = array(
1040  "gap_id" => $data["value1"],
1041  "value" => $data["value2"]
1042  );
1043  }
1044  }
1045  $points = 0;
1046  $counter = 0;
1047  $solution_values_text = array(); // for identical scoring checks
1048  $solution_values_select = array(); // for identical scoring checks
1049  $solution_values_numeric = array(); // for identical scoring checks
1050  foreach ($user_result as $gap_id => $value)
1051  {
1052  if (array_key_exists($gap_id, $this->gaps))
1053  {
1054  switch ($this->gaps[$gap_id]->getType())
1055  {
1056  case CLOZE_TEXT:
1057  $gappoints = 0;
1058  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1059  {
1060  $answer = $this->gaps[$gap_id]->getItem($order);
1061  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1062  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1063  }
1064  if (!$this->getIdenticalScoring())
1065  {
1066  // check if the same solution text was already entered
1067  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0))
1068  {
1069  $gappoints = 0;
1070  }
1071  }
1072  $points += $gappoints;
1073  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1074  array_push($solution_values_text, $value["value"]);
1075  break;
1076  case CLOZE_NUMERIC:
1077  $gappoints = 0;
1078  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1079  {
1080  $answer = $this->gaps[$gap_id]->getItem($order);
1081  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1082  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1083  }
1084  if (!$this->getIdenticalScoring())
1085  {
1086  // check if the same solution value was already entered
1087  include_once "./Services/Math/classes/class.EvalMath.php";
1088  $eval = new EvalMath();
1089  $eval->suppress_errors = TRUE;
1090  $found_value = FALSE;
1091  foreach ($solution_values_numeric as $solval)
1092  {
1093  if ($eval->e($solval) == $eval->e($value["value"]))
1094  {
1095  $found_value = TRUE;
1096  }
1097  }
1098  if ($found_value && ($gappoints > 0))
1099  {
1100  $gappoints = 0;
1101  }
1102  }
1103  $points += $gappoints;
1104  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1105  array_push($solution_values_numeric, $value["value"]);
1106  break;
1107  case CLOZE_SELECT:
1108  if ($value["value"] >= 0)
1109  {
1110  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1111  {
1112  $answer = $this->gaps[$gap_id]->getItem($order);
1113  if ($value["value"] == $answer->getOrder())
1114  {
1115  $answerpoints = $answer->getPoints();
1116  if (!$this->getIdenticalScoring())
1117  {
1118  // check if the same solution value was already entered
1119  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0))
1120  {
1121  $answerpoints = 0;
1122  }
1123  }
1124  $points += $answerpoints;
1125  $detailed[$gap_id] = array("points" =>$answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? TRUE : FALSE, "positive" => ($answerpoints > 0) ? TRUE : FALSE);
1126  array_push($solution_values_select, $answer->getAnswertext());
1127  }
1128  }
1129  }
1130  break;
1131  }
1132  }
1133  }
1134  if ($returndetails)
1135  {
1136  return $detailed;
1137  }
1138  else
1139  {
1140  $points = parent::calculateReachedPoints($active_id, $pass = NULL, $points);
1141  return $points;
1142  }
1143  }
1144 
1153  function saveWorkingData($active_id, $pass = NULL)
1154  {
1155  global $ilDB;
1156  global $ilUser;
1157  if (is_null($pass))
1158  {
1159  include_once "./Modules/Test/classes/class.ilObjTest.php";
1160  $pass = ilObjTest::_getPass($active_id);
1161  }
1162 
1163  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1164  array(
1165  "integer",
1166  "integer",
1167  "integer"
1168  ),
1169  array(
1170  $active_id,
1171  $this->getId(),
1172  $pass
1173  )
1174  );
1175 
1176  $entered_values = 0;
1177  foreach ($_POST as $key => $value)
1178  {
1179  if (preg_match("/^gap_(\d+)/", $key, $matches))
1180  {
1181  $value = ilUtil::stripSlashes($value, FALSE);
1182  if (strlen($value))
1183  {
1184  $gap = $this->getGap($matches[1]);
1185  if (is_object($gap))
1186  {
1187  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1)))
1188  {
1189  if ($gap->getType() == CLOZE_NUMERIC)
1190  {
1191  $value = str_replace(",", ".", $value);
1192  }
1193  $next_id = $ilDB->nextId("tst_solutions");
1194  $affectedRows = $ilDB->insert("tst_solutions", array(
1195  "solution_id" => array("integer", $next_id),
1196  "active_fi" => array("integer", $active_id),
1197  "question_fi" => array("integer", $this->getId()),
1198  "value1" => array("clob", trim($matches[1])),
1199  "value2" => array("clob", trim($value)),
1200  "pass" => array("integer", $pass),
1201  "tstamp" => array("integer", time())
1202  ));
1203  $entered_values++;
1204  }
1205  }
1206  }
1207  }
1208  }
1209  if ($entered_values)
1210  {
1211  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1213  {
1214  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1215  }
1216  }
1217  else
1218  {
1219  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1221  {
1222  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1223  }
1224  }
1225  parent::saveWorkingData($active_id, $pass);
1226  return TRUE;
1227  }
1228 
1235  function getQuestionType()
1236  {
1237  return "assClozeTest";
1238  }
1239 
1247  function getTextgapRating()
1248  {
1249  return $this->textgap_rating;
1250  }
1251 
1259  function setTextgapRating($a_textgap_rating)
1260  {
1261  switch ($a_textgap_rating)
1262  {
1270  $this->textgap_rating = $a_textgap_rating;
1271  break;
1272  default:
1273  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1274  break;
1275  }
1276  }
1277 
1286  {
1287  return ($this->identical_scoring) ? 1 : 0;
1288  }
1289 
1297  function setIdenticalScoring($a_identical_scoring)
1298  {
1299  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1300  }
1301 
1309  {
1310  return "qpl_qst_cloze";
1311  }
1312 
1320  {
1321  return "qpl_a_cloze";
1322  }
1323 
1330  function setFixedTextLength($a_text_len)
1331  {
1332  $this->fixedTextLength = $a_text_len;
1333  }
1334 
1342  {
1343  return $this->fixedTextLength;
1344  }
1345 
1354  function getMaximumGapPoints($gap_index)
1355  {
1356  $points = 0;
1357  if (array_key_exists($gap_index, $this->gaps))
1358  {
1359  $gap =& $this->gaps[$gap_index];
1360  foreach ($gap->getItems() as $answer)
1361  {
1362  if ($answer->getPoints() > $gap_max_points)
1363  {
1364  $gap_max_points = $answer->getPoints();
1365  }
1366  }
1367  $points += $gap_max_points;
1368  }
1369  return $points;
1370  }
1371 
1377  {
1379  }
1380 
1393  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
1394  {
1395  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
1396  $solution = $this->getSolutionValues($active_id, $pass);
1397  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
1398  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
1399  $i = 1;
1400  foreach ($this->getGaps() as $gap_index => $gap)
1401  {
1402  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($this->lng->txt("gap") . " $i"), $format_bold);
1403  $checked = FALSE;
1404  foreach ($solution as $solutionvalue)
1405  {
1406  if ($gap_index == $solutionvalue["value1"])
1407  {
1408  switch ($gap->getType())
1409  {
1410  case CLOZE_SELECT:
1411  $worksheet->writeString($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1412  break;
1413  case CLOZE_NUMERIC:
1414  case CLOZE_TEXT:
1415  $worksheet->writeString($startrow + $i, 1, $solutionvalue["value2"]);
1416  break;
1417  }
1418  }
1419  }
1420  $i++;
1421  }
1422  return $startrow + $i + 1;
1423  }
1424 
1428  public function toJSON()
1429  {
1430  include_once("./Services/RTE/classes/class.ilRTE.php");
1431  $result = array();
1432  $result['id'] = (int) $this->getId();
1433  $result['type'] = (string) $this->getQuestionType();
1434  $result['title'] = (string) $this->getTitle();
1435  $result['question'] = (string) ilRTE::_replaceMediaObjectImageSrc($this->getQuestion(), 0);
1436  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1437  $result['shuffle'] = (bool) $this->getShuffle();
1438  $result['feedback'] = array(
1439  "onenotcorrect" => nl2br(ilRTE::_replaceMediaObjectImageSrc($this->getFeedbackGeneric(0), 0)),
1440  "allcorrect" => nl2br(ilRTE::_replaceMediaObjectImageSrc($this->getFeedbackGeneric(1), 0))
1441  );
1442  $gaps = array();
1443  foreach ($this->getGaps() as $key => $gap)
1444  {
1445  $items = array();
1446  foreach ($gap->getItems() as $item)
1447  {
1448  $jitem = array();
1449  $jitem['points'] = $item->getPoints();
1450  $jitem['value'] = $item->getAnswertext();
1451  if ($gap->getType() == CLOZE_NUMERIC)
1452  {
1453  $jitem['lowerbound'] = $item->getLowerBound();
1454  $jitem['upperbound'] = $item->getUpperBound();
1455  }
1456  array_push($items, $jitem);
1457  }
1458  $jgap['shuffle'] = $gap->getShuffle();
1459  $jgap['type'] = $gap->getType();
1460  $jgap['item'] = $items;
1461  array_push($gaps, $jgap);
1462 
1463  }
1464  $result['gaps'] = $gaps;
1465  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1466  $result['mobs'] = $mobs;
1467  return json_encode($result);
1468  }
1469 
1470 }
1471 ?>