ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
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 ) {
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
84 {
85 global $ilDB;
86 $ilDB->manipulateF(
87 "DELETE FROM qpl_a_errortext WHERE question_fi = %s",
88 array( 'integer' ),
89 array( $this->getId() )
90 );
91
92 $sequence = 0;
93 foreach ($this->errordata as $object) {
94 $next_id = $ilDB->nextId('qpl_a_errortext');
95 $ilDB->manipulateF(
96 "INSERT INTO qpl_a_errortext (answer_id, question_fi, text_wrong, text_correct, points, sequence) VALUES (%s, %s, %s, %s, %s, %s)",
97 array( 'integer', 'integer', 'text', 'text', 'float', 'integer' ),
98 array(
99 $next_id,
100 $this->getId(),
101 $object->text_wrong,
102 $object->text_correct,
103 $object->points,
104 $sequence++
105 )
106 );
107 }
108 }
109
116 {
117 global $ilDB;
118 // save additional data
119 $ilDB->manipulateF(
120 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
121 array( "integer" ),
122 array( $this->getId() )
123 );
124
125 $ilDB->manipulateF(
126 "INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, errortext, textsize, points_wrong) VALUES (%s, %s, %s, %s)",
127 array("integer", "text", "float", "float"),
128 array(
129 $this->getId(),
130 $this->getErrorText(),
131 $this->getTextSize(),
132 $this->getPointsWrong()
133 )
134 );
135 }
136
143 public function loadFromDb($question_id)
144 {
145 global $ilDB;
146
147 $result = $ilDB->queryF(
148 "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",
149 array("integer"),
150 array($question_id)
151 );
152 if ($result->numRows() == 1) {
153 $data = $ilDB->fetchAssoc($result);
154 $this->setId($question_id);
155 $this->setObjId($data["obj_fi"]);
156 $this->setTitle($data["title"]);
157 $this->setComment($data["description"]);
158 $this->setOriginalId($data["original_id"]);
159 $this->setNrOfTries($data['nr_of_tries']);
160 $this->setAuthor($data["author"]);
161 $this->setPoints($data["points"]);
162 $this->setOwner($data["owner"]);
163 include_once("./Services/RTE/classes/class.ilRTE.php");
164 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
165 $this->setErrorText($data["errortext"]);
166 $this->setTextSize($data["textsize"]);
167 $this->setPointsWrong($data["points_wrong"]);
168 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
169
170 try {
171 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
172 } catch (ilTestQuestionPoolException $e) {
173 }
174 }
175
176 $result = $ilDB->queryF(
177 "SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
178 array('integer'),
179 array($question_id)
180 );
181 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
182 if ($result->numRows() > 0) {
183 while ($data = $ilDB->fetchAssoc($result)) {
184 array_push($this->errordata, new assAnswerErrorText($data["text_wrong"], $data["text_correct"], $data["points"]));
185 }
186 }
187
188 parent::loadFromDb($question_id);
189 }
190
194 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
195 {
196 if ($this->id <= 0) {
197 // The question has not been saved. It cannot be duplicated
198 return;
199 }
200 // duplicate the question in database
201 $this_id = $this->getId();
202 $thisObjId = $this->getObjId();
203
204 $clone = $this;
205 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
207 $clone->id = -1;
208
209 if ((int) $testObjId > 0) {
210 $clone->setObjId($testObjId);
211 }
212
213 if ($title) {
214 $clone->setTitle($title);
215 }
216
217 if ($author) {
218 $clone->setAuthor($author);
219 }
220 if ($owner) {
221 $clone->setOwner($owner);
222 }
223
224 if ($for_test) {
225 $clone->saveToDb($original_id);
226 } else {
227 $clone->saveToDb();
228 }
229 // copy question page content
230 $clone->copyPageOfQuestion($this_id);
231 // copy XHTML media objects
232 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
233
234 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
235 return $clone->id;
236 }
237
241 public function copyObject($target_questionpool_id, $title = "")
242 {
243 if ($this->id <= 0) {
244 // The question has not been saved. It cannot be duplicated
245 return;
246 }
247 // duplicate the question in database
248
249 $thisId = $this->getId();
250 $thisObjId = $this->getObjId();
251
252 $clone = $this;
253 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
255 $clone->id = -1;
256 $clone->setObjId($target_questionpool_id);
257 if ($title) {
258 $clone->setTitle($title);
259 }
260 $clone->saveToDb();
261
262 // copy question page content
263 $clone->copyPageOfQuestion($original_id);
264 // copy XHTML media objects
265 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
266
267 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
268
269 return $clone->id;
270 }
271
272 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
273 {
274 if ($this->id <= 0) {
275 // The question has not been saved. It cannot be duplicated
276 return;
277 }
278
279 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
280
281 $sourceQuestionId = $this->id;
282 $sourceParentId = $this->getObjId();
283
284 // duplicate the question in database
285 $clone = $this;
286 $clone->id = -1;
287
288 $clone->setObjId($targetParentId);
289
290 if ($targetQuestionTitle) {
291 $clone->setTitle($targetQuestionTitle);
292 }
293
294 $clone->saveToDb();
295 // copy question page content
296 $clone->copyPageOfQuestion($sourceQuestionId);
297 // copy XHTML media objects
298 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
299
300 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
301
302 return $clone->id;
303 }
304
310 public function getMaximumPoints()
311 {
312 $maxpoints = 0.0;
313 foreach ($this->errordata as $object) {
314 if ($object->points > 0) {
315 $maxpoints += $object->points;
316 }
317 }
318 return $maxpoints;
319 }
320
331 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
332 {
333 if ($returndetails) {
334 throw new ilTestException('return details not implemented for ' . __METHOD__);
335 }
336
337 global $ilDB;
338
339 /* First get the positions which were selected by the user. */
340 $positions = array();
341 if (is_null($pass)) {
342 $pass = $this->getSolutionMaxPass($active_id);
343 }
344 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
345
346 while ($row = $ilDB->fetchAssoc($result)) {
347 array_push($positions, $row['value1']);
348 }
349 $points = $this->getPointsForSelectedPositions($positions);
350 return $points;
351 }
352
354 {
355 $reachedPoints = $this->getPointsForSelectedPositions($previewSession->getParticipantsSolution());
356 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
357 return $this->ensureNonNegativePoints($reachedPoints);
358 }
359
368 public function saveWorkingData($active_id, $pass = null, $authorized = true)
369 {
370 global $ilDB;
371 global $ilUser;
372
373 if (is_null($pass)) {
374 include_once "./Modules/Test/classes/class.ilObjTest.php";
375 $pass = ilObjTest::_getPass($active_id);
376 }
377
378 $entered_values = false;
379
380 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
381 $this->removeCurrentSolution($active_id, $pass, $authorized);
382
383 if (strlen($_POST["qst_" . $this->getId()])) {
384 $selected = explode(",", $_POST["qst_" . $this->getId()]);
385 foreach ($selected as $position) {
386 $this->saveCurrentSolution($active_id, $pass, $position, null, $authorized);
387 }
388 $entered_values = true;
389 }
390 });
391
392 if ($entered_values) {
393 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
395 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
396 }
397 } else {
398 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
400 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
401 }
402 }
403
404 return true;
405 }
406
407 public function savePreviewData(ilAssQuestionPreviewSession $previewSession)
408 {
409 if (strlen($_POST["qst_" . $this->getId()])) {
410 $selection = explode(',', $_POST["qst_{$this->getId()}"]);
411 } else {
412 $selection = array();
413 }
414
415 $previewSession->setParticipantsSolution($selection);
416 }
417
421 protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
422 {
423 // nothing to rework!
424 }
425
431 public function getQuestionType()
432 {
433 return "assErrorText";
434 }
435
441 public function getAdditionalTableName()
442 {
443 return "qpl_qst_errortext";
444 }
445
451 public function getAnswerTableName()
452 {
453 return "qpl_a_errortext";
454 }
455
461 {
462 $text = parent::getRTETextWithMediaObjects();
463 return $text;
464 }
465
469 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
470 {
471 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
472
473 $i= 0;
474 $selections = array();
475 $solutions =&$this->getSolutionValues($active_id, $pass);
476 if (is_array($solutions)) {
477 foreach ($solutions as $solution) {
478 array_push($selections, $solution['value1']);
479 }
480 $errortext_value = join(",", $selections);
481 }
482 $errortext = $this->createErrorTextExport($selections);
483 $i++;
484 $worksheet->setCell($startrow+$i, 0, $errortext);
485 $i++;
486
487 return $startrow + $i + 1;
488 }
489
502 public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
503 {
504 include_once "./Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php";
505 $import = new assErrorTextImport($this);
506 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
507 }
508
515 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
516 {
517 include_once "./Modules/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php";
518 $export = new assErrorTextExport($this);
519 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
520 }
521
527 public function getBestSolution($active_id, $pass)
528 {
529 $user_solution = array();
530 return $user_solution;
531 }
532
533 public function getErrorsFromText($a_text = "")
534 {
535 if (strlen($a_text) == 0) {
536 $a_text = $this->getErrorText();
537 }
538
539 /* Workaround to allow '(' and ')' in passages.
540 The beginning- and ending- Passage delimiters are
541 replaced by a ~ (Tilde) symbol. */
542 $a_text = str_replace(array("((", "))"), array("~", "~"), $a_text);
543
544 /* Match either Passage delimited by double brackets
545 or single words marked with a hash (#). */
546 $r_passage = "/(~([^~]+)~|#([^\s]+))/";
547
548 preg_match_all($r_passage, $a_text, $matches);
549
550 if (is_array($matches[0]) && !empty($matches[0])) {
551 /* At least one match. */
552
553 /* We need only groups 2 and 3, respectively representing
554 passage matches and single word matches. */
555 $matches = array_intersect_key($matches, array(2 => '', 3 => ''));
556
557 /* Remove empty values. */
558 $matches[2] = array_diff($matches[2], array(''));
559 $matches[3] = array_diff($matches[3], array(''));
560
561 return array(
562 "passages" => $matches[2],
563 "words" => $matches[3],);
564 }
565
566 return array();
567 }
568
569 public function setErrorData($a_data)
570 {
571 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
572 $temp = $this->errordata;
573 $this->errordata = array();
574 foreach ($a_data as $err_type => $errors) {
575 /* Iterate through error types (Passages|single words) */
576
577 foreach ($errors as $idx => $error) {
578 /* Iterate through errors of this type. */
579 $text_correct = "";
580 $points = 0.0;
581 foreach ($temp as $object) {
582 if (strcmp($object->text_wrong, $error) == 0) {
583 $text_correct = $object->text_correct;
584 $points = $object->points;
585 continue;
586 }
587 }
588 $this->errordata[$idx] = new assAnswerErrorText($error, $text_correct, $points);
589 }
590 }
591 ksort($this->errordata);
592 }
593
594 public function createErrorTextOutput($selections = null, $graphicalOutput = false, $correct_solution = false, $use_link_tags = true)
595 {
596 $counter = 0;
597 $errorcounter = 0;
598 include_once "./Services/Utilities/classes/class.ilStr.php";
599 if (!is_array($selections)) {
600 $selections = array();
601 }
602 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
603
604 foreach ($textarray as $textidx => $text) {
605 $in_passage = false;
606 $passage_end = false;
607 $items = preg_split("/\s+/", $text);
608 foreach ($items as $idx => $item) {
609 $img = '';
610
611 if (
612 ($posHash = strpos($item, '#')) === 0 ||
613 ($posOpeningBrackets = strpos($item, '((')) === 0 ||
614 ($posClosingBrackets = strpos($item, '))')) !== false
615 ) {
616 /* (Word|Passage)-Marking delimiter found. */
617
618 if ($posHash !== false) {
619 $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
620 $passage_end = false;
621 } elseif ($posOpeningBrackets !== false) {
622 $in_passage = true;
623 $passage_start_idx = $counter;
624 $items_in_passage = array();
625 $passage_end = false;
626 $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
627
628 /* Sometimes a closing bracket group needs
629 to be removed as well. */
630 if (strpos($item, '))') !== false) {
631 $item = str_replace("))", "", $item);
632 $passage_end = true;
633 }
634 } else {
635 $passage_end = true;
636 $item = str_replace("))", "", $item);
637 }
638
639 if ($correct_solution && !$in_passage) {
640 $errorobject = $this->errordata[$errorcounter];
641 if (is_object($errorobject)) {
642 $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
643 }
644 $errorcounter++;
645 }
646 }
647
648 if ($in_passage && !$passage_end) {
649 $items_in_passage[$idx] = $item;
650 $items[$idx] = '';
651 $counter++;
652 continue;
653 }
654
655 if ($in_passage && $passage_end) {
656 $in_passage = false;
657 $passage_end = false;
658 if ($correct_solution) {
659 $class = (
660 $this->isTokenSelected($counter, $selections) ?
661 "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
662 );
663
664 $errorobject = $this->errordata[$errorcounter];
665 if (is_object($errorobject)) {
666 $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
667 }
668 $errorcounter++;
669 $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
670 $counter++;
671 continue;
672 }
673
674 $group_selected = true;
675 if ($graphicalOutput) {
676 $start_idx = $passage_start_idx;
677 foreach ($items_in_passage as $tmp_idx => $tmp_item) {
678 if (!$this->isTokenSelected($start_idx, $selections)) {
679 $group_selected = false;
680 break;
681 }
682
683 ++$start_idx;
684 }
685 if ($group_selected) {
686 if (!$this->isTokenSelected($counter, $selections)) {
687 $group_selected = false;
688 }
689 }
690 }
691
692 $item_stack = array();
693 $start_idx = $passage_start_idx;
694 foreach ($items_in_passage as $tmp_idx => $tmp_item) {
695 $class = (
696 $this->isTokenSelected($counter, $selections) ?
697 "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
698 );
699 $item_stack[] = $this->getErrorTokenHtml($tmp_item, $class, $use_link_tags) . $img;
700 $start_idx++;
701 }
702 $class = (
703 $this->isTokenSelected($counter, $selections) ?
704 "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
705 );
706 if ($graphicalOutput) {
707 if ($group_selected) {
708 $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
709 } else {
710 $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
711 }
712 }
713
714 $item_stack[] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
715 $item_stack = trim(implode(" ", $item_stack));
716 $item_stack = strlen($item_stack) ? $item_stack : '&nbsp;';
717
718 if ($graphicalOutput) {
719 $items[$idx] = '<span class="selGroup">' . $item_stack . '</span>';
720 } else {
721 $items[$idx] = $item_stack;
722 }
723
724 $counter++;
725 continue;
726 }
727
728 // Errors markes with #, group errors (()) are handled above
729 $class = 'ilc_qetitem_ErrorTextItem';
730 $img = '';
731 if ($this->isTokenSelected($counter, $selections)) {
732 $class = "ilc_qetitem_ErrorTextSelected";
733 if ($graphicalOutput) {
734 if ($this->getPointsForSelectedPositions(array($counter)) > 0) {
735 $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
736 } else {
737 $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
738 }
739 }
740 }
741
742 $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
743 $counter++;
744 }
745 $textarray[$textidx] = '<p>' . implode(" ", $items) . '</p>';
746 }
747
748 return implode("\n", $textarray);
749 }
750
751 protected function isTokenSelected($counter, array $selection)
752 {
753 foreach ($selection as $data) {
754 if (!is_array($data)) {
755 if ($counter == $data) {
756 return true;
757 }
758 } elseif (in_array($counter, $data)) {
759 return true;
760 }
761 }
762
763 return false;
764 }
765
766 public function createErrorTextExport($selections = null)
767 {
768 $counter = 0;
769 $errorcounter = 0;
770 include_once "./Services/Utilities/classes/class.ilStr.php";
771 if (!is_array($selections)) {
772 $selections = array();
773 }
774 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
775 foreach ($textarray as $textidx => $text) {
776 $items = preg_split("/\s+/", $text);
777 foreach ($items as $idx => $item) {
778 if (($posHash = strpos($item, '#')) === 0
779 || ($posOpeningBrackets = strpos($item, '((')) === 0
780 || ($posClosingBrackets = strpos($item, '))')) !== false) {
781 /* (Word|Passage)-Marking delimiter found. */
782
783 if ($posHash !== false) {
784 $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
785 } elseif ($posOpeningBrackets !== false) {
786 $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
787
788 /* Sometimes a closing bracket group needs
789 to be removed as well. */
790 if (strpos($item, '))') !== false) {
791 $item = ilStr::substr($item, 0, ilStr::strlen($item) - 2);
792 }
793 } else {
794 $appendComma = "";
795 if ($item{$posClosingBrackets+2} == ',') {
796 $appendComma = ",";
797 }
798
799 $item = ilStr::substr($item, 0, $posClosingBrackets) . $appendComma;
800 }
801 }
802
803 $word = "";
804 if (in_array($counter, $selections)) {
805 $word .= '#';
806 }
807 $word .= ilUtil::prepareFormOutput($item);
808 if (in_array($counter, $selections)) {
809 $word .= '#';
810 }
811 $items[$idx] = $word;
812 $counter++;
813 }
814 $textarray[$textidx] = join($items, " ");
815 }
816 return join($textarray, "\n");
817 }
818
819 public function getBestSelection($withPositivePointsOnly = true)
820 {
821 $passages = array();
822 $words = array();
823 $counter = 0;
824 $errorcounter = 0;
825 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
826 foreach ($textarray as $textidx => $text) {
827 $items = preg_split("/\s+/", $text);
828 $inPassage = false;
829 foreach ($items as $word) {
830 $points = $this->getPointsWrong();
831 $isErrorItem = false;
832 if (strpos($word, '#') === 0) {
833 /* Word selection detected */
834 $errorobject = $this->errordata[$errorcounter];
835 if (is_object($errorobject)) {
836 $points = $errorobject->points;
837 $isErrorItem = true;
838 }
839 $errorcounter++;
840 } elseif (($posOpeningBracket = strpos($word, '((')) === 0
841 || ($posClosingBracket = strpos($word, '))')) !== false
842 || $inPassage) {
843 /* Passage selection detected */
844
845 if ($posOpeningBracket !== false) {
846 $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
847 $inPassage = true;
848 } elseif ($posClosingBracket !== false) {
849 $inPassage = false;
850 $cur_pidx = count($passages) - 1;
851 $passages[$cur_pidx]['end_pos'] = $counter;
852
853 $errorobject = $this->errordata[$errorcounter];
854 if (is_object($errorobject)) {
855 $passages[$cur_pidx]['score'] = $errorobject->points;
856 $passages[$cur_pidx]['isError'] = true;
857 }
858
859 $errorcounter++;
860 }
861
862 $cur_pidx = count($passages) - 1;
863 $passages[$cur_pidx]['cnt_words']++;
864 $points = 0;
865 }
866
867 $words[$counter] = array("word" => $word, "points" => $points, "isError" => $isErrorItem);
868 $counter++;
869 }
870 }
871
872 $selections = array();
873 foreach ($passages as $cnt => $pdata) {
874 if (!$withPositivePointsOnly && $pdata['isError'] || $withPositivePointsOnly && $pdata['score'] > 0) {
875 $indexes = range($pdata['begin_pos'], $pdata['end_pos']);
876 $selections[$pdata['begin_pos']] = $indexes;
877 }
878 }
879
880 foreach ($words as $idx => $word) {
881 if (!$withPositivePointsOnly && $word['isError'] || $withPositivePointsOnly && $word['points'] > 0) {
882 $selections[$idx] = array($idx);
883 }
884 }
885
886 ksort($selections);
887
888 $selections = array_values($selections);
889
890 return $selections;
891 }
892
893 protected function getPointsForSelectedPositions($positions)
894 {
895 $passages = array();
896 $words = array();
897 $counter = 0;
898 $errorcounter = 0;
899 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
900 foreach ($textarray as $textidx => $text) {
901 $items = preg_split("/\s+/", $text);
902 $inPassage = false;
903 foreach ($items as $word) {
904 $points = $this->getPointsWrong();
905 if (strpos($word, '#') === 0) {
906 /* Word selection detected */
907 $errorobject = $this->errordata[$errorcounter];
908 if (is_object($errorobject)) {
909 $points = $errorobject->points;
910 }
911 $errorcounter++;
912 } elseif (($posOpeningBracket = strpos($word, '((')) === 0
913 || ($posClosingBracket = strpos($word, '))')) !== false
914 || $inPassage) {
915 /* Passage selection detected */
916
917 if ($posOpeningBracket !== false) {
918 $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
919 $inPassage = true;
920 } elseif ($posClosingBracket !== false) {
921 $inPassage = false;
922 $cur_pidx = count($passages) - 1;
923 $passages[$cur_pidx]['end_pos'] = $counter;
924
925 $errorobject = $this->errordata[$errorcounter];
926 if (is_object($errorobject)) {
927 $passages[$cur_pidx]['score'] = $errorobject->points;
928 }
929 $errorcounter++;
930 }
931
932 $cur_pidx = count($passages) - 1;
933 $passages[$cur_pidx]['cnt_words']++;
934 $points = 0;
935 }
936
937 $words[$counter] = array("word" => $word, "points" => $points);
938 $counter++;
939 }
940 }
941
942 /* Calculate reached points */
943 $total = 0;
944 foreach ($positions as $position) {
945 /* First iterate through positions
946 to identify single-word-selections. */
947
948 $total += $words[$position]['points'];
949 }
950
951 foreach ($passages as $cnt => $p_data) {
952 /* Iterate through configured passages to check
953 wether the entire passage is selected or not.
954 The total points is incremented by the passage's
955 score only if the entire passage is selected. */
956 $isSelected = in_array($p_data['begin_pos'], $positions);
957
958 for ($i = 0; $i < $p_data['cnt_words']; $i++) {
959 $current_pos = $p_data['begin_pos'] + $i;
960 $isSelected = $isSelected && in_array($current_pos, $positions);
961 }
962
963 $total += $isSelected ? $p_data['score'] : 0;
964 }
965
966 return $total;
967 }
968
972 public function flushErrorData()
973 {
974 $this->errordata = array();
975 }
976
977 public function addErrorData($text_wrong, $text_correct, $points)
978 {
979 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
980 array_push($this->errordata, new assAnswerErrorText($text_wrong, $text_correct, $points));
981 }
982
988 public function getErrorData()
989 {
990 return $this->errordata;
991 }
992
998 public function getErrorText()
999 {
1000 return $this->errortext;
1001 }
1002
1008 public function setErrorText($a_value)
1009 {
1010 $this->errortext = $a_value;
1011 }
1012
1018 public function getTextSize()
1019 {
1020 return $this->textsize;
1021 }
1022
1028 public function setTextSize($a_value)
1029 {
1030 // in self-assesment-mode value should always be set (and must not be null)
1031 if ($a_value === null) {
1032 $a_value = 100;
1033 }
1034 $this->textsize = $a_value;
1035 }
1036
1042 public function getPointsWrong()
1043 {
1044 return $this->points_wrong;
1045 }
1046
1052 public function setPointsWrong($a_value)
1053 {
1054 $this->points_wrong = $a_value;
1055 }
1056
1060 public function __get($value)
1061 {
1062 switch ($value) {
1063 case "errortext":
1064 return $this->getErrorText();
1065 break;
1066 case "textsize":
1067 return $this->getTextSize();
1068 break;
1069 case "points_wrong":
1070 return $this->getPointsWrong();
1071 break;
1072 default:
1073 return parent::__get($value);
1074 break;
1075 }
1076 }
1077
1081 public function __set($key, $value)
1082 {
1083 switch ($key) {
1084 case "errortext":
1085 $this->setErrorText($value);
1086 break;
1087 case "textsize":
1088 $this->setTextSize($value);
1089 break;
1090 case "points_wrong":
1091 $this->setPointsWrong($value);
1092 break;
1093 default:
1094 parent::__set($key, $value);
1095 break;
1096 }
1097 }
1098
1099
1103 public function toJSON()
1104 {
1105 include_once("./Services/RTE/classes/class.ilRTE.php");
1106 $result = array();
1107 $result['id'] = (int) $this->getId();
1108 $result['type'] = (string) $this->getQuestionType();
1109 $result['title'] = (string) $this->getTitle();
1110 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1111 $result['text'] = (string) ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
1112 $result['nr_of_tries'] = (int) $this->getNrOfTries();
1113 $result['shuffle'] = (bool) $this->getShuffle();
1114 $result['feedback'] = array(
1115 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1116 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1117 );
1118
1119 $answers = array();
1120 foreach ($this->getErrorData() as $idx => $answer_obj) {
1121 array_push($answers, array(
1122 "answertext_wrong" => (string) $answer_obj->text_wrong,
1123 "answertext_correct" => (string) $answer_obj->text_correct,
1124 "points" => (float) $answer_obj->points,
1125 "order" => (int) $idx+1
1126 ));
1127 }
1128 $result['correct_answers'] = $answers;
1129
1130 $answers = array();
1131 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
1132 foreach ($textarray as $textidx => $text) {
1133 $items = preg_split("/\s+/", trim($text));
1134 foreach ($items as $idx => $item) {
1135 if (substr($item, 0, 1) == "#") {
1136 $item = substr($item, 1);
1137
1138 // #14115 - add position to correct answer
1139 foreach ($result["correct_answers"] as $aidx => $answer) {
1140 if ($answer["answertext_wrong"] == $item && !$answer["pos"]) {
1141 $result["correct_answers"][$aidx]["pos"] = $this->getId() . "_" . $textidx . "_" . ($idx+1);
1142 break;
1143 }
1144 }
1145 }
1146 array_push($answers, array(
1147 "answertext" => (string) ilUtil::prepareFormOutput($item),
1148 "order" => $this->getId() . "_" . $textidx . "_" . ($idx+1)
1149 ));
1150 }
1151 if ($textidx != sizeof($textarray)-1) {
1152 array_push($answers, array(
1153 "answertext" => "###",
1154 "order" => $this->getId() . "_" . $textidx . "_" . ($idx+2)
1155 ));
1156 }
1157 }
1158 $result['answers'] = $answers;
1159
1160 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1161 $result['mobs'] = $mobs;
1162
1163 return json_encode($result);
1164 }
1165
1174 public function getOperators($expression)
1175 {
1176 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1178 }
1179
1184 public function getExpressionTypes()
1185 {
1186 return array(
1191 );
1192 }
1193
1202 public function getUserQuestionResult($active_id, $pass)
1203 {
1205 global $ilDB;
1206 $result = new ilUserQuestionResult($this, $active_id, $pass);
1207
1208 $data = $ilDB->queryF(
1209 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1210 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1211 )",
1212 array("integer", "integer", "integer","integer", "integer", "integer"),
1213 array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1214 );
1215
1216 while ($row = $ilDB->fetchAssoc($data)) {
1217 $result->addKeyValue($row["value1"], $row["value1"]);
1218 }
1219
1220 $points = $this->calculateReachedPoints($active_id, $pass);
1221 $max_points = $this->getMaximumPoints();
1222
1223 $result->setReachedPercentage(($points/$max_points) * 100);
1224
1225 return $result;
1226 }
1227
1236 public function getAvailableAnswerOptions($index = null)
1237 {
1238 $error_text_array = explode(' ', $this->errortext);
1239
1240 if ($index !== null) {
1241 if (array_key_exists($index, $error_text_array)) {
1242 return $error_text_array[$index];
1243 }
1244 return null;
1245 } else {
1246 return $error_text_array;
1247 }
1248 }
1249
1255 private function getErrorTokenHtml($item, $class, $useLinkTags)
1256 {
1257 if ($useLinkTags) {
1258 return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>';
1259 }
1260
1261 return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</span>';
1262 }
1263}
$worksheet
$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.
reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
{Reworks the allready saved working data if neccessary.}
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.
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.
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
Creates a question from a QTI file.
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.
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
$counter
$key
Definition: croninfo.php:18
$i
Definition: disco.tpl.php:19
Class iQuestionCondition.
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
Interface ilObjAnswerScoringAdjustable.
Interface ilObjQuestionScoringAdjustable.
$error
Definition: Error.php:17
$index
Definition: metadata.php:60
$errors
Definition: index.php:6
global $ilDB
$mobs
$ilUser
Definition: imgupload.php:18
$text
Definition: errorreport.php:18