ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assTextSubset.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
7 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
8 require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
9 require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
10 
27 {
35  var $answers;
36 
45 
54 
66  public function __construct(
67  $title = "",
68  $comment = "",
69  $author = "",
70  $owner = -1,
71  $question = ""
72  )
73  {
75  $this->answers = array();
76  $this->correctanswers = 0;
77  }
78 
85  function isComplete()
86  {
87  if (
88  strlen($this->title)
89  && $this->author
90  && $this->question &&
91  count($this->answers) >= $this->correctanswers
92  && $this->getMaximumPoints() > 0
93  )
94  {
95  return true;
96  }
97  return false;
98  }
99 
106  public function saveToDb($original_id = "")
107  {
108  global $ilDB;
109 
113 
115  }
116 
124  function loadFromDb($question_id)
125  {
126  global $ilDB;
127 
128  $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",
129  array("integer"),
130  array($question_id)
131  );
132  if ($result->numRows() == 1)
133  {
134  $data = $ilDB->fetchAssoc($result);
135  $this->setId($question_id);
136  $this->setObjId($data["obj_fi"]);
137  $this->setNrOfTries($data['nr_of_tries']);
138  $this->setTitle($data["title"]);
139  $this->setComment($data["description"]);
140  $this->setOriginalId($data["original_id"]);
141  $this->setAuthor($data["author"]);
142  $this->setPoints($data["points"]);
143  $this->setOwner($data["owner"]);
144  include_once("./Services/RTE/classes/class.ilRTE.php");
145  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
146  $this->setCorrectAnswers($data["correctanswers"]);
147  $this->setTextRating($data["textgap_rating"]);
148  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
149 
150  try
151  {
152  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
153  }
155  {
156  }
157  }
158 
159 
160  $result = $ilDB->queryF("SELECT * FROM qpl_a_textsubset WHERE question_fi = %s ORDER BY aorder ASC",
161  array('integer'),
162  array($question_id)
163  );
164  include_once "./Modules/TestQuestionPool/classes/class.assAnswerBinaryStateImage.php";
165  if ($result->numRows() > 0)
166  {
167  while ($data = $ilDB->fetchAssoc($result))
168  {
169  array_push($this->answers, new ASS_AnswerBinaryStateImage($data["answertext"], $data["points"], $data["aorder"]));
170  }
171  }
172 
173  parent::loadFromDb($question_id);
174  }
175 
181  function addAnswer($answertext, $points, $order)
182  {
183  include_once "./Modules/TestQuestionPool/classes/class.assAnswerBinaryStateImage.php";
184  if (array_key_exists($order, $this->answers))
185  {
186  // insert answer
187  $answer = new ASS_AnswerBinaryStateImage($answertext, $points, $order);
188  $newchoices = array();
189  for ($i = 0; $i < $order; $i++)
190  {
191  array_push($newchoices, $this->answers[$i]);
192  }
193  array_push($newchoices, $answer);
194  for ($i = $order; $i < count($this->answers); $i++)
195  {
196  $changed = $this->answers[$i];
197  $changed->setOrder($i+1);
198  array_push($newchoices, $changed);
199  }
200  $this->answers = $newchoices;
201  }
202  else
203  {
204  // add answer
205  array_push($this->answers, new ASS_AnswerBinaryStateImage($answertext, $points, count($this->answers)));
206  }
207  }
208 
214  function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
215  {
216  if ($this->id <= 0)
217  {
218  // The question has not been saved. It cannot be duplicated
219  return;
220  }
221  // duplicate the question in database
222  $this_id = $this->getId();
223  $thisObjId = $this->getObjId();
224 
225  $clone = $this;
226  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
228  $clone->id = -1;
229 
230  if( (int)$testObjId > 0 )
231  {
232  $clone->setObjId($testObjId);
233  }
234 
235  if ($title)
236  {
237  $clone->setTitle($title);
238  }
239 
240  if ($author)
241  {
242  $clone->setAuthor($author);
243  }
244  if ($owner)
245  {
246  $clone->setOwner($owner);
247  }
248 
249  if ($for_test)
250  {
251  $clone->saveToDb($original_id);
252  }
253  else
254  {
255  $clone->saveToDb();
256  }
257 
258  // copy question page content
259  $clone->copyPageOfQuestion($this_id);
260  // copy XHTML media objects
261  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
262 
263  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
264 
265  return $clone->id;
266  }
267 
273  function copyObject($target_questionpool_id, $title = "")
274  {
275  if ($this->id <= 0)
276  {
277  // The question has not been saved. It cannot be duplicated
278  return;
279  }
280  // duplicate the question in database
281  $clone = $this;
282  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
284  $clone->id = -1;
285  $source_questionpool_id = $this->getObjId();
286  $clone->setObjId($target_questionpool_id);
287  if ($title)
288  {
289  $clone->setTitle($title);
290  }
291  $clone->saveToDb();
292  // copy question page content
293  $clone->copyPageOfQuestion($original_id);
294  // copy XHTML media objects
295  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
296 
297  $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
298 
299  return $clone->id;
300  }
301 
302  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
303  {
304  if ($this->id <= 0)
305  {
306  // The question has not been saved. It cannot be duplicated
307  return;
308  }
309 
310  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
311 
312  $sourceQuestionId = $this->id;
313  $sourceParentId = $this->getObjId();
314 
315  // duplicate the question in database
316  $clone = $this;
317  $clone->id = -1;
318 
319  $clone->setObjId($targetParentId);
320 
321  if ($targetQuestionTitle)
322  {
323  $clone->setTitle($targetQuestionTitle);
324  }
325 
326  $clone->saveToDb();
327  // copy question page content
328  $clone->copyPageOfQuestion($sourceQuestionId);
329  // copy XHTML media objects
330  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
331 
332  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
333 
334  return $clone->id;
335  }
336 
344  function getAnswerCount()
345  {
346  return count($this->answers);
347  }
348 
358  function getAnswer($index = 0)
359  {
360  if ($index < 0) return NULL;
361  if (count($this->answers) < 1) return NULL;
362  if ($index >= count($this->answers)) return NULL;
363 
364  return $this->answers[$index];
365  }
366 
375  function deleteAnswer($index = 0)
376  {
377  if ($index < 0) return;
378  if (count($this->answers) < 1) return;
379  if ($index >= count($this->answers)) return;
380  unset($this->answers[$index]);
381  $this->answers = array_values($this->answers);
382  for ($i = 0; $i < count($this->answers); $i++)
383  {
384  if ($this->answers[$i]->getOrder() > $index)
385  {
386  $this->answers[$i]->setOrder($i);
387  }
388  }
389  }
390 
397  function flushAnswers()
398  {
399  $this->answers = array();
400  }
401 
408  function getMaximumPoints()
409  {
410  $points = array();
411  foreach ($this->answers as $answer)
412  {
413  if ($answer->getPoints() > 0)
414  {
415  array_push($points, $answer->getPoints());
416  }
417  }
418  rsort($points, SORT_NUMERIC);
419  $maxpoints = 0;
420  for ($counter = 0; $counter < $this->getCorrectAnswers(); $counter++)
421  {
422  $maxpoints += $points[$counter];
423  }
424  return $maxpoints;
425  }
426 
434  {
435  $available_answers = array();
436  foreach ($this->answers as $answer)
437  {
438  array_push($available_answers, $answer->getAnswertext());
439  }
440  return $available_answers;
441  }
442 
453  function isAnswerCorrect($answers, $answer)
454  {
455  include_once "./Services/Utilities/classes/class.ilStr.php";
456  $result = 0;
457  $textrating = $this->getTextRating();
458  foreach ($answers as $key => $value)
459  {
460  switch ($textrating)
461  {
463  if (strcmp(ilStr::strToLower($value), ilStr::strToLower($answer)) == 0 && $this->answers[$key]->getPoints() > 0) return $key;
464  break;
466  if (strcmp($value, $answer) == 0 && $this->answers[$key]->getPoints() > 0) return $key;
467  break;
469  if (levenshtein($value, $answer) <= 1 && $this->answers[$key]->getPoints() > 0) return $key;
470  break;
472  if (levenshtein($value, $answer) <= 2 && $this->answers[$key]->getPoints() > 0) return $key;
473  break;
475  if (levenshtein($value, $answer) <= 3 && $this->answers[$key]->getPoints() > 0) return $key;
476  break;
478  if (levenshtein($value, $answer) <= 4 && $this->answers[$key]->getPoints() > 0) return $key;
479  break;
481  if (levenshtein($value, $answer) <= 5 && $this->answers[$key]->getPoints() > 0) return $key;
482  break;
483  }
484  }
485  return FALSE;
486  }
487 
495  function getTextRating()
496  {
497  return $this->text_rating;
498  }
499 
507  function setTextRating($a_text_rating)
508  {
509  switch ($a_text_rating)
510  {
518  $this->text_rating = $a_text_rating;
519  break;
520  default:
521  $this->text_rating = TEXTGAP_RATING_CASEINSENSITIVE;
522  break;
523  }
524  }
525 
536  public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
537  {
538  if( $returndetails )
539  {
540  throw new ilTestException('return details not implemented for '.__METHOD__);
541  }
542 
543  global $ilDB;
544 
545 
546  if (is_null($pass))
547  {
548  $pass = $this->getSolutionMaxPass($active_id);
549  }
550  $result = $this->getCurrentSolutionResultSet($active_id, $pass);
551 
552  $enteredTexts = array();
553  while ($data = $ilDB->fetchAssoc($result))
554  {
555  $enteredTexts[] = $data["value1"];
556  }
557 
558  $points = $this->calculateReachedPointsForSolution($enteredTexts);
559 
560  return $points;
561  }
562 
569  function setCorrectAnswers($a_correct_answers)
570  {
571  $this->correctanswers = $a_correct_answers;
572  }
573 
580  function getCorrectAnswers()
581  {
582  return $this->correctanswers;
583  }
584 
593  public function saveWorkingData($active_id, $pass = NULL)
594  {
595  global $ilDB;
596  global $ilUser;
597 
598  if (is_null($pass))
599  {
600  include_once "./Modules/Test/classes/class.ilObjTest.php";
601  $pass = ilObjTest::_getPass($active_id);
602  }
603  $entered_values = 0;
604 
605  $solutionSubmit = $this->getSolutionSubmit();
606 
607  $this->getProcessLocker()->requestUserSolutionUpdateLock();
608 
609  $affectedRows = $this->removeCurrentSolution($active_id, $pass);
610 
611  foreach($solutionSubmit as $value)
612  {
613  if (strlen($value))
614  {
615  $this->saveCurrentSolution($active_id, $pass, $value, null);
616  $entered_values++;
617  }
618  }
619 
620  $this->getProcessLocker()->releaseUserSolutionUpdateLock();
621 
622  if ($entered_values)
623  {
624  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
626  {
627  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
628  }
629  }
630  else
631  {
632  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
634  {
635  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
636  }
637  }
638 
639  return true;
640  }
641 
642  public function saveAdditionalQuestionDataToDb()
643  {
645  global $ilDB;
646 
647  // save additional data
648  $ilDB->manipulateF( "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
649  array( "integer" ),
650  array( $this->getId() )
651  );
652 
653  $ilDB->manipulateF( "INSERT INTO " . $this->getAdditionalTableName(
654  ) . " (question_fi, textgap_rating, correctanswers) VALUES (%s, %s, %s)",
655  array( "integer", "text", "integer" ),
656  array(
657  $this->getId(),
658  $this->getTextRating(),
659  $this->getCorrectAnswers()
660  )
661  );
662  }
663 
664  public function saveAnswerSpecificDataToDb()
665  {
667  global $ilDB;
668  $ilDB->manipulateF( "DELETE FROM qpl_a_textsubset WHERE question_fi = %s",
669  array( 'integer' ),
670  array( $this->getId() )
671  );
672 
673  foreach ($this->answers as $key => $value)
674  {
675  $answer_obj = $this->answers[$key];
676  $next_id = $ilDB->nextId( 'qpl_a_textsubset' );
677  $ilDB->manipulateF( "INSERT INTO qpl_a_textsubset (answer_id, question_fi, answertext, points, aorder, tstamp) VALUES (%s, %s, %s, %s, %s, %s)",
678  array( 'integer', 'integer', 'text', 'float', 'integer', 'integer' ),
679  array(
680  $next_id,
681  $this->getId(),
682  $answer_obj->getAnswertext(),
683  $answer_obj->getPoints(),
684  $answer_obj->getOrder(),
685  time()
686  )
687  );
688  }
689  }
690 
699  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered)
700  {
701  // nothing to rework!
702  }
703 
710  function getQuestionType()
711  {
712  return "assTextSubset";
713  }
714 
721  function &joinAnswers()
722  {
723  $join = array();
724  foreach ($this->answers as $answer)
725  {
726  if (!is_array($join[$answer->getPoints() . ""]))
727  {
728  $join[$answer->getPoints() . ""] = array();
729  }
730  array_push($join[$answer->getPoints() . ""], $answer->getAnswertext());
731  }
732  return $join;
733  }
734 
742  {
743  $maxwidth = 0;
744  foreach ($this->answers as $answer)
745  {
746  $len = strlen($answer->getAnswertext());
747  if ($len > $maxwidth) $maxwidth = $len;
748  }
749  return $maxwidth + 3;
750  }
751 
759  {
760  return "qpl_qst_textsubset";
761  }
762 
770  {
771  return "qpl_a_textsubset";
772  }
773 
779  {
781  }
782 
795  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
796  {
797  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
798  $solutions = $this->getSolutionValues($active_id, $pass);
799  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
800  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
801  $i = 1;
802  foreach ($solutions as $solution)
803  {
804  $worksheet->write($startrow + $i, 0, ilExcelUtils::_convert_text($solution["value1"]));
805  $i++;
806  }
807  return $startrow + $i + 1;
808  }
809 
810  public function getAnswers()
811  {
812  return $this->answers;
813  }
814 
818  public function toJSON()
819  {
820  include_once("./Services/RTE/classes/class.ilRTE.php");
821  $result = array();
822  $result['id'] = (int) $this->getId();
823  $result['type'] = (string) $this->getQuestionType();
824  $result['title'] = (string) $this->getTitle();
825  $result['question'] = $this->formatSAQuestion($this->getQuestion());
826  $result['nr_of_tries'] = (int) $this->getNrOfTries();
827  $result['matching_method'] = (string) $this->getTextRating();
828  $result['feedback'] = array(
829  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
830  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
831  );
832 
833  $answers = array();
834  foreach ($this->getAnswers() as $key => $answer_obj)
835  {
836  array_push($answers, array(
837  "answertext" => (string) $answer_obj->getAnswertext(),
838  "points" => (float)$answer_obj->getPoints(),
839  "order" => (int)$answer_obj->getOrder()
840  ));
841  }
842  $result['correct_answers'] = $answers;
843 
844  $answers = array();
845  for($loop = 1; $loop <= (int) $this->getCorrectAnswers(); $loop++)
846  {
847  array_push($answers, array(
848  "answernr" => $loop
849  ));
850  }
851  $result['answers'] = $answers;
852 
853  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
854  $result['mobs'] = $mobs;
855 
856  return json_encode($result);
857  }
858 
862  protected function getSolutionSubmit()
863  {
864  $solutionSubmit = array();
865  $purifier = $this->getHtmlUserSolutionPurifier();
866  foreach($_POST as $key => $val)
867  {
868  if(preg_match("/^TEXTSUBSET_(\d+)/", $key, $matches))
869  {
870  $val = trim($val);
871  if(strlen($val))
872  {
873  $val = $purifier->purify($val);
874  $solutionSubmit[] = $val;
875  }
876  }
877  }
878  return $solutionSubmit;
879  }
880 
885  protected function calculateReachedPointsForSolution($enteredTexts)
886  {
887  $available_answers = $this->getAvailableAnswers();
888  $points = 0;
889  foreach($enteredTexts as $enteredtext)
890  {
891  $index = $this->isAnswerCorrect($available_answers, $enteredtext);
892  if($index !== FALSE)
893  {
894  unset($available_answers[$index]);
895  $points += $this->answers[$index]->getPoints();
896  }
897  }
898  return $points;
899  }
900 
909  public function getOperators($expression)
910  {
911  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
913  }
914 
919  public function getExpressionTypes()
920  {
921  return array(
926  );
927  }
928 
937  public function getUserQuestionResult($active_id, $pass)
938  {
940  global $ilDB;
941  $result = new ilUserQuestionResult($this, $active_id, $pass);
942 
943  $data = $ilDB->queryF(
944  "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
945  SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
946  ) ORDER BY solution_id",
947  array("integer", "integer", "integer","integer", "integer", "integer"),
948  array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
949  );
950 
951  for($index = 1; $index <= $ilDB->numRows($data); ++$index)
952  {
953  $row = $ilDB->fetchAssoc($data);
954  $result->addKeyValue($index, $row["value1"]);
955  }
956 
957  $points = $this->calculateReachedPoints($active_id, $pass);
958  $max_points = $this->getMaximumPoints();
959 
960  $result->setReachedPercentage(($points/$max_points) * 100);
961 
962  return $result;
963  }
964 
973  public function getAvailableAnswerOptions($index = null)
974  {
975  if($index !== null)
976  {
977  return $this->getAnswer($index);
978  }
979  else
980  {
981  return $this->getAnswers();
982  }
983  }
984 }