ILIAS  Release_3_10_x_branch Revision 61812
 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 assClozeTest(
106  $title = "",
107  $comment = "",
108  $author = "",
109  $owner = -1,
110  $cloze_text = ""
111  )
112  {
113  $this->start_tag = "[gap]";
114  $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  if ($question_id < 1) return;
166  $statement = $ilDB->prepare("SELECT qpl_questions.*, " . $this->getAdditionalTableName() . ".* FROM qpl_questions, " . $this->getAdditionalTableName() . " WHERE question_id = ? AND qpl_questions.question_id = " . $this->getAdditionalTableName() . ".question_fi",
167  array(
168  "integer"
169  )
170  );
171  $result = $ilDB->execute($statement,
172  array(
173  $question_id
174  )
175  );
176  if ($result->numRows() == 1)
177  {
178  $data = $ilDB->fetchAssoc($result);
179  $this->setId($question_id);
180  $this->setObjId($data["obj_fi"]);
181  $this->setTitle($data["title"]);
182  $this->setComment($data["comment"]);
183  $this->setOriginalId($data["original_id"]);
184  $this->setAuthor($data["author"]);
185  $this->setPoints($data["points"]);
186  $this->setOwner($data["owner"]);
187  $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
188  $this->setFixedTextLength($data["fixed_textlen"]);
189  $this->setIdenticalScoring($data["identical_scoring"]);
190  // replacement of old syntax with new syntax
191  include_once("./Services/RTE/classes/class.ilRTE.php");
192  $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
193  $this->setTextgapRating($data["textgap_rating"]);
194  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
195 
196  // open the cloze gaps with all answers
197  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
198  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
199  $statement = $ilDB->prepare("SELECT * FROM qpl_answer_cloze WHERE question_fi = ? ORDER BY gap_id, aorder ASC",
200  array(
201  "integer"
202  )
203  );
204  $result = $ilDB->execute($statement,
205  array(
206  $question_id
207  )
208  );
209  if ($result->numRows() > 0)
210  {
211  $this->gaps = array();
212  while ($data = $ilDB->fetchAssoc($result))
213  {
214  switch ($data["cloze_type"])
215  {
216  case CLOZE_TEXT:
217  if (!array_key_exists($data["gap_id"], $this->gaps))
218  {
219  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
220  }
221  $answer = new assAnswerCloze(
222  $data["answertext"],
223  $data["points"],
224  $data["aorder"]
225  );
226  $this->gaps[$data["gap_id"]]->addItem($answer);
227  break;
228  case CLOZE_SELECT:
229  if (!array_key_exists($data["gap_id"], $this->gaps))
230  {
231  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
232  $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
233  }
234  $answer = new assAnswerCloze(
235  $data["answertext"],
236  $data["points"],
237  $data["aorder"]
238  );
239  $this->gaps[$data["gap_id"]]->addItem($answer);
240  break;
241  case CLOZE_NUMERIC:
242  if (!array_key_exists($data["gap_id"], $this->gaps))
243  {
244  $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
245  }
246  $answer = new assAnswerCloze(
247  $data["answertext"],
248  $data["points"],
249  $data["aorder"]
250  );
251  $answer->setLowerBound($data["lowerlimit"]);
252  $answer->setUpperBound($data["upperlimit"]);
253  $this->gaps[$data["gap_id"]]->addItem($answer);
254  break;
255  }
256  }
257  }
258  }
259  parent::loadFromDb($question_id);
260  }
261 
270  function saveToDb($original_id = "")
271  {
272  global $ilDB;
273 
274  include_once "./Services/Math/classes/class.EvalMath.php";
275  $eval = new EvalMath();
276  $eval->suppress_errors = TRUE;
277 
278  $estw_time = $this->getEstimatedWorkingTime();
279  $estw_time = sprintf("%02d:%02d:%02d", $estw_time['h'], $estw_time['m'], $estw_time['s']);
280 
281  // cleanup RTE images which are not inserted into the question text
282  include_once("./Services/RTE/classes/class.ilRTE.php");
283  if ($this->id == -1)
284  {
285  $now = getdate();
286  $created = sprintf("%04d%02d%02d%02d%02d%02d", $now['year'], $now['mon'], $now['mday'], $now['hours'], $now['minutes'], $now['seconds']);
287  $statement = $ilDB->prepareManip("INSERT INTO qpl_questions (question_id, question_type_fi, obj_fi, title, comment, points, author, " .
288  "owner, question_text, working_time, complete, created, original_id, TIMESTAMP) " .
289  "VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)",
290  array(
291  "integer",
292  "integer",
293  "text",
294  "text",
295  "float",
296  "text",
297  "integer",
298  "text",
299  "time",
300  "text",
301  "text",
302  "integer"
303  )
304  );
305  $data = array(
306  $this->getQuestionTypeID(),
307  $this->getObjId(),
308  $this->getTitle(),
309  $this->getComment(),
310  $this->getMaximumPoints(),
311  $this->getAuthor(),
312  $this->getOwner(),
314  $estw_time,
315  ($this->isComplete()) ? "1" : "0",
316  $created,
317  ($original_id > 0) ? $original_id : NULL
318  );
319  $affectedRows = $ilDB->execute($statement, $data);
320 
321  $this->setId($ilDB->getLastInsertId());
322  $statement = $ilDB->prepareManip("INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, textgap_rating, identical_scoring, fixed_textlen) VALUES (?, ?, ?, ?)",
323  array(
324  "integer",
325  "text",
326  "text",
327  "integer"
328  )
329  );
330  $data = array(
331  $this->getId(),
332  $this->getTextgapRating(),
333  $this->getIdenticalScoring(),
334  $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL
335  );
336  $affectedRows = $ilDB->execute($statement, $data);
337 
338  $this->createPageObject();
339 
340  if ($this->getTestId() > 0)
341  {
342  $this->insertIntoTest($this->getTestId());
343  }
344  }
345  else
346  {
347  $statement = $ilDB->prepareManip("UPDATE qpl_questions SET obj_fi = ?, title = ?, comment = ?, points = ?, author = ?, " .
348  "question_text = ?, working_time = ?, complete = ? WHERE question_id = ?",
349  array(
350  "integer",
351  "text",
352  "text",
353  "float",
354  "text",
355  "text",
356  "time",
357  "text",
358  "integer"
359  )
360  );
361  $data = array(
362  $this->getObjId(),
363  $this->getTitle(),
364  $this->getComment(),
365  $this->getMaximumPoints(),
366  $this->getAuthor(),
368  $estw_time,
369  ($this->isComplete()) ? "1" : "0",
370  $this->getId()
371  );
372  $affectedRows = $ilDB->execute($statement, $data);
373 
374  $statement = $ilDB->prepareManip("UPDATE " . $this->getAdditionalTableName() . " SET textgap_rating = ?, fixed_textlen = ?, identical_scoring = ? WHERE question_fi = ?",
375  array(
376  "text",
377  "integer",
378  "text",
379  "integer"
380  )
381  );
382  $data = array(
383  $this->getTextgapRating(),
384  $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL,
385  $this->getIdenticalScoring(),
386  $this->getId()
387  );
388  $affectedRows = $ilDB->execute($statement, $data);
389  }
390 
391  $statement = $ilDB->prepareManip("DELETE FROM qpl_answer_cloze WHERE question_fi = ?",
392  array(
393  "integer"
394  )
395  );
396  $data = array(
397  $this->getId()
398  );
399  $affectedRows = $ilDB->execute($statement, $data);
400 
401  foreach ($this->gaps as $key => $gap)
402  {
403  foreach ($gap->getItems() as $item)
404  {
405  $query = "";
406  switch ($gap->getType())
407  {
408  case CLOZE_TEXT:
409  $statement = $ilDB->prepareManip("INSERT INTO qpl_answer_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type) VALUES (NULL, ?, ?, ?, ?, ?, ?)",
410  array(
411  "integer",
412  "integer",
413  "text",
414  "float",
415  "integer",
416  "text"
417  )
418  );
419  $data = array(
420  $this->getId(),
421  $key,
422  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
423  $item->getPoints(),
424  $item->getOrder(),
425  $gap->getType()
426  );
427  break;
428  case CLOZE_SELECT:
429  $statement = $ilDB->prepareManip("INSERT INTO qpl_answer_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, shuffle) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)",
430  array(
431  "integer",
432  "integer",
433  "text",
434  "float",
435  "integer",
436  "text",
437  "text"
438  )
439  );
440  $data = array(
441  $this->getId(),
442  $key,
443  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
444  $item->getPoints(),
445  $item->getOrder(),
446  $gap->getType(),
447  ($gap->getShuffle()) ? "1" : "0"
448  );
449  break;
450  case CLOZE_NUMERIC:
451  $statement = $ilDB->prepareManip("INSERT INTO qpl_answer_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, lowerlimit, upperlimit) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?)",
452  array(
453  "integer",
454  "integer",
455  "text",
456  "float",
457  "integer",
458  "text",
459  "text",
460  "text"
461  )
462  );
463  $data = array(
464  $this->getId(),
465  $key,
466  strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
467  $item->getPoints(),
468  $item->getOrder(),
469  $gap->getType(),
470  ($eval->e($item->getLowerBound() !== FALSE) && strlen($item->getLowerBound()) > 0) ? $item->getLowerBound() : "0",
471  ($eval->e($item->getUpperBound() !== FALSE) && strlen($item->getUpperBound()) > 0) ? $item->getUpperBound() : "0"
472  );
473  break;
474  }
475  $affectedRows = $ilDB->execute($statement, $data);
476  }
477  }
479  }
480 
487  function getGaps()
488  {
489  return $this->gaps;
490  }
491 
492 
499  function flushGaps()
500  {
501  $this->gaps = array();
502  }
503 
513  function setClozeText($cloze_text = "")
514  {
515  $this->gaps = array();
516  $cloze_text = $this->cleanQuestiontext($cloze_text);
517  $this->question = $cloze_text;
519  }
520 
528  function getClozeText()
529  {
530  return $this->question;
531  }
532 
540  function getStartTag()
541  {
542  return $this->start_tag;
543  }
544 
552  function setStartTag($start_tag = "[gap]")
553  {
554  $this->start_tag = $start_tag;
555  }
556 
564  function getEndTag()
565  {
566  return $this->end_tag;
567  }
568 
576  function setEndTag($end_tag = "[/gap]")
577  {
578  $this->end_tag = $end_tag;
579  }
580 
588  {
589  include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
590  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
591  $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
592  preg_match_all($search_pattern, $this->getClozeText(), $found);
593  $this->gaps = array();
594  if (count($found[0]))
595  {
596  foreach ($found[1] as $gap_index => $answers)
597  {
598  // create text gaps by default
599  $gap = new assClozeGap(CLOZE_TEXT);
600  $textparams = preg_split("/(?<!\\\\),/", $answers);
601  foreach ($textparams as $key => $value)
602  {
603  $answer = new assAnswerCloze($value, 0, $key);
604  $gap->addItem($answer);
605  }
606  $this->gaps[$gap_index] = $gap;
607  }
608  }
609  }
610 
616  function setGapType($gap_index, $gap_type)
617  {
618  if (array_key_exists($gap_index, $this->gaps))
619  {
620  $this->gaps[$gap_index]->setType($gap_type);
621  }
622  }
623 
633  function setGapShuffle($gap_index = 0, $shuffle = 1)
634  {
635  if (array_key_exists($gap_index, $this->gaps))
636  {
637  $this->gaps[$gap_index]->setShuffle($shuffle);
638  }
639  }
640 
647  function clearGapAnswers()
648  {
649  foreach ($this->gaps as $gap_index => $gap)
650  {
651  $this->gaps[$gap_index]->clearItems();
652  }
653  }
654 
662  function getGapCount()
663  {
664  if (is_array($this->gaps))
665  {
666  return count($this->gaps);
667  }
668  else
669  {
670  return 0;
671  }
672  }
673 
684  function addGapAnswer($gap_index, $order, $answer)
685  {
686  if (array_key_exists($gap_index, $this->gaps))
687  {
688  if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC)
689  {
690  // only allow notation with "." for real numbers
691  $answer = str_replace(",", ".", $answer);
692  }
693  $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
694  }
695  }
696 
705  function getGap($gap_index = 0)
706  {
707  if (array_key_exists($gap_index, $this->gaps))
708  {
709  return $this->gaps[$gap_index];
710  }
711  else
712  {
713  return NULL;
714  }
715  }
716 
727  function setGapAnswerPoints($gap_index, $order, $points)
728  {
729  if (array_key_exists($gap_index, $this->gaps))
730  {
731  $this->gaps[$gap_index]->setItemPoints($order, $points);
732  }
733  }
734 
743  function addGapText($gap_index)
744  {
745  if (array_key_exists($gap_index, $this->gaps))
746  {
747  include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
748  $answer = new assAnswerCloze(
749  "",
750  0,
751  $this->gaps[$gap_index]->getItemCount()
752  );
753  $this->gaps[$gap_index]->addItem($answer);
754  }
755  }
756 
765  function addGapAtIndex($gap, $index)
766  {
767  $this->gaps[$index] = $gap;
768  }
769 
780  function setGapAnswerLowerBound($gap_index, $order, $bound)
781  {
782  if (array_key_exists($gap_index, $this->gaps))
783  {
784  $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
785  }
786  }
787 
798  function setGapAnswerUpperBound($gap_index, $order, $bound)
799  {
800  if (array_key_exists($gap_index, $this->gaps))
801  {
802  $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
803  }
804  }
805 
812  function getMaximumPoints()
813  {
814  $points = 0;
815  foreach ($this->gaps as $gap_index => $gap)
816  {
817  if ($gap->getType() == CLOZE_TEXT)
818  {
819  $gap_max_points = 0;
820  foreach ($gap->getItems() as $item)
821  {
822  if ($item->getPoints() > $gap_max_points)
823  {
824  $gap_max_points = $item->getPoints();
825  }
826  }
827  $points += $gap_max_points;
828  }
829  else if ($gap->getType() == CLOZE_SELECT)
830  {
831  $srpoints = 0;
832  foreach ($gap->getItems() as $item)
833  {
834  if ($item->getPoints() > $srpoints)
835  {
836  $srpoints = $item->getPoints();
837  }
838  }
839  $points += $srpoints;
840  }
841  else if ($gap->getType() == CLOZE_NUMERIC)
842  {
843  $numpoints = 0;
844  foreach ($gap->getItems() as $item)
845  {
846  if ($item->getPoints() > $numpoints)
847  {
848  $numpoints = $item->getPoints();
849  }
850  }
851  $points += $numpoints;
852  }
853  }
854  return $points;
855  }
856 
862  function duplicate($for_test = true, $title = "", $author = "", $owner = "")
863  {
864  if ($this->id <= 0)
865  {
866  // The question has not been saved. It cannot be duplicated
867  return;
868  }
869  // duplicate the question in database
870  $this_id = $this->getId();
871  $clone = $this;
872  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
874  $clone->id = -1;
875  if ($title)
876  {
877  $clone->setTitle($title);
878  }
879  if ($author)
880  {
881  $clone->setAuthor($author);
882  }
883  if ($owner)
884  {
885  $clone->setOwner($owner);
886  }
887  if ($for_test)
888  {
889  $clone->saveToDb($original_id);
890  }
891  else
892  {
893  $clone->saveToDb();
894  }
895 
896  // copy question page content
897  $clone->copyPageOfQuestion($this_id);
898  // copy XHTML media objects
899  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
900  // duplicate the generic feedback
901  $clone->duplicateFeedbackGeneric($this_id);
902 
903  return $clone->getId();
904  }
905 
911  function copyObject($target_questionpool, $title = "")
912  {
913  if ($this->getId() <= 0)
914  {
915  // The question has not been saved. It cannot be duplicated
916  return;
917  }
918  $clone = $this;
919  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
921  $clone->id = -1;
922  $source_questionpool = $this->getObjId();
923  $clone->setObjId($target_questionpool);
924  if ($title)
925  {
926  $clone->setTitle($title);
927  }
928  $clone->saveToDb();
929 
930  // copy question page content
931  $clone->copyPageOfQuestion($original_id);
932  // copy XHTML media objects
933  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
934  // duplicate the generic feedback
935  $clone->duplicateFeedbackGeneric($original_id);
936 
937  return $clone->getId();
938  }
939 
946  {
947  $output = $this->getClozeText();
948  foreach ($this->getGaps() as $gap_index => $gap)
949  {
950  $answers = array();
951  foreach ($gap->getItemsRaw() as $item)
952  {
953  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
954  }
955  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . ilUtil::prepareFormOutput(join(",", $answers)) . "[/_gap]", $output, 1);
956  }
957  $output = str_replace("_gap]", "gap]", $output);
958  $this->question = $output;
959  }
960 
970  function deleteAnswerText($gap_index, $answer_index)
971  {
972  if (array_key_exists($gap_index, $this->gaps))
973  {
974  if ($this->gaps[$gap_index]->getItemCount() == 1)
975  {
976  // this is the last answer text => remove the gap
977  $this->deleteGap($gap_index);
978  }
979  else
980  {
981  // remove the answer text
982  $this->gaps[$gap_index]->deleteItem($answer_index);
983  $this->updateClozeTextFromGaps();
984  }
985  }
986  }
987 
996  function deleteGap($gap_index)
997  {
998  if (array_key_exists($gap_index, $this->gaps))
999  {
1000  $output = $this->getClozeText();
1001  foreach ($this->getGaps() as $replace_gap_index => $gap)
1002  {
1003  $answers = array();
1004  foreach ($gap->getItemsRaw() as $item)
1005  {
1006  array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1007  }
1008  if ($replace_gap_index == $gap_index)
1009  {
1010  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "", $output, 1);
1011  }
1012  else
1013  {
1014  $output = preg_replace("/\[gap\].*?\[\/gap\]/", "[_gap]" . join(",", $answers) . "[/_gap]", $output, 1);
1015  }
1016  }
1017  $output = str_replace("_gap]", "gap]", $output);
1018  $this->question = $output;
1019  unset($this->gaps[$gap_index]);
1020  $this->gaps = array_values($this->gaps);
1021  }
1022  }
1023 
1033  function getTextgapPoints($a_original, $a_entered, $max_points)
1034  {
1035  include_once "./Services/Utilities/classes/class.ilStr.php";
1036  $result = 0;
1037  $gaprating = $this->getTextgapRating();
1038  switch ($gaprating)
1039  {
1041  if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) $result = $max_points;
1042  break;
1044  if (strcmp($a_original, $a_entered) == 0) $result = $max_points;
1045  break;
1047  if (levenshtein($a_original, $a_entered) <= 1) $result = $max_points;
1048  break;
1050  if (levenshtein($a_original, $a_entered) <= 2) $result = $max_points;
1051  break;
1053  if (levenshtein($a_original, $a_entered) <= 3) $result = $max_points;
1054  break;
1056  if (levenshtein($a_original, $a_entered) <= 4) $result = $max_points;
1057  break;
1059  if (levenshtein($a_original, $a_entered) <= 5) $result = $max_points;
1060  break;
1061  }
1062  return $result;
1063  }
1064 
1074  function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1075  {
1076  include_once "./Services/Math/classes/class.EvalMath.php";
1077  $eval = new EvalMath();
1078  $eval->suppress_errors = TRUE;
1079  $result = 0;
1080  if (($eval->e($lowerBound) !== FALSE) && ($eval->e($upperBound) !== FALSE))
1081  {
1082  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1083  }
1084  else if ($eval->e($lowerBound) !== FALSE)
1085  {
1086  if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) $result = $max_points;
1087  }
1088  else if ($eval->e($upperBound) !== FALSE)
1089  {
1090  if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) $result = $max_points;
1091  }
1092  else
1093  {
1094  if ($eval->e($a_entered) == $eval->e($a_original)) $result = $max_points;
1095  }
1096  return $result;
1097  }
1098 
1108  function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
1109  {
1110  global $ilDB;
1111 
1112  $found_value1 = array();
1113  $found_value2 = array();
1114  $detailed = array();
1115  if (is_null($pass))
1116  {
1117  $pass = $this->getSolutionMaxPass($active_id);
1118  }
1119  $statement = $ilDB->prepare("SELECT * FROM tst_solutions WHERE active_fi = ? AND question_fi = ? AND pass = ?",
1120  array(
1121  "integer",
1122  "integer",
1123  "integer"
1124  )
1125  );
1126  $result = $ilDB->execute($statement,
1127  array(
1128  $active_id,
1129  $this->getId(),
1130  $pass
1131  )
1132  );
1133  $user_result = array();
1134  while ($data = $ilDB->fetchAssoc($result))
1135  {
1136  if (strcmp($data["value2"], "") != 0)
1137  {
1138  $user_result[$data["value1"]] = array(
1139  "gap_id" => $data["value1"],
1140  "value" => $data["value2"]
1141  );
1142  }
1143  }
1144  $points = 0;
1145  $counter = 0;
1146  $solution_values_text = array(); // for identical scoring checks
1147  $solution_values_select = array(); // for identical scoring checks
1148  $solution_values_numeric = array(); // for identical scoring checks
1149  foreach ($user_result as $gap_id => $value)
1150  {
1151  if (array_key_exists($gap_id, $this->gaps))
1152  {
1153  switch ($this->gaps[$gap_id]->getType())
1154  {
1155  case CLOZE_TEXT:
1156  $gappoints = 0;
1157  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1158  {
1159  $answer = $this->gaps[$gap_id]->getItem($order);
1160  $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1161  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1162  }
1163  if (!$this->getIdenticalScoring())
1164  {
1165  // check if the same solution text was already entered
1166  if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0))
1167  {
1168  $gappoints = 0;
1169  }
1170  }
1171  $points += $gappoints;
1172  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1173  array_push($solution_values_text, $value["value"]);
1174  break;
1175  case CLOZE_NUMERIC:
1176  $gappoints = 0;
1177  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1178  {
1179  $answer = $this->gaps[$gap_id]->getItem($order);
1180  $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1181  if ($gotpoints > $gappoints) $gappoints = $gotpoints;
1182  }
1183  if (!$this->getIdenticalScoring())
1184  {
1185  // check if the same solution value was already entered
1186  include_once "./Services/Math/classes/class.EvalMath.php";
1187  $eval = new EvalMath();
1188  $eval->suppress_errors = TRUE;
1189  $found_value = FALSE;
1190  foreach ($solution_values_numeric as $solval)
1191  {
1192  if ($eval->e($solval) == $eval->e($value["value"]))
1193  {
1194  $found_value = TRUE;
1195  }
1196  }
1197  if ($found_value && ($gappoints > 0))
1198  {
1199  $gappoints = 0;
1200  }
1201  }
1202  $points += $gappoints;
1203  $detailed[$gap_id] = array("points" =>$gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? TRUE : FALSE, "positive" => ($gappoints > 0) ? TRUE : FALSE);
1204  array_push($solution_values_numeric, $value["value"]);
1205  break;
1206  case CLOZE_SELECT:
1207  if ($value["value"] >= 0)
1208  {
1209  for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++)
1210  {
1211  $answer = $this->gaps[$gap_id]->getItem($order);
1212  if ($value["value"] == $answer->getOrder())
1213  {
1214  $answerpoints = $answer->getPoints();
1215  if (!$this->getIdenticalScoring())
1216  {
1217  // check if the same solution value was already entered
1218  if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0))
1219  {
1220  $answerpoints = 0;
1221  }
1222  }
1223  $points += $answerpoints;
1224  $detailed[$gap_id] = array("points" =>$answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? TRUE : FALSE, "positive" => ($answerpoints > 0) ? TRUE : FALSE);
1225  array_push($solution_values_select, $answer->getAnswertext());
1226  }
1227  }
1228  }
1229  break;
1230  }
1231  }
1232  }
1233  if ($returndetails)
1234  {
1235  return $detailed;
1236  }
1237  else
1238  {
1239  $points = parent::calculateReachedPoints($active_id, $pass = NULL, $points);
1240  return $points;
1241  }
1242  }
1243 
1252  function saveWorkingData($active_id, $pass = NULL)
1253  {
1254  global $ilDB;
1255  global $ilUser;
1256  if (is_null($pass))
1257  {
1258  include_once "./Modules/Test/classes/class.ilObjTest.php";
1259  $pass = ilObjTest::_getPass($active_id);
1260  }
1261 
1262  $statement = $ilDB->prepareManip("DELETE FROM tst_solutions WHERE active_fi = ? AND question_fi = ? AND pass = ?",
1263  array(
1264  "integer",
1265  "integer",
1266  "integer"
1267  )
1268  );
1269  $data = array(
1270  $active_id,
1271  $this->getId(),
1272  $pass
1273  );
1274  $affectedRows = $ilDB->execute($statement, $data);
1275 
1276  $entered_values = 0;
1277  foreach ($_POST as $key => $value)
1278  {
1279  if (preg_match("/^gap_(\d+)/", $key, $matches))
1280  {
1281  $value = ilUtil::stripSlashes($value, FALSE);
1282  if (strlen($value))
1283  {
1284  $gap = $this->getGap($matches[1]);
1285  if (is_object($gap))
1286  {
1287  if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1)))
1288  {
1289  if ($gap->getType() == CLOZE_NUMERIC)
1290  {
1291  $value = str_replace(",", ".", $value);
1292  }
1293  $statement = $ilDB->prepareManip("INSERT INTO tst_solutions (solution_id, active_fi, question_fi, value1, value2, pass, TIMESTAMP) VALUES (NULL, ?, ?, ?, ?, ?, NULL)",
1294  array(
1295  "integer",
1296  "integer",
1297  "text",
1298  "text",
1299  "integer"
1300  )
1301  );
1302  $data = array(
1303  $active_id,
1304  $this->getId(),
1305  trim($matches[1]),
1306  trim($value),
1307  $pass
1308  );
1309  $affectedRows = $ilDB->execute($statement, $data);
1310  $entered_values++;
1311  }
1312  }
1313  }
1314  }
1315  }
1316  if ($entered_values)
1317  {
1318  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1320  {
1321  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1322  }
1323  }
1324  else
1325  {
1326  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1328  {
1329  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1330  }
1331  }
1332  parent::saveWorkingData($active_id, $pass);
1333  return TRUE;
1334  }
1335 
1342  function getQuestionType()
1343  {
1344  return "assClozeTest";
1345  }
1346 
1354  function getTextgapRating()
1355  {
1356  return $this->textgap_rating;
1357  }
1358 
1366  function setTextgapRating($a_textgap_rating)
1367  {
1368  switch ($a_textgap_rating)
1369  {
1377  $this->textgap_rating = $a_textgap_rating;
1378  break;
1379  default:
1380  $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1381  break;
1382  }
1383  }
1384 
1393  {
1394  return ($this->identical_scoring) ? 1 : 0;
1395  }
1396 
1404  function setIdenticalScoring($a_identical_scoring)
1405  {
1406  $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1407  }
1408 
1416  {
1417  return "qpl_question_cloze";
1418  }
1419 
1427  {
1428  return "qpl_answer_cloze";
1429  }
1430 
1437  function setFixedTextLength($a_text_len)
1438  {
1439  $this->fixedTextLength = $a_text_len;
1440  }
1441 
1449  {
1450  return $this->fixedTextLength;
1451  }
1452 
1461  function getMaximumGapPoints($gap_index)
1462  {
1463  $points = 0;
1464  if (array_key_exists($gap_index, $this->gaps))
1465  {
1466  $gap =& $this->gaps[$gap_index];
1467  foreach ($gap->getItems() as $answer)
1468  {
1469  if ($answer->getPoints() > $gap_max_points)
1470  {
1471  $gap_max_points = $answer->getPoints();
1472  }
1473  }
1474  $points += $gap_max_points;
1475  }
1476  return $points;
1477  }
1478 
1484  {
1486  }
1487 
1500  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
1501  {
1502  include_once ("./classes/class.ilExcelUtils.php");
1503  $solution = $this->getSolutionValues($active_id, $pass);
1504  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
1505  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
1506  $i = 1;
1507  foreach ($this->getGaps() as $gap_index => $gap)
1508  {
1509  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($this->lng->txt("gap") . " $i"), $format_bold);
1510  $checked = FALSE;
1511  foreach ($solution as $solutionvalue)
1512  {
1513  if ($gap_index == $solutionvalue["value1"])
1514  {
1515  switch ($gap->getType())
1516  {
1517  case CLOZE_SELECT:
1518  $worksheet->writeString($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1519  break;
1520  case CLOZE_NUMERIC:
1521  case CLOZE_TEXT:
1522  $worksheet->writeString($startrow + $i, 1, $solutionvalue["value2"]);
1523  break;
1524  }
1525  }
1526  }
1527  $i++;
1528  }
1529  return $startrow + $i + 1;
1530  }
1531 }
1532 ?>