ILIAS  release_8 Revision v8.24
class.assErrorText.php
Go to the documentation of this file.
1<?php
2
19require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20
34{
35 protected const ERROR_TYPE_WORD = 1;
36 protected const ERROR_TYPE_PASSAGE = 2;
37 protected const DEFAULT_TEXT_SIZE = 100.0;
38 protected const ERROR_MAX_LENGTH = 150;
39
40 protected const PARAGRAPH_SPLIT_REGEXP = '/[\n\r]+/';
41 protected const WORD_SPLIT_REGEXP = '/\s+/';
42 protected const FIND_PUNCTUATION_REGEXP = '/\p{P}/';
43 protected const ERROR_WORD_MARKER = '#';
44 protected const ERROR_PARAGRAPH_DELIMITERS = [
45 'start' => '((',
46 'end' => '))'
47 ];
48
49 protected string $errortext = '';
50 protected array $parsed_errortext = [];
52 protected array $errordata = [];
53 protected float $textsize;
54 protected ?float $points_wrong;
55
65 public function __construct(
66 $title = '',
67 $comment = '',
68 $author = '',
69 $owner = -1,
70 $question = ''
71 ) {
73 $this->textsize = self::DEFAULT_TEXT_SIZE;
74 }
75
81 public function isComplete(): bool
82 {
83 if (mb_strlen($this->title)
84 && ($this->author)
85 && ($this->question)
86 && ($this->getMaximumPoints() > 0)) {
87 return true;
88 } else {
89 return false;
90 }
91 }
92
97 public function saveToDb($original_id = ""): void
98 {
99 if ($original_id == '') {
100 $this->saveQuestionDataToDb();
101 } else {
103 }
104
107 parent::saveToDb();
108 }
109
111 {
112 $this->db->manipulateF(
113 "DELETE FROM qpl_a_errortext WHERE question_fi = %s",
114 ['integer'],
115 [$this->getId()]
116 );
117
118 $sequence = 0;
119 foreach ($this->errordata as $error) {
120 $next_id = $this->db->nextId('qpl_a_errortext');
121 $this->db->manipulateF(
122 "INSERT INTO qpl_a_errortext (answer_id, question_fi, text_wrong, text_correct, points, sequence, position) VALUES (%s, %s, %s, %s, %s, %s, %s)",
123 ['integer', 'integer', 'text', 'text', 'float', 'integer', 'integer'],
124 [
125 $next_id,
126 $this->getId(),
127 $error->getTextWrong(),
128 $error->getTextCorrect(),
129 $error->getPoints(),
130 $sequence++,
131 $error->getPosition()
132 ]
133 );
134 }
135 }
136
143 {
144 $this->db->manipulateF(
145 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
146 ["integer"],
147 [$this->getId()]
148 );
149
150 $this->db->manipulateF(
151 "INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, errortext, parsed_errortext, textsize, points_wrong) VALUES (%s, %s, %s, %s, %s)",
152 ["integer", "text", "text", "float", "float"],
153 [
154 $this->getId(),
155 $this->getErrorText(),
156 json_encode($this->getParsedErrorText()),
157 $this->getTextSize(),
158 $this->getPointsWrong()
159 ]
160 );
161 }
162
169 public function loadFromDb($question_id): void
170 {
171 $db_question = $this->db->queryF(
172 "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",
173 ["integer"],
174 [$question_id]
175 );
176 if ($db_question->numRows() === 1) {
177 $data = $this->db->fetchAssoc($db_question);
178 $this->setId($question_id);
179 $this->setObjId($data["obj_fi"]);
180 $this->setTitle((string) $data["title"]);
181 $this->setComment((string) $data["description"]);
182 $this->setOriginalId($data["original_id"]);
183 $this->setNrOfTries($data['nr_of_tries']);
184 $this->setAuthor($data["author"]);
185 $this->setPoints($data["points"]);
186 $this->setOwner($data["owner"]);
187 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data["question_text"], 1));
188 $this->setErrorText((string) $data["errortext"]);
189 $this->setParsedErrorText(json_decode($data['parsed_errortext'], true) ?? []);
190 $this->setTextSize($data["textsize"]);
191 $this->setPointsWrong($data["points_wrong"]);
192
193 try {
194 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
197 }
198
199 try {
200 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
202 }
203 }
204
205 $db_error_text = $this->db->queryF(
206 "SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
207 ['integer'],
208 [$question_id]
209 );
210 if ($db_error_text->numRows() > 0) {
211 while ($data = $this->db->fetchAssoc($db_error_text)) {
212 $this->errordata[] = new assAnswerErrorText(
213 (string) $data['text_wrong'],
214 (string) $data['text_correct'],
215 (float) $data['points'],
216 $data['position']
217 );
218 }
219 }
220
222
223 parent::loadFromDb($question_id);
224 }
225
226 private function correctDataAfterParserUpdate(): void
227 {
228 if ($this->getErrorText() === '') {
229 return;
230 }
231 $needs_finalizing = false;
232 if ($this->getParsedErrorText() === []) {
233 $needs_finalizing = true;
234 $this->parseErrorText();
235 }
236
237 if (isset($this->errordata[0])
238 && $this->errordata[0]->getPosition() === null) {
239 foreach ($this->errordata as $key => $error) {
240 $this->errordata[$key] = $this->addPositionToErrorAnswer($error);
241 }
243 }
244
245 if ($needs_finalizing) {
248 }
249 }
250
254 public function duplicate(bool $for_test = true, string $title = "", string $author = "", string $owner = "", $testObjId = null): int
255 {
256 if ($this->id <= 0) {
257 // The question has not been saved. It cannot be duplicated
258 return -1;
259 }
260 // duplicate the question in database
261 $this_id = $this->getId();
262 $thisObjId = $this->getObjId();
263
264 $clone = $this;
266 $clone->id = -1;
267
268 if ((int) $testObjId > 0) {
269 $clone->setObjId($testObjId);
270 }
271
272 if ($title) {
273 $clone->setTitle($title);
274 }
275
276 if ($author) {
277 $clone->setAuthor($author);
278 }
279 if ($owner) {
280 $clone->setOwner($owner);
281 }
282
283 if ($for_test) {
284 $clone->saveToDb($original_id);
285 } else {
286 $clone->saveToDb();
287 }
288 // copy question page content
289 $clone->copyPageOfQuestion($this_id);
290 // copy XHTML media objects
291 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
292
293 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
294 return $clone->id;
295 }
296
300 public function copyObject($target_questionpool_id, $title = ""): int
301 {
302 if ($this->getId() <= 0) {
303 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
304 }
305 // duplicate the question in database
306
307 $thisId = $this->getId();
308 $thisObjId = $this->getObjId();
309
310 $clone = $this;
312 $clone->id = -1;
313 $clone->setObjId($target_questionpool_id);
314 if ($title) {
315 $clone->setTitle($title);
316 }
317 $clone->saveToDb();
318
319 // copy question page content
320 $clone->copyPageOfQuestion($original_id);
321 // copy XHTML media objects
322 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
323
324 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
325
326 return $clone->id;
327 }
328
329 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = ""): int
330 {
331 if ($this->getId() <= 0) {
332 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
333 }
334
335 $sourceQuestionId = $this->id;
336 $sourceParentId = $this->getObjId();
337
338 // duplicate the question in database
339 $clone = $this;
340 $clone->id = -1;
341
342 $clone->setObjId($targetParentId);
343
344 if ($targetQuestionTitle) {
345 $clone->setTitle($targetQuestionTitle);
346 }
347
348 $clone->saveToDb();
349 // copy question page content
350 $clone->copyPageOfQuestion($sourceQuestionId);
351 // copy XHTML media objects
352 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
353
354 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
355
356 return $clone->id;
357 }
358
364 public function getMaximumPoints(): float
365 {
366 $maxpoints = 0.0;
367 foreach ($this->errordata as $error) {
368 if ($error->getPoints() > 0) {
369 $maxpoints += $error->getPoints();
370 }
371 }
372 return $maxpoints;
373 }
374
385 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): float
386 {
387 if ($returndetails) {
388 throw new ilTestException('return details not implemented for ' . __METHOD__);
389 }
390
391 /* First get the positions which were selected by the user. */
392 $positions = [];
393 if (is_null($pass)) {
394 $pass = $this->getSolutionMaxPass($active_id);
395 }
396 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
397
398 while ($row = $this->db->fetchAssoc($result)) {
399 $positions[] = $row['value1'];
400 }
401 $points = $this->getPointsForSelectedPositions($positions);
402 return $points;
403 }
404
406 {
407 $reached_points = $this->getPointsForSelectedPositions($preview_session->getParticipantsSolution() ?? []);
408 $reached_points = $this->deductHintPointsFromReachedPoints($preview_session, $reached_points);
409 return $this->ensureNonNegativePoints($reached_points);
410 }
411
420 public function saveWorkingData($active_id, $pass = null, $authorized = true): bool
421 {
422 if (is_null($pass)) {
423 $pass = ilObjTest::_getPass($active_id);
424 }
425
426 $selected = $this->getAnswersFromRequest();
427 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
428 function () use ($selected, $active_id, $pass, $authorized) {
429 $this->removeCurrentSolution($active_id, $pass, $authorized);
430
431 foreach ($selected as $position) {
432 $this->saveCurrentSolution($active_id, $pass, $position, null, $authorized);
433 }
434 }
435 );
436
438 $this->logUserAction($selected !== [], (int) $active_id);
439 }
440
441 return true;
442 }
443
444 public function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
445 {
446 $selection = $this->getAnswersFromRequest();
447 $previewSession->setParticipantsSolution($selection);
448 }
449
450 private function logUserAction(bool $user_entered_values, int $active_id): void
451 {
452 $log_text = $this->lng->txtlng(
453 "assessment",
454 $user_entered_values ? 'log_user_entered_values' : 'log_user_not_entered_values',
456 );
457 assQuestion::logAction($log_text, $active_id, $this->getId());
458 }
459
460 private function getAnswersFromRequest(): array
461 {
462 if (mb_strlen($_POST["qst_" . $this->getId()])) {
463 return explode(',', $_POST["qst_{$this->getId()}"]);
464 }
465
466 return [];
467 }
468
469 public function getQuestionType(): string
470 {
471 return 'assErrorText';
472 }
473
474 public function getAdditionalTableName(): string
475 {
476 return 'qpl_qst_errortext';
477 }
478
479 public function getAnswerTableName(): string
480 {
481 return 'qpl_a_errortext';
482 }
483
487 public function setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass): int
488 {
489 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
490
491 $i = 0;
492 $selections = [];
493 $solutions = $this->getSolutionValues($active_id, $pass);
494 if (is_array($solutions)) {
495 foreach ($solutions as $solution) {
496 $selections[] = $solution['value1'];
497 }
498 }
499 $errortext = $this->createErrorTextExport($selections);
500 $i++;
501 $worksheet->setCell($startrow + $i, 2, $errortext);
502 $i++;
503
504 return $startrow + $i + 1;
505 }
506
507 public function fromXML($item, int $questionpool_id, ?int $tst_id, &$tst_object, int &$question_counter, array $import_mapping, array &$solutionhints = []): array
508 {
509 $import = new assErrorTextImport($this);
510 return $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
511 }
512
513 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false): string
514 {
515 $export = new assErrorTextExport($this);
516 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
517 }
518
519 public function setErrorsFromParsedErrorText(): void
520 {
521 $current_error_data = $this->getErrorData();
522 $this->errordata = [];
523
524 $has_too_long_errors = false;
525 foreach ($this->getParsedErrorText() as $paragraph) {
526 foreach ($paragraph as $position => $word) {
527 if ($word['error_type'] === 'in_passage'
528 || $word['error_type'] === 'passage_end'
529 || $word['error_type'] === 'none') {
530 continue;
531 }
532
533 $text_wrong = $word['text_wrong'];
534 if (mb_strlen($text_wrong) > self::ERROR_MAX_LENGTH) {
535 $has_too_long_errors = true;
536 continue;
537 }
538
539 list($text_correct, $points) =
540 $this->getAdditionalInformationFromExistingErrorDataByErrorText($current_error_data, $text_wrong);
541 $this->errordata[] = new assAnswerErrorText($text_wrong, $text_correct, $points, $position);
542 }
543 }
544
545 if ($has_too_long_errors) {
546 $this->tpl->setOnScreenMessage(
547 'failure',
548 $this->lng->txt('qst_error_text_too_long')
549 );
550 }
551 }
552
554 {
555 foreach ($this->getParsedErrorText() as $paragraph) {
556 foreach ($paragraph as $position => $word) {
557 if (isset($word['text_wrong'])
558 && ($word['text_wrong'] === $error->getTextWrong()
559 || mb_substr($word['text_wrong'], 0, -1) === $error->getTextWrong()
560 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word['text_wrong'], -1)) === 1)
561 && !array_key_exists($position, $this->generateArrayByPositionFromErrorData())
562 ) {
563 return $error->withPosition($position);
564 }
565
566 }
567 }
568
569 return $error;
570 }
571
573 {
574 foreach ($this->errordata as $error) {
575 $position = $error->getPosition();
576 foreach ($this->getParsedErrorText() as $key => $paragraph) {
577 if (array_key_exists($position, $paragraph)) {
578 $this->parsed_errortext[$key][$position]['text_correct'] =
579 $error->getTextCorrect();
580 $this->parsed_errortext[$key][$position]['points'] =
581 $error->getPoints();
582 break;
583 }
584 }
585 }
586 }
587
592 public function setErrorData(array $errors): void
593 {
594 $this->errordata = [];
595
596 foreach ($errors as $error) {
597 $answer = $this->addPositionToErrorAnswer($error);
598 $this->errordata[] = $answer;
599 }
601 }
602
603 public function removeErrorDataWithoutPosition(): void
604 {
605 foreach ($this->getErrorData() as $index => $error) {
606 if ($error->getPosition() === null) {
607 unset($this->errordata[$index]);
608 }
609 }
610 $this->errordata = array_values($this->errordata);
611 }
612
619 array $current_error_data,
620 string $text_wrong
621 ): array {
622 foreach ($current_error_data as $answer_object) {
623 if (strcmp($answer_object->getTextWrong(), $text_wrong) === 0) {
624 return[
625 $answer_object->getTextCorrect(),
626 $answer_object->getPoints()
627 ];
628 }
629 }
630 return ['', 0.0];
631 }
632
633 public function assembleErrorTextOutput(
634 array $selections,
635 bool $graphical_output = false,
636 bool $show_correct_solution = false,
637 bool $use_link_tags = true,
638 array $correctness_icons = []
639 ): string {
640 $output_array = [];
641 foreach ($this->getParsedErrorText() as $paragraph) {
642 $array_reduce_function = fn (?string $carry, int $position)
643 => $carry . $this->generateOutputStringFromPosition(
644 $position,
645 $selections,
646 $paragraph,
647 $graphical_output,
648 $show_correct_solution,
649 $use_link_tags,
650 $correctness_icons
651 );
652 $output_array[] = '<p>' . trim(array_reduce(array_keys($paragraph), $array_reduce_function)) . '</p>';
653 }
654
655 return implode("\n", $output_array);
656 }
657
659 int $position,
660 array $selections,
661 array $paragraph,
662 bool $graphical_output,
663 bool $show_correct_solution,
664 bool $use_link_tags,
665 array $correctness_icons
666 ): string {
667 $text = $this->getTextForPosition($position, $paragraph, $show_correct_solution);
668 if ($text === '') {
669 return '';
670 }
671 $class = $this->getClassForPosition($position, $show_correct_solution, $selections);
672 $img = $this->getCorrectnessIconForPosition(
673 $position,
674 $graphical_output,
675 $selections,
676 $correctness_icons
677 );
678
679 return ' ' . $this->getErrorTokenHtml($text, $class, $use_link_tags) . $img;
680 }
681
682 private function getTextForPosition(
683 int $position,
684 array $paragraph,
685 bool $show_correct_solution
686 ): string {
687 $v = $paragraph[$position];
688 if ($show_correct_solution === true
689 && ($v['error_type'] === 'in_passage'
690 || $v['error_type'] === 'passage_end')) {
691 return '';
692 }
693 if ($show_correct_solution
694 && ($v['error_type'] === 'passage_start'
695 || $v['error_type'] === 'word')) {
696 return $v['text_correct'] ?? '';
697 }
698
699 return $v['text'];
700 }
701
702 private function getClassForPosition(
703 int $position,
704 bool $show_correct_solution,
705 array $selections
706 ): string {
707 if ($show_correct_solution !== true
708 && in_array($position, $selections['user'])) {
709 return 'ilc_qetitem_ErrorTextSelected';
710 }
711
712 if ($show_correct_solution === true
713 && in_array($position, $selections['best'])) {
714 return 'ilc_qetitem_ErrorTextSelected';
715 }
716
717 return 'ilc_qetitem_ErrorTextItem';
718 }
719
721 int $position,
722 bool $graphical_output,
723 array $selections,
724 array $correctness_icons
725 ): string {
726 if ($graphical_output === true
727 && (in_array($position, $selections['user']) && !in_array($position, $selections['best'])
728 || !in_array($position, $selections['user']) && in_array($position, $selections['best']))) {
729 return $correctness_icons['not_correct'];
730 }
731
732 if ($graphical_output === true
733 && in_array($position, $selections['user']) && in_array($position, $selections['best'])) {
734 return $correctness_icons['correct'];
735 }
736
737 return '';
738 }
739
740 public function createErrorTextExport(array $selections): string
741 {
742 if (!is_array($selections)) {
743 $selections = [];
744 }
745
746 foreach ($this->getParsedErrorText() as $paragraph) {
747 $array_reduce_function = function ($carry, $k) use ($paragraph, $selections) {
748 $text = $paragraph[$k]['text'];
749 if (in_array($k, $selections)) {
750 $text = self::ERROR_WORD_MARKER . $paragraph[$k]['text'] . self::ERROR_WORD_MARKER;
751 }
752 return $carry . ' ' . $text;
753 };
754 $output_array[] = trim(array_reduce(array_keys($paragraph), $array_reduce_function));
755 }
756 return implode("\n", $output_array);
757 }
758
759 public function getBestSelection($withPositivePointsOnly = true): array
760 {
761 $positions_array = $this->generateArrayByPositionFromErrorData();
762 $selections = [];
763 foreach ($positions_array as $position => $position_data) {
764 if ($position === ''
765 || $withPositivePointsOnly && $position_data['points'] < 1) {
766 continue;
767 }
768
769 $selections[] = $position;
770 if ($position_data['length'] > 1) {
771 for ($i = 1;$i < $position_data['length'];$i++) {
772 $selections[] = $position + $i;
773 }
774 }
775 }
776
777 return $selections;
778 }
779
784 protected function getPointsForSelectedPositions(array $selected_word_positions): float
785 {
786 $points = 0;
787 $correct_positions = $this->generateArrayByPositionFromErrorData();
788
789 foreach ($correct_positions as $correct_position => $correct_position_data) {
790 $selected_word_key = array_search($correct_position, $selected_word_positions);
791 if ($selected_word_key === false) {
792 continue;
793 }
794
795 if ($correct_position_data['length'] === 1) {
796 $points += $correct_position_data['points'];
797 unset($selected_word_positions[$selected_word_key]);
798 continue;
799 }
800
801 $passage_complete = true;
802 for ($i = 1;$i < $correct_position_data['length'];$i++) {
803 $selected_passage_element_key = array_search($correct_position + $i, $selected_word_positions);
804 if ($selected_passage_element_key === false) {
805 $passage_complete = false;
806 continue;
807 }
808 unset($selected_word_positions[$selected_passage_element_key]);
809 }
810
811 if ($passage_complete) {
812 $points += $correct_position_data['points'];
813 unset($selected_word_positions[$selected_word_key]);
814 }
815 }
816
817 foreach ($selected_word_positions as $word_position) {
818 if (!array_key_exists($word_position, $correct_positions)) {
819 $points += $this->getPointsWrong();
820 continue;
821 }
822 }
823
824 return $points;
825 }
826
827 public function flushErrorData(): void
828 {
829 $this->errordata = [];
830 }
831
836 public function getErrorData(): array
837 {
838 return $this->errordata;
839 }
840
845 private function getErrorDataAsArrayForJS(): array
846 {
847 $correct_answers = [];
848 foreach ($this->getErrorData() as $index => $answer_obj) {
849 $correct_answers[] = [
850 'answertext_wrong' => $answer_obj->getTextWrong(),
851 'answertext_correct' => $answer_obj->getTextCorrect(),
852 'points' => $answer_obj->getPoints(),
853 'length' => $answer_obj->getLength(),
854 'pos' => $this->getId() . '_' . $answer_obj->getPosition()
855 ];
856 }
857 return $correct_answers;
858 }
859
860 public function getErrorText(): string
861 {
862 return $this->errortext ?? '';
863 }
864
865 public function setErrorText(?string $text): void
866 {
867 $this->errortext = $text ?? '';
868 }
869
870 public function getParsedErrorText(): array
871 {
872 return $this->parsed_errortext;
873 }
874
875 private function getParsedErrorTextForJS(): array
876 {
877 $answers = [];
878 foreach ($this->parsed_errortext as $paragraph) {
879 foreach ($paragraph as $position => $word) {
880 $answers[] = [
881 'answertext' => $word['text'],
882 'order' => $this->getId() . '_' . $position
883 ];
884 }
885 $answers[] = [
886 'answertext' => '###'
887 ];
888 }
889 array_pop($answers);
890
891 return $answers;
892 }
893
894 public function setParsedErrorText(array $parsed_errortext): void
895 {
896 $this->parsed_errortext = $parsed_errortext;
897 }
898
899 public function getTextSize(): float
900 {
901 return $this->textsize;
902 }
903
904 public function setTextSize($a_value): void
905 {
906 // in self-assesment-mode value should always be set (and must not be null)
907 if ($a_value === null) {
908 $a_value = 100;
909 }
910 $this->textsize = $a_value;
911 }
912
913 public function getPointsWrong(): ?float
914 {
915 return $this->points_wrong;
916 }
917
918 public function setPointsWrong($a_value): void
919 {
920 $this->points_wrong = $a_value;
921 }
922
923 public function toJSON(): string
924 {
925 $result = [];
926 $result['id'] = $this->getId();
927 $result['type'] = (string) $this->getQuestionType();
928 $result['title'] = $this->getTitleForHTMLOutput();
929 $result['question'] = $this->formatSAQuestion($this->getQuestion());
930 $result['text'] = ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
931 $result['nr_of_tries'] = $this->getNrOfTries();
932 $result['shuffle'] = $this->getShuffle();
933 $result['feedback'] = [
934 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
935 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
936 ];
937
938 $result['correct_answers'] = $this->getErrorDataAsArrayForJS();
939 $result['answers'] = $this->getParsedErrorTextForJS();
940
941 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
942 $result['mobs'] = $mobs;
943
944 return json_encode($result);
945 }
946
955 public function getOperators($expression): array
956 {
958 }
959
964 public function getExpressionTypes(): array
965 {
966 return [
971 ];
972 }
973
980 public function getUserQuestionResult($active_id, $pass): ilUserQuestionResult
981 {
982 $result = new ilUserQuestionResult($this, $active_id, $pass);
983
984 $data = $this->db->queryF(
985 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
986 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
987 )",
988 ["integer", "integer", "integer","integer", "integer", "integer"],
989 [$active_id, $pass, $this->getId(), $active_id, $pass, $this->getId()]
990 );
991
992 while ($row = $this->db->fetchAssoc($data)) {
993 $result->addKeyValue($row["value1"], $row["value1"]);
994 }
995
996 $points = $this->calculateReachedPoints($active_id, $pass);
997 $max_points = $this->getMaximumPoints();
998
999 $result->setReachedPercentage(($points / $max_points) * 100);
1000
1001 return $result;
1002 }
1003
1004 public function parseErrorText(): void
1005 {
1006 $text_by_paragraphs = preg_split(self::PARAGRAPH_SPLIT_REGEXP, $this->getErrorText());
1007 $text_array = [];
1008 $offset = 0;
1009 foreach ($text_by_paragraphs as $paragraph) {
1010 $text_array[] = $this->addErrorInformationToTextParagraphArray(
1011 preg_split(self::WORD_SPLIT_REGEXP, trim($paragraph)),
1012 $offset
1013 );
1014 $offset += count(end($text_array));
1015 }
1016 $this->setParsedErrorText($text_array);
1017 }
1018
1024 private function addErrorInformationToTextParagraphArray(array $paragraph, int $offset): array
1025 {
1026 $paragraph_with_error_info = [];
1027 $passage_start = null;
1028 foreach ($paragraph as $position => $word) {
1029 $actual_position = $position + $offset;
1030 if ($passage_start !== null
1031 && (mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS['end']) === mb_strlen($word) - 2
1032 || mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS['end']) === mb_strlen($word) - 3
1033 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word, -1)) === 1)) {
1034
1035 $actual_word = $this->parsePassageEndWord($word);
1036
1037 $paragraph_with_error_info[$passage_start]['text_wrong'] .=
1038 ' ' . $actual_word;
1039 $paragraph_with_error_info[$actual_position] = [
1040 'text' => $actual_word,
1041 'error_type' => 'passage_end'
1042 ];
1043 $passage_start = null;
1044 continue;
1045 }
1046 if ($passage_start !== null) {
1047 $paragraph_with_error_info[$passage_start]['text_wrong'] .= ' ' . $word;
1048 $paragraph_with_error_info[$actual_position] = [
1049 'text' => $word,
1050 'error_type' => 'in_passage'
1051 ];
1052 continue;
1053 }
1054 if (mb_strpos($word, self::ERROR_PARAGRAPH_DELIMITERS['start']) === 0) {
1055 $paragraph_with_error_info[$actual_position] = [
1056 'text' => substr($word, 2),
1057 'text_wrong' => substr($word, 2),
1058 'error_type' => 'passage_start',
1059 'error_position' => $actual_position,
1060 ];
1061 $passage_start = $actual_position;
1062 continue;
1063 }
1064 if (mb_strpos($word, self::ERROR_WORD_MARKER) === 0) {
1065 $paragraph_with_error_info[$actual_position] = [
1066 'text' => substr($word, 1),
1067 'text_wrong' => substr($word, 1),
1068 'error_type' => 'word',
1069 'error_position' => $actual_position,
1070 ];
1071 continue;
1072 }
1073
1074 $paragraph_with_error_info[$actual_position] = [
1075 'text' => $word,
1076 'error_type' => 'none',
1077 'points' => $this->getPointsWrong()
1078 ];
1079 }
1080
1081 return $paragraph_with_error_info;
1082 }
1083
1084 private function parsePassageEndWord(string $word): string
1085 {
1086 if (mb_substr($word, -2) === self::ERROR_PARAGRAPH_DELIMITERS['end']) {
1087 return mb_substr($word, 0, -2);
1088 }
1089 return mb_substr($word, 0, -3) . mb_substr($word, -1);
1090 }
1091
1099 public function getAvailableAnswerOptions($index = null): ?int
1100 {
1101 $error_text_array = array_reduce(
1102 $this->parsed_errortext,
1103 fn ($c, $v) => $c + $v
1104 );
1105
1106 if ($index === null) {
1107 return $error_text_array;
1108 }
1109
1110 if (array_key_exists($index, $error_text_array)) {
1111 return $error_text_array[$index];
1112 }
1113
1114 return null;
1115 }
1116
1118 {
1119 $array_by_position = [];
1120 foreach ($this->errordata as $error) {
1121 $array_by_position[$error->getPosition()] = [
1122 'length' => $error->getLength(),
1123 'points' => $error->getPoints(),
1124 'text' => $error->getTextWrong(),
1125 'text_correct' => $error->getTextCorrect()
1126 ];
1127 }
1128 ksort($array_by_position);
1129 return $array_by_position;
1130 }
1131
1137 private function getErrorTokenHtml($item, $class, $useLinkTags): string
1138 {
1139 if ($useLinkTags) {
1140 return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilLegacyFormElementsUtil::prepareFormOutput(
1141 $item
1142 )) . '</a>';
1143 }
1144
1145 return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilLegacyFormElementsUtil::prepareFormOutput(
1146 $item
1147 )) . '</span>';
1148 }
1149}
return true
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
setParsedErrorText(array $parsed_errortext)
addErrorInformationToTextParagraphArray(array $paragraph, int $offset)
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
getClassForPosition(int $position, bool $show_correct_solution, array $selections)
duplicate(bool $for_test=true, string $title="", string $author="", string $owner="", $testObjId=null)
Duplicates the object.
loadFromDb($question_id)
Loads the object from the database.
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 ...
setErrorData(array $errors)
logUserAction(bool $user_entered_values, int $active_id)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
completeParsedErrorTextFromErrorData()
getAdditionalInformationFromExistingErrorDataByErrorText(array $current_error_data, string $text_wrong)
const PARAGRAPH_SPLIT_REGEXP
const ERROR_PARAGRAPH_DELIMITERS
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
assembleErrorTextOutput(array $selections, bool $graphical_output=false, bool $show_correct_solution=false, bool $use_link_tags=true, array $correctness_icons=[])
createErrorTextExport(array $selections)
addPositionToErrorAnswer(assAnswerErrorText $error)
parsePassageEndWord(string $word)
saveToDb($original_id="")
Saves a the object to the database.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
copyObject($target_questionpool_id, $title="")
Copies an object.
setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass)
{}
setPointsWrong($a_value)
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
getTextForPosition(int $position, array $paragraph, bool $show_correct_solution)
getErrorTokenHtml($item, $class, $useLinkTags)
getBestSelection($withPositivePointsOnly=true)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
generateOutputStringFromPosition(int $position, array $selections, array $paragraph, bool $graphical_output, bool $show_correct_solution, bool $use_link_tags, array $correctness_icons)
setErrorText(?string $text)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
const FIND_PUNCTUATION_REGEXP
getCorrectnessIconForPosition(int $position, bool $graphical_output, array $selections, array $correctness_icons)
fromXML($item, int $questionpool_id, ?int $tst_id, &$tst_object, int &$question_counter, array $import_mapping, array &$solutionhints=[])
Receives parameters from a QTI parser and creates a valid ILIAS question object.
isComplete()
Returns true, if a single choice question is complete for use.
getPointsForSelectedPositions(array $selected_word_positions)
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
getExpressionTypes()
Get all available expression types for a specific question.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
getQuestionType()
Returns the question type of the question.
Abstract basic class which is to be extended by the concrete assessment question type classes.
float $points
The maximum available points for the question.
setOriginalId(?int $original_id)
string $question
The question text.
static logAction(string $logtext, int $active_id, int $question_id)
setId(int $id=-1)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
setQuestion(string $question="")
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
static _getOriginalId(int $question_id)
saveQuestionDataToDb(int $original_id=-1)
setAuthor(string $author="")
setComment(string $comment="")
setObjId(int $obj_id=0)
getSolutionMaxPass(int $active_id)
setOwner(int $owner=-1)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
setTitle(string $title="")
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
setPoints(float $points)
ensureNonNegativePoints($points)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setCell($a_row, $a_col, $a_value, $datatype=null)
static prepareFormOutput($a_str, bool $a_strip=false)
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$c
Definition: cli.php:38
if(!file_exists(getcwd() . '/ilias.ini.php'))
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: confirmReg.php:20
$img
Definition: imgupload.php:83
$mobs
Definition: imgupload.php:70
$errors
Definition: imgupload.php:65
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$index
Definition: metadata.php:145
$i
Definition: metadata.php:41
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193