ILIAS  release_7 Revision v7.30-3-g800a261c036
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
4require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
7require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
8require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
9require_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 ) {
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
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 {
177 }
178
179 try {
180 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
182 }
183 }
184
185 $result = $ilDB->queryF(
186 "SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
187 array('integer'),
188 array($question_id)
189 );
190 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
191 if ($result->numRows() > 0) {
192 while ($data = $ilDB->fetchAssoc($result)) {
193 array_push($this->errordata, new assAnswerErrorText($data["text_wrong"], $data["text_correct"], $data["points"]));
194 }
195 }
196
197 parent::loadFromDb($question_id);
198 }
199
203 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
204 {
205 if ($this->id <= 0) {
206 // The question has not been saved. It cannot be duplicated
207 return;
208 }
209 // duplicate the question in database
210 $this_id = $this->getId();
211 $thisObjId = $this->getObjId();
212
213 $clone = $this;
214 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
216 $clone->id = -1;
217
218 if ((int) $testObjId > 0) {
219 $clone->setObjId($testObjId);
220 }
221
222 if ($title) {
223 $clone->setTitle($title);
224 }
225
226 if ($author) {
227 $clone->setAuthor($author);
228 }
229 if ($owner) {
230 $clone->setOwner($owner);
231 }
232
233 if ($for_test) {
234 $clone->saveToDb($original_id);
235 } else {
236 $clone->saveToDb();
237 }
238 // copy question page content
239 $clone->copyPageOfQuestion($this_id);
240 // copy XHTML media objects
241 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
242
243 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
244 return $clone->id;
245 }
246
250 public function copyObject($target_questionpool_id, $title = "")
251 {
252 if ($this->id <= 0) {
253 // The question has not been saved. It cannot be duplicated
254 return;
255 }
256 // duplicate the question in database
257
258 $thisId = $this->getId();
259 $thisObjId = $this->getObjId();
260
261 $clone = $this;
262 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
264 $clone->id = -1;
265 $clone->setObjId($target_questionpool_id);
266 if ($title) {
267 $clone->setTitle($title);
268 }
269 $clone->saveToDb();
270
271 // copy question page content
272 $clone->copyPageOfQuestion($original_id);
273 // copy XHTML media objects
274 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
275
276 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
277
278 return $clone->id;
279 }
280
281 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
282 {
283 if ($this->id <= 0) {
284 // The question has not been saved. It cannot be duplicated
285 return;
286 }
287
288 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
289
290 $sourceQuestionId = $this->id;
291 $sourceParentId = $this->getObjId();
292
293 // duplicate the question in database
294 $clone = $this;
295 $clone->id = -1;
296
297 $clone->setObjId($targetParentId);
298
299 if ($targetQuestionTitle) {
300 $clone->setTitle($targetQuestionTitle);
301 }
302
303 $clone->saveToDb();
304 // copy question page content
305 $clone->copyPageOfQuestion($sourceQuestionId);
306 // copy XHTML media objects
307 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
308
309 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
310
311 return $clone->id;
312 }
313
319 public function getMaximumPoints()
320 {
321 $maxpoints = 0.0;
322 foreach ($this->errordata as $object) {
323 if ($object->points > 0) {
324 $maxpoints += $object->points;
325 }
326 }
327 return $maxpoints;
328 }
329
340 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
341 {
342 if ($returndetails) {
343 throw new ilTestException('return details not implemented for ' . __METHOD__);
344 }
345
346 global $DIC;
347 $ilDB = $DIC['ilDB'];
348
349 /* First get the positions which were selected by the user. */
350 $positions = array();
351 if (is_null($pass)) {
352 $pass = $this->getSolutionMaxPass($active_id);
353 }
354 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
355
356 while ($row = $ilDB->fetchAssoc($result)) {
357 array_push($positions, $row['value1']);
358 }
359 $points = $this->getPointsForSelectedPositions($positions);
360 return $points;
361 }
362
364 {
365 $reachedPoints = $this->getPointsForSelectedPositions($previewSession->getParticipantsSolution());
366 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
367 return $this->ensureNonNegativePoints($reachedPoints);
368 }
369
378 public function saveWorkingData($active_id, $pass = null, $authorized = true)
379 {
380 global $DIC;
381 $ilDB = $DIC['ilDB'];
382 $ilUser = $DIC['ilUser'];
383
384 if (is_null($pass)) {
385 include_once "./Modules/Test/classes/class.ilObjTest.php";
386 $pass = ilObjTest::_getPass($active_id);
387 }
388
389 $entered_values = false;
390
391 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
392 $this->removeCurrentSolution($active_id, $pass, $authorized);
393
394 if (strlen($_POST["qst_" . $this->getId()])) {
395 $selected = explode(",", $_POST["qst_" . $this->getId()]);
396 foreach ($selected as $position) {
397 $this->saveCurrentSolution($active_id, $pass, $position, null, $authorized);
398 }
399 $entered_values = true;
400 }
401 });
402
403 if ($entered_values) {
404 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
406 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
407 }
408 } else {
409 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
411 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
412 }
413 }
414
415 return true;
416 }
417
418 public function savePreviewData(ilAssQuestionPreviewSession $previewSession)
419 {
420 if (strlen($_POST["qst_" . $this->getId()])) {
421 $selection = explode(',', $_POST["qst_{$this->getId()}"]);
422 } else {
423 $selection = array();
424 }
425
426 $previewSession->setParticipantsSolution($selection);
427 }
428
434 public function getQuestionType()
435 {
436 return "assErrorText";
437 }
438
444 public function getAdditionalTableName()
445 {
446 return "qpl_qst_errortext";
447 }
448
454 public function getAnswerTableName()
455 {
456 return "qpl_a_errortext";
457 }
458
464 {
465 $text = parent::getRTETextWithMediaObjects();
466 return $text;
467 }
468
472 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
473 {
474 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
475
476 $i = 0;
477 $selections = array();
478 $solutions = &$this->getSolutionValues($active_id, $pass);
479 if (is_array($solutions)) {
480 foreach ($solutions as $solution) {
481 array_push($selections, $solution['value1']);
482 }
483 $errortext_value = join(",", $selections);
484 }
485 $errortext = $this->createErrorTextExport($selections);
486 $i++;
487 $worksheet->setCell($startrow + $i, 2, $errortext);
488 $i++;
489
490 return $startrow + $i + 1;
491 }
492
505 public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping, array $solutionhints = [])
506 {
507 include_once "./Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php";
508 $import = new assErrorTextImport($this);
509 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
510 }
511
518 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
519 {
520 include_once "./Modules/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php";
521 $export = new assErrorTextExport($this);
522 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
523 }
524
530 public function getBestSolution($active_id, $pass)
531 {
532 $user_solution = array();
533 return $user_solution;
534 }
535
536 public function getErrorsFromText($a_text = "")
537 {
538 if (strlen($a_text) == 0) {
539 $a_text = $this->getErrorText();
540 }
541
542 /* Workaround to allow '(' and ')' in passages.
543 The beginning- and ending- Passage delimiters are
544 replaced by a ~ (Tilde) symbol. */
545 $a_text = str_replace(array("((", "))"), array("~", "~"), $a_text);
546
547 /* Match either Passage delimited by double brackets
548 or single words marked with a hash (#). */
549 $r_passage = "/(~([^~]+)~|#([^\s]+))/";
550
551 preg_match_all($r_passage, $a_text, $matches);
552
553 if (is_array($matches[0]) && !empty($matches[0])) {
554 /* At least one match. */
555
556 /* We need only groups 2 and 3, respectively representing
557 passage matches and single word matches. */
558 $matches = array_intersect_key($matches, array(2 => '', 3 => ''));
559
560 /* Remove empty values. */
561 $matches[2] = array_diff($matches[2], array(''));
562 $matches[3] = array_diff($matches[3], array(''));
563
564 return array(
565 "passages" => $matches[2],
566 "words" => $matches[3],);
567 }
568
569 return array();
570 }
571
572 public function setErrorData($a_data)
573 {
574 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
575 $temp = $this->errordata;
576 $this->errordata = array();
577 foreach ($a_data as $err_type => $errors) {
578 /* Iterate through error types (Passages|single words) */
579
580 foreach ($errors as $idx => $error) {
581 /* Iterate through errors of this type. */
582 $text_correct = "";
583 $points = 0.0;
584 foreach ($temp as $object) {
585 if (strcmp($object->text_wrong, $error) == 0) {
586 $text_correct = $object->text_correct;
587 $points = $object->points;
588 continue;
589 }
590 }
591 $this->errordata[$idx] = new assAnswerErrorText($error, $text_correct, $points);
592 }
593 }
594 ksort($this->errordata);
595 }
596
597 public function createErrorTextOutput($selections = null, $graphicalOutput = false, $correct_solution = false, $use_link_tags = true)
598 {
599 $counter = 0;
600 $errorcounter = 0;
601 include_once "./Services/Utilities/classes/class.ilStr.php";
602 if (!is_array($selections)) {
603 $selections = array();
604 }
605 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
606
607 foreach ($textarray as $textidx => $text) {
608 $in_passage = false;
609 $passage_end = false;
610 $items = preg_split("/\s+/", $text);
611 foreach ($items as $idx => $item) {
612 $img = '';
613
614 if (
615 ($posHash = strpos($item, '#')) === 0 ||
616 ($posOpeningBrackets = strpos($item, '((')) === 0 ||
617 ($posClosingBrackets = strpos($item, '))')) !== false
618 ) {
619 /* (Word|Passage)-Marking delimiter found. */
620
621 if ($posHash !== false) {
622 $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
623 $passage_end = false;
624 } elseif ($posOpeningBrackets !== false) {
625 $in_passage = true;
626 $passage_start_idx = $counter;
627 $items_in_passage = array();
628 $passage_end = false;
629 $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
630
631 /* Sometimes a closing bracket group needs
632 to be removed as well. */
633 if (strpos($item, '))') !== false) {
634 $item = str_replace("))", "", $item);
635 $passage_end = true;
636 }
637 } else {
638 $passage_end = true;
639 $item = str_replace("))", "", $item);
640 }
641
642 if ($correct_solution && !$in_passage) {
643 $errorobject = $this->errordata[$errorcounter];
644 if (is_object($errorobject)) {
645 $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
646 }
647 $errorcounter++;
648 }
649 }
650
651 if ($in_passage && !$passage_end) {
652 $items_in_passage[$idx] = $item;
653 $items[$idx] = '';
654 $counter++;
655 continue;
656 }
657
658 if ($in_passage && $passage_end) {
659 $in_passage = false;
660 $passage_end = false;
661 if ($correct_solution) {
662 $class = (
663 $this->isTokenSelected($counter, $selections) ?
664 "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
665 );
666
667 $errorobject = $this->errordata[$errorcounter];
668 if (is_object($errorobject)) {
669 $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
670 }
671 $errorcounter++;
672 $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
673 $counter++;
674 continue;
675 }
676
677 $group_selected = true;
678 if ($graphicalOutput) {
679 $start_idx = $passage_start_idx;
680 foreach ($items_in_passage as $tmp_idx => $tmp_item) {
681 if (!$this->isTokenSelected($start_idx, $selections)) {
682 $group_selected = false;
683 break;
684 }
685
686 ++$start_idx;
687 }
688 if ($group_selected) {
689 if (!$this->isTokenSelected($counter, $selections)) {
690 $group_selected = false;
691 }
692 }
693 }
694
695 $item_stack = array();
696 $start_idx = $passage_start_idx;
697 foreach ($items_in_passage as $tmp_idx => $tmp_item) {
698 $class = (
699 $this->isTokenSelected($counter, $selections) ?
700 "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
701 );
702 $item_stack[] = $this->getErrorTokenHtml($tmp_item, $class, $use_link_tags) . $img;
703 $start_idx++;
704 }
705 $class = (
706 $this->isTokenSelected($counter, $selections) ?
707 "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
708 );
709 if ($graphicalOutput) {
710 if ($group_selected) {
711 $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
712 } else {
713 $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
714 }
715 }
716
717 $item_stack[] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
718 $item_stack = trim(implode(" ", $item_stack));
719 $item_stack = strlen($item_stack) ? $item_stack : '&nbsp;';
720
721 if ($graphicalOutput) {
722 $items[$idx] = '<span class="selGroup">' . $item_stack . '</span>';
723 } else {
724 $items[$idx] = $item_stack;
725 }
726
727 $counter++;
728 continue;
729 }
730
731 // Errors markes with #, group errors (()) are handled above
732 $class = 'ilc_qetitem_ErrorTextItem';
733 $img = '';
734 if ($this->isTokenSelected($counter, $selections)) {
735 $class = "ilc_qetitem_ErrorTextSelected";
736 if ($graphicalOutput) {
737 if ($this->getPointsForSelectedPositions(array($counter)) > 0) {
738 $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
739 } else {
740 $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
741 }
742 }
743 }
744
745 $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
746 $counter++;
747 }
748 $textarray[$textidx] = '<p>' . implode(" ", $items) . '</p>';
749 }
750
751 return implode("\n", $textarray);
752 }
753
754 protected function isTokenSelected($counter, array $selection)
755 {
756 foreach ($selection as $data) {
757 if (!is_array($data)) {
758 if ($counter == $data) {
759 return true;
760 }
761 } elseif (in_array($counter, $data)) {
762 return true;
763 }
764 }
765
766 return false;
767 }
768
769 public function createErrorTextExport($selections = null)
770 {
771 $counter = 0;
772 $errorcounter = 0;
773 include_once "./Services/Utilities/classes/class.ilStr.php";
774 if (!is_array($selections)) {
775 $selections = array();
776 }
777 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
778 foreach ($textarray as $textidx => $text) {
779 $items = preg_split("/\s+/", $text);
780 foreach ($items as $idx => $item) {
781 if (($posHash = strpos($item, '#')) === 0
782 || ($posOpeningBrackets = strpos($item, '((')) === 0
783 || ($posClosingBrackets = strpos($item, '))')) !== false) {
784 /* (Word|Passage)-Marking delimiter found. */
785
786 if ($posHash !== false) {
787 $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
788 } elseif ($posOpeningBrackets !== false) {
789 $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
790
791 /* Sometimes a closing bracket group needs
792 to be removed as well. */
793 if (strpos($item, '))') !== false) {
794 $item = ilStr::substr($item, 0, ilStr::strlen($item) - 2);
795 }
796 } else {
797 $appendComma = "";
798 if ($item[$posClosingBrackets + 2] == ',') {
799 $appendComma = ",";
800 }
801
802 $item = ilStr::substr($item, 0, $posClosingBrackets) . $appendComma;
803 }
804 }
805
806 $word = "";
807 if (in_array($counter, $selections)) {
808 $word .= '#';
809 }
810 $word .= ilUtil::prepareFormOutput($item);
811 if (in_array($counter, $selections)) {
812 $word .= '#';
813 }
814 $items[$idx] = $word;
815 $counter++;
816 }
817 $textarray[$textidx] = join(" ", $items);
818 }
819 return join("\n", $textarray);
820 }
821
822 public function getBestSelection($withPositivePointsOnly = true)
823 {
824 $passages = array();
825 $words = array();
826 $counter = 0;
827 $errorcounter = 0;
828 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
829 foreach ($textarray as $textidx => $text) {
830 $items = preg_split("/\s+/", $text);
831 $inPassage = false;
832 foreach ($items as $word) {
833 $points = $this->getPointsWrong();
834 $isErrorItem = false;
835 if (strpos($word, '#') === 0) {
836 /* Word selection detected */
837 $errorobject = $this->errordata[$errorcounter];
838 if (is_object($errorobject)) {
839 $points = $errorobject->points;
840 $isErrorItem = true;
841 }
842 $errorcounter++;
843 } elseif (($posOpeningBracket = strpos($word, '((')) === 0
844 || ($posClosingBracket = strpos($word, '))')) !== false
845 || $inPassage) {
846 /* Passage selection detected */
847
848 if ($posOpeningBracket !== false) {
849 $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
850 $inPassage = true;
851 } elseif ($posClosingBracket !== false) {
852 $inPassage = false;
853 $cur_pidx = count($passages) - 1;
854 $passages[$cur_pidx]['end_pos'] = $counter;
855
856 $errorobject = $this->errordata[$errorcounter];
857 if (is_object($errorobject)) {
858 $passages[$cur_pidx]['score'] = $errorobject->points;
859 $passages[$cur_pidx]['isError'] = true;
860 }
861
862 $errorcounter++;
863 }
864
865 $cur_pidx = count($passages) - 1;
866 $passages[$cur_pidx]['cnt_words']++;
867 $points = 0;
868 }
869
870 $words[$counter] = array("word" => $word, "points" => $points, "isError" => $isErrorItem);
871 $counter++;
872 }
873 }
874
875 $selections = array();
876 foreach ($passages as $cnt => $pdata) {
877 if (!$withPositivePointsOnly && $pdata['isError'] || $withPositivePointsOnly && $pdata['score'] > 0) {
878 $indexes = range($pdata['begin_pos'], $pdata['end_pos']);
879 $selections[$pdata['begin_pos']] = $indexes;
880 }
881 }
882
883 foreach ($words as $idx => $word) {
884 if (!$withPositivePointsOnly && $word['isError'] || $withPositivePointsOnly && $word['points'] > 0) {
885 $selections[$idx] = array($idx);
886 }
887 }
888
889 ksort($selections);
890
891 $selections = array_values($selections);
892
893 return $selections;
894 }
895
896 protected function getPointsForSelectedPositions($positions)
897 {
898 $passages = array();
899 $words = array();
900 $counter = 0;
901 $errorcounter = 0;
902 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
903 foreach ($textarray as $textidx => $text) {
904 $items = preg_split("/\s+/", $text);
905 $inPassage = false;
906 foreach ($items as $word) {
907 $points = $this->getPointsWrong();
908 if (strpos($word, '#') === 0) {
909 /* Word selection detected */
910 $errorobject = $this->errordata[$errorcounter];
911 if (is_object($errorobject)) {
912 $points = $errorobject->points;
913 }
914 $errorcounter++;
915 } elseif (($posOpeningBracket = strpos($word, '((')) === 0
916 || ($posClosingBracket = strpos($word, '))')) !== false
917 || $inPassage) {
918 /* Passage selection detected */
919
920 if ($posOpeningBracket !== false) {
921 $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
922 $inPassage = true;
923 } elseif ($posClosingBracket !== false) {
924 $inPassage = false;
925 $cur_pidx = count($passages) - 1;
926 $passages[$cur_pidx]['end_pos'] = $counter;
927
928 $errorobject = $this->errordata[$errorcounter];
929 if (is_object($errorobject)) {
930 $passages[$cur_pidx]['score'] = $errorobject->points;
931 }
932 $errorcounter++;
933 }
934
935 $cur_pidx = count($passages) - 1;
936 $passages[$cur_pidx]['cnt_words']++;
937 $points = 0;
938 }
939
940 $words[$counter] = array("word" => $word, "points" => $points);
941 $counter++;
942 }
943 }
944
945 /* Calculate reached points */
946 $total = 0;
947 foreach ($positions as $position) {
948 /* First iterate through positions
949 to identify single-word-selections. */
950
951 $total += $words[$position]['points'];
952 }
953
954 foreach ($passages as $cnt => $p_data) {
955 /* Iterate through configured passages to check
956 wether the entire passage is selected or not.
957 The total points is incremented by the passage's
958 score only if the entire passage is selected. */
959 $isSelected = in_array($p_data['begin_pos'], $positions);
960
961 for ($i = 0; $i < $p_data['cnt_words']; $i++) {
962 $current_pos = $p_data['begin_pos'] + $i;
963 $isSelected = $isSelected && in_array($current_pos, $positions);
964 }
965
966 $total += $isSelected ? $p_data['score'] : 0;
967 }
968
969 return $total;
970 }
971
975 public function flushErrorData()
976 {
977 $this->errordata = array();
978 }
979
980 public function addErrorData($text_wrong, $text_correct, $points)
981 {
982 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
983 array_push($this->errordata, new assAnswerErrorText($text_wrong, $text_correct, $points));
984 }
985
991 public function getErrorData()
992 {
993 return $this->errordata;
994 }
995
1001 public function getErrorText()
1002 {
1003 return $this->errortext;
1004 }
1005
1011 public function setErrorText($a_value)
1012 {
1013 $this->errortext = $this->getHtmlQuestionContentPurifier()->purify($a_value ?? '');
1014 }
1015
1021 public function getTextSize()
1022 {
1023 return $this->textsize;
1024 }
1025
1031 public function setTextSize($a_value)
1032 {
1033 // in self-assesment-mode value should always be set (and must not be null)
1034 if ($a_value === null) {
1035 $a_value = 100;
1036 }
1037 $this->textsize = $a_value;
1038 }
1039
1045 public function getPointsWrong()
1046 {
1047 return $this->points_wrong;
1048 }
1049
1055 public function setPointsWrong($a_value)
1056 {
1057 $this->points_wrong = $a_value;
1058 }
1059
1063 public function __get($value)
1064 {
1065 switch ($value) {
1066 case "errortext":
1067 return $this->getErrorText();
1068 break;
1069 case "textsize":
1070 return $this->getTextSize();
1071 break;
1072 case "points_wrong":
1073 return $this->getPointsWrong();
1074 break;
1075 default:
1076 return parent::__get($value);
1077 break;
1078 }
1079 }
1080
1084 public function __set($key, $value)
1085 {
1086 switch ($key) {
1087 case "errortext":
1088 $this->setErrorText($value);
1089 break;
1090 case "textsize":
1091 $this->setTextSize($value);
1092 break;
1093 case "points_wrong":
1094 $this->setPointsWrong($value);
1095 break;
1096 default:
1097 parent::__set($key, $value);
1098 break;
1099 }
1100 }
1101
1102
1106 public function toJSON()
1107 {
1108 include_once("./Services/RTE/classes/class.ilRTE.php");
1109 $result = array();
1110 $result['id'] = (int) $this->getId();
1111 $result['type'] = (string) $this->getQuestionType();
1112 $result['title'] = (string) $this->getTitle();
1113 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1114 $result['text'] = (string) ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
1115 $result['nr_of_tries'] = (int) $this->getNrOfTries();
1116 $result['shuffle'] = (bool) $this->getShuffle();
1117 $result['feedback'] = array(
1118 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1119 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1120 );
1121
1122 $answers = array();
1123 foreach ($this->getErrorData() as $idx => $answer_obj) {
1124 array_push($answers, array(
1125 "answertext_wrong" => (string) $answer_obj->text_wrong,
1126 "answertext_correct" => (string) $answer_obj->text_correct,
1127 "points" => (float) $answer_obj->points,
1128 "order" => (int) $idx + 1
1129 ));
1130 }
1131 $result['correct_answers'] = $answers;
1132
1133 $answers = array();
1134 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
1135 foreach ($textarray as $textidx => $text) {
1136 $items = preg_split("/\s+/", trim($text));
1137 foreach ($items as $idx => $item) {
1138 if (substr($item, 0, 1) == "#") {
1139 $item = substr($item, 1);
1140
1141 // #14115 - add position to correct answer
1142 foreach ($result["correct_answers"] as $aidx => $answer) {
1143 if ($answer["answertext_wrong"] == $item && !$answer["pos"]) {
1144 $result["correct_answers"][$aidx]["pos"] = $this->getId() . "_" . $textidx . "_" . ($idx + 1);
1145 break;
1146 }
1147 }
1148 }
1149 array_push($answers, array(
1150 "answertext" => (string) ilUtil::prepareFormOutput($item),
1151 "order" => $this->getId() . "_" . $textidx . "_" . ($idx + 1)
1152 ));
1153 }
1154 if ($textidx != sizeof($textarray) - 1) {
1155 array_push($answers, array(
1156 "answertext" => "###",
1157 "order" => $this->getId() . "_" . $textidx . "_" . ($idx + 2)
1158 ));
1159 }
1160 }
1161 $result['answers'] = $answers;
1162
1163 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1164 $result['mobs'] = $mobs;
1165
1166 return json_encode($result);
1167 }
1168
1177 public function getOperators($expression)
1178 {
1179 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1181 }
1182
1187 public function getExpressionTypes()
1188 {
1189 return array(
1194 );
1195 }
1196
1205 public function getUserQuestionResult($active_id, $pass)
1206 {
1208 global $DIC;
1209 $ilDB = $DIC['ilDB'];
1210 $result = new ilUserQuestionResult($this, $active_id, $pass);
1211
1212 $data = $ilDB->queryF(
1213 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1214 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1215 )",
1216 array("integer", "integer", "integer","integer", "integer", "integer"),
1217 array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1218 );
1219
1220 while ($row = $ilDB->fetchAssoc($data)) {
1221 $result->addKeyValue($row["value1"], $row["value1"]);
1222 }
1223
1224 $points = $this->calculateReachedPoints($active_id, $pass);
1225 $max_points = $this->getMaximumPoints();
1226
1227 $result->setReachedPercentage(($points / $max_points) * 100);
1228
1229 return $result;
1230 }
1231
1240 public function getAvailableAnswerOptions($index = null)
1241 {
1242 $error_text_array = explode(' ', $this->errortext);
1243
1244 if ($index !== null) {
1245 if (array_key_exists($index, $error_text_array)) {
1246 return $error_text_array[$index];
1247 }
1248 return null;
1249 } else {
1250 return $error_text_array;
1251 }
1252 }
1253
1259 private function getErrorTokenHtml($item, $class, $useLinkTags)
1260 {
1261 if ($useLinkTags) {
1262 return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>';
1263 }
1264
1265 return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</span>';
1266 }
1267}
$result
$total
Definition: Utf8Test.php:87
$_POST["username"]
An exception for terminatinating execution or to throw for unit testing.
Class for error text answers.
Class for error text question exports.
Class for error text question imports.
Class for error text questions.
getErrorData()
Get error data.
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...
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
flushErrorData()
Flush error data.
loadFromDb($question_id)
Loads the object from the database.
getTextSize()
Set text size in percent.
getOperators($expression)
Get all available operations for a specific question.
__construct( $title='', $comment='', $author='', $owner=-1, $question='')
assErorText constructor
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
getErrorsFromText($a_text="")
__get($value)
Object getter.
createErrorTextExport($selections=null)
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates the object.
savePreviewData(ilAssQuestionPreviewSession $previewSession)
__set($key, $value)
Object setter.
getErrorText()
Get error text.
toJSON()
Returns a JSON representation of the question.
createErrorTextOutput($selections=null, $graphicalOutput=false, $correct_solution=false, $use_link_tags=true)
setTextSize($a_value)
Set text size in percent.
setErrorText($a_value)
Set error text.
getAnswerTableName()
Returns the name of the answer table in the database.
saveToDb($original_id="")
Saves a the object to the database.
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
{Creates an Excel worksheet for the detailed cumulated results of this question.object}
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
copyObject($target_questionpool_id, $title="")
Copies an object.
setPointsWrong($a_value)
Set wrong points.
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping, array $solutionhints=[])
Creates a question from a QTI file.
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
getErrorTokenHtml($item, $class, $useLinkTags)
getBestSelection($withPositivePointsOnly=true)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
getPointsForSelectedPositions($positions)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getAdditionalTableName()
Returns the name of the additional question data table in the database.
getPointsWrong()
Get wrong points.
isTokenSelected($counter, array $selection)
isComplete()
Returns true, if a single choice question is complete for use.
getBestSolution($active_id, $pass)
Returns the best solution for a given pass of a participant.
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
addErrorData($text_wrong, $text_correct, $points)
getExpressionTypes()
Get all available expression types for a specific question.
getQuestionType()
Returns the question type of the question.
Abstract basic class which is to be extended by the concrete assessment question type classes.
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass.
getSolutionValues($active_id, $pass=null, $authorized=true)
Loads solutions of a given user from the database an returns it.
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
setId($id=-1)
Sets the id of the assQuestion object.
setOriginalId($original_id)
setObjId($obj_id=0)
Set the object id of the container object.
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
saveQuestionDataToDb($original_id="")
getId()
Gets the id of the assQuestion object.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
getObjId()
Get the object id of the container object.
setTitle($title="")
Sets the title string of the assQuestion object.
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
removeCurrentSolution($active_id, $pass, $authorized=true)
setAuthor($author="")
Sets the authors name of the assQuestion object.
getShuffle()
Gets the shuffle flag.
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getTitle()
Gets the title string of the assQuestion object.
setPoints($a_points)
Sets the maximum available points for the question.
setComment($comment="")
Sets the comment string of the assQuestion object.
setNrOfTries($a_nr_of_tries)
getQuestion()
Gets the question string of the question object.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
setQuestion($question="")
Sets the question string of the question object.
ensureNonNegativePoints($points)
static _getLogLanguage()
retrieve the log language for assessment logging
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
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...
Base Exception for all Exceptions relating to Modules/Test.
Class ilUserQuestionResult.
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
static prepareFormOutput($a_str, $a_strip=false)
prepares string output for html forms @access public
global $DIC
Definition: goto.php:24
$img
Definition: imgupload.php:57
$mobs
Definition: imgupload.php:54
$errors
Definition: imgupload.php:49
$ilUser
Definition: imgupload.php:18
Class iQuestionCondition.
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
Interface ilObjAnswerScoringAdjustable.
Interface ilObjQuestionScoringAdjustable.
$index
Definition: metadata.php:128
$i
Definition: metadata.php:24
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
global $ilDB
$data
Definition: storeScorm.php:23