ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
class.assErrorText.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 
24 {
25  protected $errortext;
26  protected $textsize;
27  protected $errordata;
28  protected $points_wrong;
29 
41  public function __construct(
42  $title = '',
43  $comment = '',
44  $author = '',
45  $owner = -1,
46  $question = ''
47  ) {
48  parent::__construct($title, $comment, $author, $owner, $question);
49  $this->errortext = '';
50  $this->textsize = 100.0;
51  $this->errordata = array();
52  }
53 
59  public function isComplete()
60  {
61  if (strlen($this->title)
62  && ($this->author)
63  && ($this->question)
64  && ($this->getMaximumPoints() > 0)) {
65  return true;
66  } else {
67  return false;
68  }
69  }
70 
75  public function saveToDb($original_id = "")
76  {
80  parent::saveToDb();
81  }
82 
83  public function saveAnswerSpecificDataToDb()
84  {
85  global $DIC;
86  $ilDB = $DIC['ilDB'];
87  $ilDB->manipulateF(
88  "DELETE FROM qpl_a_errortext WHERE question_fi = %s",
89  array( 'integer' ),
90  array( $this->getId() )
91  );
92 
93  $sequence = 0;
94  foreach ($this->errordata as $object) {
95  $next_id = $ilDB->nextId('qpl_a_errortext');
96  $ilDB->manipulateF(
97  "INSERT INTO qpl_a_errortext (answer_id, question_fi, text_wrong, text_correct, points, sequence) VALUES (%s, %s, %s, %s, %s, %s)",
98  array( 'integer', 'integer', 'text', 'text', 'float', 'integer' ),
99  array(
100  $next_id,
101  $this->getId(),
102  $object->text_wrong,
103  $object->text_correct,
104  $object->points,
105  $sequence++
106  )
107  );
108  }
109  }
110 
117  {
118  global $DIC;
119  $ilDB = $DIC['ilDB'];
120  // save additional data
121  $ilDB->manipulateF(
122  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
123  array( "integer" ),
124  array( $this->getId() )
125  );
126 
127  $ilDB->manipulateF(
128  "INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, errortext, textsize, points_wrong) VALUES (%s, %s, %s, %s)",
129  array("integer", "text", "float", "float"),
130  array(
131  $this->getId(),
132  $this->getErrorText(),
133  $this->getTextSize(),
134  $this->getPointsWrong()
135  )
136  );
137  }
138 
145  public function loadFromDb($question_id)
146  {
147  global $DIC;
148  $ilDB = $DIC['ilDB'];
149 
150  $result = $ilDB->queryF(
151  "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",
152  array("integer"),
153  array($question_id)
154  );
155  if ($result->numRows() == 1) {
156  $data = $ilDB->fetchAssoc($result);
157  $this->setId($question_id);
158  $this->setObjId($data["obj_fi"]);
159  $this->setTitle($data["title"]);
160  $this->setComment($data["description"]);
161  $this->setOriginalId($data["original_id"]);
162  $this->setNrOfTries($data['nr_of_tries']);
163  $this->setAuthor($data["author"]);
164  $this->setPoints($data["points"]);
165  $this->setOwner($data["owner"]);
166  include_once("./Services/RTE/classes/class.ilRTE.php");
167  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
168  $this->setErrorText($data["errortext"]);
169  $this->setTextSize($data["textsize"]);
170  $this->setPointsWrong($data["points_wrong"]);
171  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
172 
173  try {
174  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
175  } catch (ilTestQuestionPoolException $e) {
176  }
177  }
178 
179  $result = $ilDB->queryF(
180  "SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
181  array('integer'),
182  array($question_id)
183  );
184  include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
185  if ($result->numRows() > 0) {
186  while ($data = $ilDB->fetchAssoc($result)) {
187  array_push($this->errordata, new assAnswerErrorText($data["text_wrong"], $data["text_correct"], $data["points"]));
188  }
189  }
190 
191  parent::loadFromDb($question_id);
192  }
193 
197  public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
198  {
199  if ($this->id <= 0) {
200  // The question has not been saved. It cannot be duplicated
201  return;
202  }
203  // duplicate the question in database
204  $this_id = $this->getId();
205  $thisObjId = $this->getObjId();
206 
207  $clone = $this;
208  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
210  $clone->id = -1;
211 
212  if ((int) $testObjId > 0) {
213  $clone->setObjId($testObjId);
214  }
215 
216  if ($title) {
217  $clone->setTitle($title);
218  }
219 
220  if ($author) {
221  $clone->setAuthor($author);
222  }
223  if ($owner) {
224  $clone->setOwner($owner);
225  }
226 
227  if ($for_test) {
228  $clone->saveToDb($original_id);
229  } else {
230  $clone->saveToDb();
231  }
232  // copy question page content
233  $clone->copyPageOfQuestion($this_id);
234  // copy XHTML media objects
235  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
236 
237  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
238  return $clone->id;
239  }
240 
244  public function copyObject($target_questionpool_id, $title = "")
245  {
246  if ($this->id <= 0) {
247  // The question has not been saved. It cannot be duplicated
248  return;
249  }
250  // duplicate the question in database
251 
252  $thisId = $this->getId();
253  $thisObjId = $this->getObjId();
254 
255  $clone = $this;
256  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
258  $clone->id = -1;
259  $clone->setObjId($target_questionpool_id);
260  if ($title) {
261  $clone->setTitle($title);
262  }
263  $clone->saveToDb();
264 
265  // copy question page content
266  $clone->copyPageOfQuestion($original_id);
267  // copy XHTML media objects
268  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
269 
270  $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
271 
272  return $clone->id;
273  }
274 
275  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
276  {
277  if ($this->id <= 0) {
278  // The question has not been saved. It cannot be duplicated
279  return;
280  }
281 
282  include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
283 
284  $sourceQuestionId = $this->id;
285  $sourceParentId = $this->getObjId();
286 
287  // duplicate the question in database
288  $clone = $this;
289  $clone->id = -1;
290 
291  $clone->setObjId($targetParentId);
292 
293  if ($targetQuestionTitle) {
294  $clone->setTitle($targetQuestionTitle);
295  }
296 
297  $clone->saveToDb();
298  // copy question page content
299  $clone->copyPageOfQuestion($sourceQuestionId);
300  // copy XHTML media objects
301  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
302 
303  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
304 
305  return $clone->id;
306  }
307 
313  public function getMaximumPoints()
314  {
315  $maxpoints = 0.0;
316  foreach ($this->errordata as $object) {
317  if ($object->points > 0) {
318  $maxpoints += $object->points;
319  }
320  }
321  return $maxpoints;
322  }
323 
334  public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
335  {
336  if ($returndetails) {
337  throw new ilTestException('return details not implemented for ' . __METHOD__);
338  }
339 
340  global $DIC;
341  $ilDB = $DIC['ilDB'];
342 
343  /* First get the positions which were selected by the user. */
344  $positions = array();
345  if (is_null($pass)) {
346  $pass = $this->getSolutionMaxPass($active_id);
347  }
348  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
349 
350  while ($row = $ilDB->fetchAssoc($result)) {
351  array_push($positions, $row['value1']);
352  }
353  $points = $this->getPointsForSelectedPositions($positions);
354  return $points;
355  }
356 
358  {
359  $reachedPoints = $this->getPointsForSelectedPositions($previewSession->getParticipantsSolution());
360  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
361  return $this->ensureNonNegativePoints($reachedPoints);
362  }
363 
372  public function saveWorkingData($active_id, $pass = null, $authorized = true)
373  {
374  global $DIC;
375  $ilDB = $DIC['ilDB'];
376  $ilUser = $DIC['ilUser'];
377 
378  if (is_null($pass)) {
379  include_once "./Modules/Test/classes/class.ilObjTest.php";
380  $pass = ilObjTest::_getPass($active_id);
381  }
382 
383  $entered_values = false;
384 
385  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
386  $this->removeCurrentSolution($active_id, $pass, $authorized);
387 
388  if (strlen($_POST["qst_" . $this->getId()])) {
389  $selected = explode(",", $_POST["qst_" . $this->getId()]);
390  foreach ($selected as $position) {
391  $this->saveCurrentSolution($active_id, $pass, $position, null, $authorized);
392  }
393  $entered_values = true;
394  }
395  });
396 
397  if ($entered_values) {
398  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
400  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
401  }
402  } else {
403  include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
405  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
406  }
407  }
408 
409  return true;
410  }
411 
412  public function savePreviewData(ilAssQuestionPreviewSession $previewSession)
413  {
414  if (strlen($_POST["qst_" . $this->getId()])) {
415  $selection = explode(',', $_POST["qst_{$this->getId()}"]);
416  } else {
417  $selection = array();
418  }
419 
420  $previewSession->setParticipantsSolution($selection);
421  }
422 
428  public function getQuestionType()
429  {
430  return "assErrorText";
431  }
432 
438  public function getAdditionalTableName()
439  {
440  return "qpl_qst_errortext";
441  }
442 
448  public function getAnswerTableName()
449  {
450  return "qpl_a_errortext";
451  }
452 
457  public function getRTETextWithMediaObjects()
458  {
459  $text = parent::getRTETextWithMediaObjects();
460  return $text;
461  }
462 
466  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
467  {
468  parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
469 
470  $i = 0;
471  $selections = array();
472  $solutions = &$this->getSolutionValues($active_id, $pass);
473  if (is_array($solutions)) {
474  foreach ($solutions as $solution) {
475  array_push($selections, $solution['value1']);
476  }
477  $errortext_value = join(",", $selections);
478  }
479  $errortext = $this->createErrorTextExport($selections);
480  $i++;
481  $worksheet->setCell($startrow + $i, 0, $errortext);
482  $i++;
483 
484  return $startrow + $i + 1;
485  }
486 
499  public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
500  {
501  include_once "./Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php";
502  $import = new assErrorTextImport($this);
503  $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
504  }
505 
512  public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
513  {
514  include_once "./Modules/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php";
515  $export = new assErrorTextExport($this);
516  return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
517  }
518 
524  public function getBestSolution($active_id, $pass)
525  {
526  $user_solution = array();
527  return $user_solution;
528  }
529 
530  public function getErrorsFromText($a_text = "")
531  {
532  if (strlen($a_text) == 0) {
533  $a_text = $this->getErrorText();
534  }
535 
536  /* Workaround to allow '(' and ')' in passages.
537  The beginning- and ending- Passage delimiters are
538  replaced by a ~ (Tilde) symbol. */
539  $a_text = str_replace(array("((", "))"), array("~", "~"), $a_text);
540 
541  /* Match either Passage delimited by double brackets
542  or single words marked with a hash (#). */
543  $r_passage = "/(~([^~]+)~|#([^\s]+))/";
544 
545  preg_match_all($r_passage, $a_text, $matches);
546 
547  if (is_array($matches[0]) && !empty($matches[0])) {
548  /* At least one match. */
549 
550  /* We need only groups 2 and 3, respectively representing
551  passage matches and single word matches. */
552  $matches = array_intersect_key($matches, array(2 => '', 3 => ''));
553 
554  /* Remove empty values. */
555  $matches[2] = array_diff($matches[2], array(''));
556  $matches[3] = array_diff($matches[3], array(''));
557 
558  return array(
559  "passages" => $matches[2],
560  "words" => $matches[3],);
561  }
562 
563  return array();
564  }
565 
566  public function setErrorData($a_data)
567  {
568  include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
569  $temp = $this->errordata;
570  $this->errordata = array();
571  foreach ($a_data as $err_type => $errors) {
572  /* Iterate through error types (Passages|single words) */
573 
574  foreach ($errors as $idx => $error) {
575  /* Iterate through errors of this type. */
576  $text_correct = "";
577  $points = 0.0;
578  foreach ($temp as $object) {
579  if (strcmp($object->text_wrong, $error) == 0) {
580  $text_correct = $object->text_correct;
581  $points = $object->points;
582  continue;
583  }
584  }
585  $this->errordata[$idx] = new assAnswerErrorText($error, $text_correct, $points);
586  }
587  }
588  ksort($this->errordata);
589  }
590 
591  public function createErrorTextOutput($selections = null, $graphicalOutput = false, $correct_solution = false, $use_link_tags = true)
592  {
593  $counter = 0;
594  $errorcounter = 0;
595  include_once "./Services/Utilities/classes/class.ilStr.php";
596  if (!is_array($selections)) {
597  $selections = array();
598  }
599  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
600 
601  foreach ($textarray as $textidx => $text) {
602  $in_passage = false;
603  $passage_end = false;
604  $items = preg_split("/\s+/", $text);
605  foreach ($items as $idx => $item) {
606  $img = '';
607 
608  if (
609  ($posHash = strpos($item, '#')) === 0 ||
610  ($posOpeningBrackets = strpos($item, '((')) === 0 ||
611  ($posClosingBrackets = strpos($item, '))')) !== false
612  ) {
613  /* (Word|Passage)-Marking delimiter found. */
614 
615  if ($posHash !== false) {
616  $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
617  $passage_end = false;
618  } elseif ($posOpeningBrackets !== false) {
619  $in_passage = true;
620  $passage_start_idx = $counter;
621  $items_in_passage = array();
622  $passage_end = false;
623  $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
624 
625  /* Sometimes a closing bracket group needs
626  to be removed as well. */
627  if (strpos($item, '))') !== false) {
628  $item = str_replace("))", "", $item);
629  $passage_end = true;
630  }
631  } else {
632  $passage_end = true;
633  $item = str_replace("))", "", $item);
634  }
635 
636  if ($correct_solution && !$in_passage) {
637  $errorobject = $this->errordata[$errorcounter];
638  if (is_object($errorobject)) {
639  $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
640  }
641  $errorcounter++;
642  }
643  }
644 
645  if ($in_passage && !$passage_end) {
646  $items_in_passage[$idx] = $item;
647  $items[$idx] = '';
648  $counter++;
649  continue;
650  }
651 
652  if ($in_passage && $passage_end) {
653  $in_passage = false;
654  $passage_end = false;
655  if ($correct_solution) {
656  $class = (
657  $this->isTokenSelected($counter, $selections) ?
658  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
659  );
660 
661  $errorobject = $this->errordata[$errorcounter];
662  if (is_object($errorobject)) {
663  $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
664  }
665  $errorcounter++;
666  $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
667  $counter++;
668  continue;
669  }
670 
671  $group_selected = true;
672  if ($graphicalOutput) {
673  $start_idx = $passage_start_idx;
674  foreach ($items_in_passage as $tmp_idx => $tmp_item) {
675  if (!$this->isTokenSelected($start_idx, $selections)) {
676  $group_selected = false;
677  break;
678  }
679 
680  ++$start_idx;
681  }
682  if ($group_selected) {
683  if (!$this->isTokenSelected($counter, $selections)) {
684  $group_selected = false;
685  }
686  }
687  }
688 
689  $item_stack = array();
690  $start_idx = $passage_start_idx;
691  foreach ($items_in_passage as $tmp_idx => $tmp_item) {
692  $class = (
693  $this->isTokenSelected($counter, $selections) ?
694  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
695  );
696  $item_stack[] = $this->getErrorTokenHtml($tmp_item, $class, $use_link_tags) . $img;
697  $start_idx++;
698  }
699  $class = (
700  $this->isTokenSelected($counter, $selections) ?
701  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
702  );
703  if ($graphicalOutput) {
704  if ($group_selected) {
705  $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
706  } else {
707  $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
708  }
709  }
710 
711  $item_stack[] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
712  $item_stack = trim(implode(" ", $item_stack));
713  $item_stack = strlen($item_stack) ? $item_stack : '&nbsp;';
714 
715  if ($graphicalOutput) {
716  $items[$idx] = '<span class="selGroup">' . $item_stack . '</span>';
717  } else {
718  $items[$idx] = $item_stack;
719  }
720 
721  $counter++;
722  continue;
723  }
724 
725  // Errors markes with #, group errors (()) are handled above
726  $class = 'ilc_qetitem_ErrorTextItem';
727  $img = '';
728  if ($this->isTokenSelected($counter, $selections)) {
729  $class = "ilc_qetitem_ErrorTextSelected";
730  if ($graphicalOutput) {
731  if ($this->getPointsForSelectedPositions(array($counter)) > 0) {
732  $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
733  } else {
734  $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
735  }
736  }
737  }
738 
739  $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
740  $counter++;
741  }
742  $textarray[$textidx] = '<p>' . implode(" ", $items) . '</p>';
743  }
744 
745  return implode("\n", $textarray);
746  }
747 
748  protected function isTokenSelected($counter, array $selection)
749  {
750  foreach ($selection as $data) {
751  if (!is_array($data)) {
752  if ($counter == $data) {
753  return true;
754  }
755  } elseif (in_array($counter, $data)) {
756  return true;
757  }
758  }
759 
760  return false;
761  }
762 
763  public function createErrorTextExport($selections = null)
764  {
765  $counter = 0;
766  $errorcounter = 0;
767  include_once "./Services/Utilities/classes/class.ilStr.php";
768  if (!is_array($selections)) {
769  $selections = array();
770  }
771  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
772  foreach ($textarray as $textidx => $text) {
773  $items = preg_split("/\s+/", $text);
774  foreach ($items as $idx => $item) {
775  if (($posHash = strpos($item, '#')) === 0
776  || ($posOpeningBrackets = strpos($item, '((')) === 0
777  || ($posClosingBrackets = strpos($item, '))')) !== false) {
778  /* (Word|Passage)-Marking delimiter found. */
779 
780  if ($posHash !== false) {
781  $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
782  } elseif ($posOpeningBrackets !== false) {
783  $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
784 
785  /* Sometimes a closing bracket group needs
786  to be removed as well. */
787  if (strpos($item, '))') !== false) {
788  $item = ilStr::substr($item, 0, ilStr::strlen($item) - 2);
789  }
790  } else {
791  $appendComma = "";
792  if ($item[$posClosingBrackets + 2] == ',') {
793  $appendComma = ",";
794  }
795 
796  $item = ilStr::substr($item, 0, $posClosingBrackets) . $appendComma;
797  }
798  }
799 
800  $word = "";
801  if (in_array($counter, $selections)) {
802  $word .= '#';
803  }
804  $word .= ilUtil::prepareFormOutput($item);
805  if (in_array($counter, $selections)) {
806  $word .= '#';
807  }
808  $items[$idx] = $word;
809  $counter++;
810  }
811  $textarray[$textidx] = join(" ", $items);
812  }
813  return join("\n", $textarray);
814  }
815 
816  public function getBestSelection($withPositivePointsOnly = true)
817  {
818  $passages = array();
819  $words = array();
820  $counter = 0;
821  $errorcounter = 0;
822  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
823  foreach ($textarray as $textidx => $text) {
824  $items = preg_split("/\s+/", $text);
825  $inPassage = false;
826  foreach ($items as $word) {
827  $points = $this->getPointsWrong();
828  $isErrorItem = false;
829  if (strpos($word, '#') === 0) {
830  /* Word selection detected */
831  $errorobject = $this->errordata[$errorcounter];
832  if (is_object($errorobject)) {
833  $points = $errorobject->points;
834  $isErrorItem = true;
835  }
836  $errorcounter++;
837  } elseif (($posOpeningBracket = strpos($word, '((')) === 0
838  || ($posClosingBracket = strpos($word, '))')) !== false
839  || $inPassage) {
840  /* Passage selection detected */
841 
842  if ($posOpeningBracket !== false) {
843  $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
844  $inPassage = true;
845  } elseif ($posClosingBracket !== false) {
846  $inPassage = false;
847  $cur_pidx = count($passages) - 1;
848  $passages[$cur_pidx]['end_pos'] = $counter;
849 
850  $errorobject = $this->errordata[$errorcounter];
851  if (is_object($errorobject)) {
852  $passages[$cur_pidx]['score'] = $errorobject->points;
853  $passages[$cur_pidx]['isError'] = true;
854  }
855 
856  $errorcounter++;
857  }
858 
859  $cur_pidx = count($passages) - 1;
860  $passages[$cur_pidx]['cnt_words']++;
861  $points = 0;
862  }
863 
864  $words[$counter] = array("word" => $word, "points" => $points, "isError" => $isErrorItem);
865  $counter++;
866  }
867  }
868 
869  $selections = array();
870  foreach ($passages as $cnt => $pdata) {
871  if (!$withPositivePointsOnly && $pdata['isError'] || $withPositivePointsOnly && $pdata['score'] > 0) {
872  $indexes = range($pdata['begin_pos'], $pdata['end_pos']);
873  $selections[$pdata['begin_pos']] = $indexes;
874  }
875  }
876 
877  foreach ($words as $idx => $word) {
878  if (!$withPositivePointsOnly && $word['isError'] || $withPositivePointsOnly && $word['points'] > 0) {
879  $selections[$idx] = array($idx);
880  }
881  }
882 
883  ksort($selections);
884 
885  $selections = array_values($selections);
886 
887  return $selections;
888  }
889 
890  protected function getPointsForSelectedPositions($positions)
891  {
892  $passages = array();
893  $words = array();
894  $counter = 0;
895  $errorcounter = 0;
896  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
897  foreach ($textarray as $textidx => $text) {
898  $items = preg_split("/\s+/", $text);
899  $inPassage = false;
900  foreach ($items as $word) {
901  $points = $this->getPointsWrong();
902  if (strpos($word, '#') === 0) {
903  /* Word selection detected */
904  $errorobject = $this->errordata[$errorcounter];
905  if (is_object($errorobject)) {
906  $points = $errorobject->points;
907  }
908  $errorcounter++;
909  } elseif (($posOpeningBracket = strpos($word, '((')) === 0
910  || ($posClosingBracket = strpos($word, '))')) !== false
911  || $inPassage) {
912  /* Passage selection detected */
913 
914  if ($posOpeningBracket !== false) {
915  $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
916  $inPassage = true;
917  } elseif ($posClosingBracket !== false) {
918  $inPassage = false;
919  $cur_pidx = count($passages) - 1;
920  $passages[$cur_pidx]['end_pos'] = $counter;
921 
922  $errorobject = $this->errordata[$errorcounter];
923  if (is_object($errorobject)) {
924  $passages[$cur_pidx]['score'] = $errorobject->points;
925  }
926  $errorcounter++;
927  }
928 
929  $cur_pidx = count($passages) - 1;
930  $passages[$cur_pidx]['cnt_words']++;
931  $points = 0;
932  }
933 
934  $words[$counter] = array("word" => $word, "points" => $points);
935  $counter++;
936  }
937  }
938 
939  /* Calculate reached points */
940  $total = 0;
941  foreach ($positions as $position) {
942  /* First iterate through positions
943  to identify single-word-selections. */
944 
945  $total += $words[$position]['points'];
946  }
947 
948  foreach ($passages as $cnt => $p_data) {
949  /* Iterate through configured passages to check
950  wether the entire passage is selected or not.
951  The total points is incremented by the passage's
952  score only if the entire passage is selected. */
953  $isSelected = in_array($p_data['begin_pos'], $positions);
954 
955  for ($i = 0; $i < $p_data['cnt_words']; $i++) {
956  $current_pos = $p_data['begin_pos'] + $i;
957  $isSelected = $isSelected && in_array($current_pos, $positions);
958  }
959 
960  $total += $isSelected ? $p_data['score'] : 0;
961  }
962 
963  return $total;
964  }
965 
969  public function flushErrorData()
970  {
971  $this->errordata = array();
972  }
973 
974  public function addErrorData($text_wrong, $text_correct, $points)
975  {
976  include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
977  array_push($this->errordata, new assAnswerErrorText($text_wrong, $text_correct, $points));
978  }
979 
985  public function getErrorData()
986  {
987  return $this->errordata;
988  }
989 
995  public function getErrorText()
996  {
997  return $this->errortext;
998  }
999 
1005  public function setErrorText($a_value)
1006  {
1007  $this->errortext = $a_value;
1008  }
1009 
1015  public function getTextSize()
1016  {
1017  return $this->textsize;
1018  }
1019 
1025  public function setTextSize($a_value)
1026  {
1027  // in self-assesment-mode value should always be set (and must not be null)
1028  if ($a_value === null) {
1029  $a_value = 100;
1030  }
1031  $this->textsize = $a_value;
1032  }
1033 
1039  public function getPointsWrong()
1040  {
1041  return $this->points_wrong;
1042  }
1043 
1049  public function setPointsWrong($a_value)
1050  {
1051  $this->points_wrong = $a_value;
1052  }
1053 
1057  public function __get($value)
1058  {
1059  switch ($value) {
1060  case "errortext":
1061  return $this->getErrorText();
1062  break;
1063  case "textsize":
1064  return $this->getTextSize();
1065  break;
1066  case "points_wrong":
1067  return $this->getPointsWrong();
1068  break;
1069  default:
1070  return parent::__get($value);
1071  break;
1072  }
1073  }
1074 
1078  public function __set($key, $value)
1079  {
1080  switch ($key) {
1081  case "errortext":
1082  $this->setErrorText($value);
1083  break;
1084  case "textsize":
1085  $this->setTextSize($value);
1086  break;
1087  case "points_wrong":
1088  $this->setPointsWrong($value);
1089  break;
1090  default:
1091  parent::__set($key, $value);
1092  break;
1093  }
1094  }
1095 
1096 
1100  public function toJSON()
1101  {
1102  include_once("./Services/RTE/classes/class.ilRTE.php");
1103  $result = array();
1104  $result['id'] = (int) $this->getId();
1105  $result['type'] = (string) $this->getQuestionType();
1106  $result['title'] = (string) $this->getTitle();
1107  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1108  $result['text'] = (string) ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
1109  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1110  $result['shuffle'] = (bool) $this->getShuffle();
1111  $result['feedback'] = array(
1112  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1113  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1114  );
1115 
1116  $answers = array();
1117  foreach ($this->getErrorData() as $idx => $answer_obj) {
1118  array_push($answers, array(
1119  "answertext_wrong" => (string) $answer_obj->text_wrong,
1120  "answertext_correct" => (string) $answer_obj->text_correct,
1121  "points" => (float) $answer_obj->points,
1122  "order" => (int) $idx + 1
1123  ));
1124  }
1125  $result['correct_answers'] = $answers;
1126 
1127  $answers = array();
1128  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
1129  foreach ($textarray as $textidx => $text) {
1130  $items = preg_split("/\s+/", trim($text));
1131  foreach ($items as $idx => $item) {
1132  if (substr($item, 0, 1) == "#") {
1133  $item = substr($item, 1);
1134 
1135  // #14115 - add position to correct answer
1136  foreach ($result["correct_answers"] as $aidx => $answer) {
1137  if ($answer["answertext_wrong"] == $item && !$answer["pos"]) {
1138  $result["correct_answers"][$aidx]["pos"] = $this->getId() . "_" . $textidx . "_" . ($idx + 1);
1139  break;
1140  }
1141  }
1142  }
1143  array_push($answers, array(
1144  "answertext" => (string) ilUtil::prepareFormOutput($item),
1145  "order" => $this->getId() . "_" . $textidx . "_" . ($idx + 1)
1146  ));
1147  }
1148  if ($textidx != sizeof($textarray) - 1) {
1149  array_push($answers, array(
1150  "answertext" => "###",
1151  "order" => $this->getId() . "_" . $textidx . "_" . ($idx + 2)
1152  ));
1153  }
1154  }
1155  $result['answers'] = $answers;
1156 
1157  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1158  $result['mobs'] = $mobs;
1159 
1160  return json_encode($result);
1161  }
1162 
1171  public function getOperators($expression)
1172  {
1173  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1175  }
1176 
1181  public function getExpressionTypes()
1182  {
1183  return array(
1188  );
1189  }
1190 
1199  public function getUserQuestionResult($active_id, $pass)
1200  {
1202  global $DIC;
1203  $ilDB = $DIC['ilDB'];
1204  $result = new ilUserQuestionResult($this, $active_id, $pass);
1205 
1206  $data = $ilDB->queryF(
1207  "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1208  SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1209  )",
1210  array("integer", "integer", "integer","integer", "integer", "integer"),
1211  array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1212  );
1213 
1214  while ($row = $ilDB->fetchAssoc($data)) {
1215  $result->addKeyValue($row["value1"], $row["value1"]);
1216  }
1217 
1218  $points = $this->calculateReachedPoints($active_id, $pass);
1219  $max_points = $this->getMaximumPoints();
1220 
1221  $result->setReachedPercentage(($points / $max_points) * 100);
1222 
1223  return $result;
1224  }
1225 
1234  public function getAvailableAnswerOptions($index = null)
1235  {
1236  $error_text_array = explode(' ', $this->errortext);
1237 
1238  if ($index !== null) {
1239  if (array_key_exists($index, $error_text_array)) {
1240  return $error_text_array[$index];
1241  }
1242  return null;
1243  } else {
1244  return $error_text_array;
1245  }
1246  }
1247 
1253  private function getErrorTokenHtml($item, $class, $useLinkTags)
1254  {
1255  if ($useLinkTags) {
1256  return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>';
1257  }
1258 
1259  return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</span>';
1260  }
1261 }
getErrorText()
Get error text.
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
getId()
Gets the id of the assQuestion object.
static prepareFormOutput($a_str, $a_strip=false)
prepares string output for html forms public
getErrorData()
Get error data.
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
Class iQuestionCondition.
setTextSize($a_value)
Set text size in percent.
toJSON()
Returns a JSON representation of the question.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
$result
global $DIC
Definition: saml.php:7
getTextSize()
Set text size in percent.
createErrorTextExport($selections=null)
Abstract basic class which is to be extended by the concrete assessment question type classes...
loadFromDb($question_id)
Loads the object from the database.
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
Returns a QTI xml representation of the question and sets the internal domxml variable with the DOM X...
__get($value)
Object getter.
createErrorTextOutput($selections=null, $graphicalOutput=false, $correct_solution=false, $use_link_tags=true)
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
ensureNonNegativePoints($points)
saveToDb($original_id="")
Saves a the object to the database.
isTokenSelected($counter, array $selection)
getErrorsFromText($a_text="")
getSolutionValues($active_id, $pass=null, $authorized=true)
Loads solutions of a given user from the database an returns it.
setId($id=-1)
Sets the id of the assQuestion object.
setErrorText($a_value)
Set error text.
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second. ...
copyObject($target_questionpool_id, $title="")
Copies an object.
getQuestionType()
Returns the question type of the question.
Class for error text answers.
$index
Definition: metadata.php:60
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
setNrOfTries($a_nr_of_tries)
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
Creates a question from a QTI file.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
Class for error text question exports.
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
__construct( $title='', $comment='', $author='', $owner=-1, $question='')
assErorText constructor
getObjId()
Get the object id of the container object.
$total
Definition: Utf8Test.php:87
getShuffle()
Gets the shuffle flag.
Base Exception for all Exceptions relating to Modules/Test.
getPointsWrong()
Get wrong points.
isComplete()
Returns true, if a single choice question is complete for use.
static _getLogLanguage()
retrieve the log language for assessment logging
setAuthor($author="")
Sets the authors name of the assQuestion object.
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
getPointsForSelectedPositions($positions)
$mobs
Class ilUserQuestionResult.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
getBestSolution($active_id, $pass)
Returns the best solution for a given pass of a participant.
setPointsWrong($a_value)
Set wrong points.
getOperators($expression)
Get all available operations for a specific question.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
Class for error text questions.
$text
Definition: errorreport.php:18
Interface ilObjAnswerScoringAdjustable.
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates the object.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
getQuestion()
Gets the question string of the question object.
getExpressionTypes()
Get all available expression types for a specific question.
$ilUser
Definition: imgupload.php:18
savePreviewData(ilAssQuestionPreviewSession $previewSession)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
$row
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
setPoints($a_points)
Sets the maximum available points for the question.
saveQuestionDataToDb($original_id="")
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
{}
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
$errors
Definition: index.php:6
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
setQuestion($question="")
Sets the question string of the question object.
Interface ilObjQuestionScoringAdjustable.
removeCurrentSolution($active_id, $pass, $authorized=true)
getBestSelection($withPositivePointsOnly=true)
global $ilDB
setOriginalId($original_id)
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass...
$i
Definition: disco.tpl.php:19
__set($key, $value)
Object setter.
getTitle()
Gets the title string of the assQuestion object.
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
addErrorData($text_wrong, $text_correct, $points)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
setTitle($title="")
Sets the title string of the assQuestion object.
setObjId($obj_id=0)
Set the object id of the container object.
flushErrorData()
Flush error data.
$key
Definition: croninfo.php:18
setComment($comment="")
Sets the comment string of the assQuestion object.
getAnswerTableName()
Returns the name of the answer table in the database.
$_POST["username"]
getErrorTokenHtml($item, $class, $useLinkTags)
Class for error text question imports.
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
$data
Definition: bench.php:6