ILIAS  Release_4_2_x_branch Revision 61807
 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 (strlen($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 = "", $testObjId = null)
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 
772  if( (int)$testObjId > 0 )
773  {
774  $thisObjId = $this->getObjId();
775  }
776 
777  $clone = $this;
778  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
780  $clone->id = -1;
781 
782  if( (int)$testObjId > 0 )
783  {
784  $clone->setObjId($testObjId);
785  }
786 
787  if ($title)
788  {
789  $clone->setTitle($title);
790  }
791  if ($author)
792  {
793  $clone->setAuthor($author);
794  }
795  if ($owner)
796  {
797  $clone->setOwner($owner);
798  }
799  if ($for_test)
800  {
801  $clone->saveToDb($original_id);
802  }
803  else
804  {
805  $clone->saveToDb();
806  }
807 
808  // copy question page content
809  $clone->copyPageOfQuestion($this_id);
810  // copy XHTML media objects
811  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
812  // duplicate the generic feedback
813  $clone->duplicateFeedbackGeneric($this_id);
814 
815  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
816 
817  return $clone->getId();
818  }
819 
825  function copyObject($target_questionpool, $title = "")
826  {
827  if ($this->getId() <= 0)
828  {
829  // The question has not been saved. It cannot be duplicated
830  return;
831  }
832  $clone = $this;
833  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
835  $clone->id = -1;
836  $source_questionpool = $this->getObjId();
837  $clone->setObjId($target_questionpool);
838  if ($title)
839  {
840  $clone->setTitle($title);
841  }
842  $clone->saveToDb();
843 
844  // copy question page content
845  $clone->copyPageOfQuestion($original_id);
846  // copy XHTML media objects
847  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
848  // duplicate the generic feedback
849  $clone->duplicateFeedbackGeneric($original_id);
850 
851  $clone->onCopy($this->getObjId(), $this->getId());
852  return $clone->getId();
853  }
854 
861  {
862  $output = $this->getClozeText();
863  foreach ($this->getGaps() as $gap_index => $gap)
864  {
865  $answers = array();
866  foreach ($gap->getItemsRaw() as $item)
867  {
868  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
869  }
870  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]", $output, 1);
871  }
872  $output = str_replace("_gap]", "gap]", $output);
873  $this->question = $output;
874  }
875 
885  function deleteAnswerText($gap_index, $answer_index)
886  {
887  if (array_key_exists($gap_index, $this->gaps))
888  {
889  if ($this->gaps[$gap_index]->getItemCount() == 1)
890  {
891  // this is the last answer text => remove the gap
892  $this->deleteGap($gap_index);
893  }
894  else
895  {
896  // remove the answer text
897  $this->gaps[$gap_index]->deleteItem($answer_index);
898  $this->updateClozeTextFromGaps();
899  }
900  }
901  }
902 
911  function deleteGap($gap_index)
912  {
913  if (array_key_exists($gap_index, $this->gaps))
914  {
915  $output = $this->getClozeText();
916  foreach ($this->getGaps() as $replace_gap_index => $gap)
917  {
918  $answers = array();
919  foreach ($gap->getItemsRaw() as $item)
920  {
921  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
922  }
923  if ($replace_gap_index == $gap_index)
924  {
925  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1);
926  }
927  else
928  {
929  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1);
930  }
931  }
932  $output = str_replace("_gap]", "gap]", $output);
933  $this->question = $output;
934  unset($this->gaps[$gap_index]);
935  $this->gaps = array_values($this->gaps);
936  }
937  }
938 
948  function getTextgapPoints($a_original, $a_entered, $max_points)
949  {
950  include_once "./Services/Utilities/classes/class.ilStr.php";
951  $result = 0;
952  $gaprating = $this->getTextgapRating();
953  switch ($gaprating)
954  {
956  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) $result = $max_points;
957  break;
959  if (strcmp($a_original, $a_entered) == 0) $result = $max_points;
960  break;
962  if (levenshtein($a_original, $a_entered) <= 1) $result = $max_points;
963  break;
965  if (levenshtein($a_original, $a_entered) <= 2) $result = $max_points;
966  break;
968  if (levenshtein($a_original, $a_entered) <= 3) $result = $max_points;
969  break;
971  if (levenshtein($a_original, $a_entered) <= 4) $result = $max_points;
972  break;
974  if (levenshtein($a_original, $a_entered) <= 5) $result = $max_points;
975  break;
976  }
977  return $result;
978  }
979 
989  function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
990  {
991  if( !is_numeric($a_entered) )
992  {
993  return 0;
994  }
995 
996  include_once "./Services/Math/classes/class.EvalMath.php";
997  $eval = new EvalMath();
998  $eval->suppress_errors = TRUE;
999  $result = 0;
1000  if (($eval->e($lowerBound) !== FALSE) && ($eval->e($upperBound) !== FALSE))
1001  {
1002  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1003  }
1004  else if ($eval->e($lowerBound) !== FALSE)
1005  {
1006  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) $result = $max_points;
1007  }
1008  else if ($eval->e($upperBound) !== FALSE)
1009  {
1010  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1011  }
1012  else
1013  {
1014  if ($eval->e($a_entered) == $eval->e($a_original)) $result = $max_points;
1015  }
1016  return $result;
1017  }
1018 
1028  function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
1029  {
1030  global $ilDB;
1031 
1032  $found_value1 = array();
1033  $found_value2 = array();
1034  $detailed = array();
1035  if (is_null($pass))
1036  {
1037  $pass = $this->getSolutionMaxPass($active_id);
1038  }
1039  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1040  array(
1041  "integer",
1042  "integer",
1043  "integer"
1044  ),
1045  array(
1046  $active_id,
1047  $this->getId(),
1048  $pass
1049  )
1050  );
1051  $user_result = array();
1052  while ($data = $ilDB->fetchAssoc($result))
1053  {
1054  if (strcmp($data["value2"], "") != 0)
1055  {
1056  $user_result[$data["value1"]] = array(
1057  "gap_id" => $data["value1"],
1058  "value" => $data["value2"]
1059  );
1060  }
1061  }
1062  $points = 0;
1063  $counter = 0;
1064  $solution_values_text = array(); // for identical scoring checks
1065  $solution_values_select = array(); // for identical scoring checks
1066  $solution_values_numeric = array(); // for identical scoring checks
1067  foreach ($user_result as $gap_id => $value)
1068  {
1069  if (array_key_exists($gap_id, $this->gaps))
1070  {
1071  switch ($this->gaps[$gap_id]->getType())
1072  {
1073  case CLOZE_TEXT:
1074  $gappoints = 0;
1075  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1076  {
1077  $answer = $this->gaps[$gap_id]->getItem($order);
1078  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1079  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1080  }
1081  if (!$this->getIdenticalScoring())
1082  {
1083  // check if the same solution text was already entered
1084  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0))
1085  {
1086  $gappoints = 0;
1087  }
1088  }
1089  $points += $gappoints;
1090  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1091  array_push($solution_values_text, $value["value"]);
1092  break;
1093  case CLOZE_NUMERIC:
1094  $gappoints = 0;
1095  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1096  {
1097  $answer = $this->gaps[$gap_id]->getItem($order);
1098  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1099  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1100  }
1101  if (!$this->getIdenticalScoring())
1102  {
1103  // check if the same solution value was already entered
1104  include_once "./Services/Math/classes/class.EvalMath.php";
1105  $eval = new EvalMath();
1106  $eval->suppress_errors = TRUE;
1107  $found_value = FALSE;
1108  foreach ($solution_values_numeric as $solval)
1109  {
1110  if ($eval->e($solval) == $eval->e($value["value"]))
1111  {
1112  $found_value = TRUE;
1113  }
1114  }
1115  if ($found_value && ($gappoints > 0))
1116  {
1117  $gappoints = 0;
1118  }
1119  }
1120  $points += $gappoints;
1121  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1122  array_push($solution_values_numeric, $value["value"]);
1123  break;
1124  case CLOZE_SELECT:
1125  if ($value["value"] >= 0)
1126  {
1127  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1128  {
1129  $answer = $this->gaps[$gap_id]->getItem($order);
1130  if ($value["value"] == $answer->getOrder())
1131  {
1132  $answerpoints = $answer->getPoints();
1133  if (!$this->getIdenticalScoring())
1134  {
1135  // check if the same solution value was already entered
1136  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0))
1137  {
1138  $answerpoints = 0;
1139  }
1140  }
1141  $points += $answerpoints;
1142  $detailed[$gap_id] = array("points" =>$answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? TRUE : FALSE, "positive" => ($answerpoints > 0) ? TRUE : FALSE);
1143  array_push($solution_values_select, $answer->getAnswertext());
1144  }
1145  }
1146  }
1147  break;
1148  }
1149  }
1150  }
1151  if ($returndetails)
1152  {
1153  return $detailed;
1154  }
1155  else
1156  {
1157  $points = parent::calculateReachedPoints($active_id, $pass = NULL, $points);
1158  return $points;
1159  }
1160  }
1161 
1170  function saveWorkingData($active_id, $pass = NULL)
1171  {
1172  global $ilDB;
1173  global $ilUser;
1174  if (is_null($pass))
1175  {
1176  include_once "./Modules/Test/classes/class.ilObjTest.php";
1177  $pass = ilObjTest::_getPass($active_id);
1178  }
1179 
1180  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
1181  array(
1182  "integer",
1183  "integer",
1184  "integer"
1185  ),
1186  array(
1187  $active_id,
1188  $this->getId(),
1189  $pass
1190  )
1191  );
1192 
1193  $entered_values = 0;
1194  foreach ($_POST as $key => $value)
1195  {
1196  if (preg_match("/^gap_(\d+)/", $key, $matches))
1197  {
1198  $value = ilUtil::stripSlashes($value, FALSE);
1199  if (strlen($value))
1200  {
1201  $gap = $this->getGap($matches[1]);
1202  if (is_object($gap))
1203  {
1204  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1)))
1205  {
1206  if ($gap->getType() == CLOZE_NUMERIC)
1207  {
1208  $value = str_replace(",", ".", $value);
1209  }
1210  $next_id = $ilDB->nextId("tst_solutions");
1211  $affectedRows = $ilDB->insert("tst_solutions", array(
1212  "solution_id" => array("integer", $next_id),
1213  "active_fi" => array("integer", $active_id),
1214  "question_fi" => array("integer", $this->getId()),
1215  "value1" => array("clob", trim($matches[1])),
1216  "value2" => array("clob", trim($value)),
1217  "pass" => array("integer", $pass),
1218  "tstamp" => array("integer", time())
1219  ));
1220  $entered_values++;
1221  }
1222  }
1223  }
1224  }
1225  }
1226  if ($entered_values)
1227  {
1228  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1230  {
1231  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1232  }
1233  }
1234  else
1235  {
1236  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1238  {
1239  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1240  }
1241  }
1242  parent::saveWorkingData($active_id, $pass);
1243  return TRUE;
1244  }
1245 
1252  function getQuestionType()
1253  {
1254  return "assClozeTest";
1255  }
1256 
1264  function getTextgapRating()
1265  {
1266  return $this->textgap_rating;
1267  }
1268 
1276  function setTextgapRating($a_textgap_rating)
1277  {
1278  switch ($a_textgap_rating)
1279  {
1287  $this->textgap_rating = $a_textgap_rating;
1288  break;
1289  default:
1290  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1291  break;
1292  }
1293  }
1294 
1303  {
1304  return ($this->identical_scoring) ? 1 : 0;
1305  }
1306 
1314  function setIdenticalScoring($a_identical_scoring)
1315  {
1316  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1317  }
1318 
1326  {
1327  return "qpl_qst_cloze";
1328  }
1329 
1337  {
1338  return "qpl_a_cloze";
1339  }
1340 
1347  function setFixedTextLength($a_text_len)
1348  {
1349  $this->fixedTextLength = $a_text_len;
1350  }
1351 
1359  {
1360  return $this->fixedTextLength;
1361  }
1362 
1371  function getMaximumGapPoints($gap_index)
1372  {
1373  $points = 0;
1374  if (array_key_exists($gap_index, $this->gaps))
1375  {
1376  $gap =& $this->gaps[$gap_index];
1377  foreach ($gap->getItems() as $answer)
1378  {
1379  if ($answer->getPoints() > $gap_max_points)
1380  {
1381  $gap_max_points = $answer->getPoints();
1382  }
1383  }
1384  $points += $gap_max_points;
1385  }
1386  return $points;
1387  }
1388 
1394  {
1396  }
1397 
1410  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
1411  {
1412  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
1413  $solution = $this->getSolutionValues($active_id, $pass);
1414  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
1415  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
1416  $i = 1;
1417  foreach ($this->getGaps() as $gap_index => $gap)
1418  {
1419  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($this->lng->txt("gap") . " $i"), $format_bold);
1420  $checked = FALSE;
1421  foreach ($solution as $solutionvalue)
1422  {
1423  if ($gap_index == $solutionvalue["value1"])
1424  {
1425  switch ($gap->getType())
1426  {
1427  case CLOZE_SELECT:
1428  $worksheet->writeString($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1429  break;
1430  case CLOZE_NUMERIC:
1431  case CLOZE_TEXT:
1432  $worksheet->writeString($startrow + $i, 1, $solutionvalue["value2"]);
1433  break;
1434  }
1435  }
1436  }
1437  $i++;
1438  }
1439  return $startrow + $i + 1;
1440  }
1441 
1445  public function toJSON()
1446  {
1447  include_once("./Services/RTE/classes/class.ilRTE.php");
1448  $result = array();
1449  $result['id'] = (int) $this->getId();
1450  $result['type'] = (string) $this->getQuestionType();
1451  $result['title'] = (string) $this->getTitle();
1452  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1453  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1454  $result['shuffle'] = (bool) $this->getShuffle();
1455  $result['feedback'] = array(
1456  "onenotcorrect" => nl2br(ilRTE::_replaceMediaObjectImageSrc($this->getFeedbackGeneric(0), 0)),
1457  "allcorrect" => nl2br(ilRTE::_replaceMediaObjectImageSrc($this->getFeedbackGeneric(1), 0))
1458  );
1459  $gaps = array();
1460  foreach ($this->getGaps() as $key => $gap)
1461  {
1462  $items = array();
1463  foreach ($gap->getItems() as $item)
1464  {
1465  $jitem = array();
1466  $jitem['points'] = $item->getPoints();
1467  $jitem['value'] = $item->getAnswertext();
1468  $jitem['order'] = $item->getOrder();
1469  if ($gap->getType() == CLOZE_NUMERIC)
1470  {
1471  $jitem['lowerbound'] = $item->getLowerBound();
1472  $jitem['upperbound'] = $item->getUpperBound();
1473  }
1474  array_push($items, $jitem);
1475  }
1476  $jgap['shuffle'] = $gap->getShuffle();
1477  $jgap['type'] = $gap->getType();
1478  $jgap['item'] = $items;
1479  array_push($gaps, $jgap);
1480 
1481  }
1482  $result['gaps'] = $gaps;
1483  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1484  $result['mobs'] = $mobs;
1485  return json_encode($result);
1486  }
1487 
1488 }
1489 ?>