ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.assErrorText.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
24
38{
39 protected const ERROR_TYPE_WORD = 1;
40 protected const ERROR_TYPE_PASSAGE = 2;
41 protected const DEFAULT_TEXT_SIZE = 100.0;
42 protected const ERROR_MAX_LENGTH = 150;
43
44 protected const PARAGRAPH_SPLIT_REGEXP = '/[\n\r]+/';
45 protected const WORD_SPLIT_REGEXP = '/\s+/';
46 protected const FIND_PUNCTUATION_REGEXP = '/\p{P}/';
47 protected const ERROR_WORD_MARKER = '#';
48 protected const ERROR_PARAGRAPH_DELIMITERS = [
49 'start' => '((',
50 'end' => '))'
51 ];
52
53 protected string $errortext = '';
54 protected array $parsed_errortext = [];
56 protected array $errordata = [];
57 protected float $textsize;
58 protected ?float $points_wrong = null;
59
60 public function __construct(
61 string $title = '',
62 string $comment = '',
63 string $author = '',
64 int $owner = -1,
65 string $question = ''
66 ) {
68 $this->textsize = self::DEFAULT_TEXT_SIZE;
69 }
70
71 public function isComplete(): bool
72 {
73 if (mb_strlen($this->title)
74 && ($this->author)
75 && ($this->question)
76 && ($this->getMaximumPoints() > 0)) {
77 return true;
78 } else {
79 return false;
80 }
81 }
82
83 public function saveToDb(?int $original_id = null): void
84 {
88 parent::saveToDb();
89 }
90
92 {
93 $this->db->manipulateF(
94 "DELETE FROM qpl_a_errortext WHERE question_fi = %s",
95 ['integer'],
96 [$this->getId()]
97 );
98
99 $sequence = 0;
100 foreach ($this->errordata as $error) {
101 $next_id = $this->db->nextId('qpl_a_errortext');
102 $this->db->manipulateF(
103 "INSERT INTO qpl_a_errortext (answer_id, question_fi, text_wrong, text_correct, points, sequence, position) VALUES (%s, %s, %s, %s, %s, %s, %s)",
104 ['integer', 'integer', 'text', 'text', 'float', 'integer', 'integer'],
105 [
106 $next_id,
107 $this->getId(),
108 $error->getTextWrong(),
109 $error->getTextCorrect(),
110 $error->getPoints(),
111 $sequence++,
112 $error->getPosition()
113 ]
114 );
115 }
116 }
117
124 {
125 $this->db->manipulateF(
126 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
127 ["integer"],
128 [$this->getId()]
129 );
130
131 $this->db->manipulateF(
132 "INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, errortext, parsed_errortext, textsize, points_wrong) VALUES (%s, %s, %s, %s, %s)",
133 ["integer", "text", "text", "float", "float"],
134 [
135 $this->getId(),
136 $this->getErrorText(),
137 json_encode($this->getParsedErrorText()),
138 $this->getTextSize(),
139 $this->getPointsWrong()
140 ]
141 );
142 }
143
150 public function loadFromDb($question_id): void
151 {
152 $db_question = $this->db->queryF(
153 "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",
154 ["integer"],
155 [$question_id]
156 );
157 if ($db_question->numRows() === 1) {
158 $data = $this->db->fetchAssoc($db_question);
159 $this->setId($question_id);
160 $this->setObjId($data["obj_fi"]);
161 $this->setTitle((string) $data["title"]);
162 $this->setComment((string) $data["description"]);
163 $this->setOriginalId($data["original_id"]);
164 $this->setNrOfTries($data['nr_of_tries']);
165 $this->setAuthor($data["author"]);
166 $this->setPoints($data["points"]);
167 $this->setOwner($data["owner"]);
168 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data["question_text"], 1));
169 $this->setErrorText((string) $data["errortext"]);
170 $this->setParsedErrorText(json_decode($data['parsed_errortext'] ?? json_encode([]), true));
171 $this->setTextSize($data["textsize"]);
172 $this->setPointsWrong($data["points_wrong"]);
173
174 try {
175 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
178 }
179
180 try {
181 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
183 }
184 }
185
186 $db_error_text = $this->db->queryF(
187 "SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
188 ['integer'],
189 [$question_id]
190 );
191
192 if ($db_error_text->numRows() > 0) {
193 while ($data = $this->db->fetchAssoc($db_error_text)) {
194 $this->errordata[] = new assAnswerErrorText(
195 (string) $data['text_wrong'],
196 (string) $data['text_correct'],
197 (float) $data['points'],
198 $data['position']
199 );
200 }
201 }
202
204
205 parent::loadFromDb($question_id);
206 }
207
208 private function correctDataAfterParserUpdate(): void
209 {
210 if ($this->getErrorText() === '') {
211 return;
212 }
213 $needs_finalizing = false;
214 if ($this->getParsedErrorText() === []) {
215 $needs_finalizing = true;
216 $this->parseErrorText();
217 }
218
219 if (isset($this->errordata[0])
220 && $this->errordata[0]->getPosition() === null) {
221 foreach ($this->errordata as $key => $error) {
222 $this->errordata[$key] = $this->addPositionToErrorAnswer($error);
223 }
225 }
226
227 if ($needs_finalizing) {
230 }
231 }
232
238 public function getMaximumPoints(): float
239 {
240 $maxpoints = 0.0;
241 foreach ($this->errordata as $error) {
242 if ($error->getPoints() > 0) {
243 $maxpoints += $error->getPoints();
244 }
245 }
246 return $maxpoints;
247 }
248
249 public function calculateReachedPoints(
250 int $active_id,
251 ?int $pass = null,
252 bool $authorized_solution = true
253 ): float {
254 if ($pass === null) {
255 $pass = $this->getSolutionMaxPass($active_id);
256 }
257 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
258
259 $positions = [];
260 while ($row = $this->db->fetchAssoc($result)) {
261 $positions[] = $row['value1'];
262 }
263 $points = $this->getPointsForSelectedPositions($positions);
264 return $points;
265 }
266
268 {
269 $reached_points = $this->getPointsForSelectedPositions($preview_session->getParticipantsSolution() ?? []);
270 return $this->ensureNonNegativePoints($reached_points);
271 }
272
273 public function saveWorkingData(
274 int $active_id,
275 ?int $pass = null,
276 bool $authorized = true
277 ): bool {
278 if (is_null($pass)) {
279 $pass = ilObjTest::_getPass($active_id);
280 }
281
282 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
283 function () use ($active_id, $pass, $authorized) {
284 $selected = $this->getAnswersFromRequest();
285 $this->removeCurrentSolution($active_id, $pass, $authorized);
286 foreach ($selected as $position) {
287 $this->saveCurrentSolution($active_id, $pass, $position, null, $authorized);
288 }
289 }
290 );
291
292 return true;
293 }
294
295 public function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
296 {
297 $selection = $this->getAnswersFromRequest();
298 $previewSession->setParticipantsSolution($selection);
299 }
300
301 private function getAnswersFromRequest(): array
302 {
303 return explode(
304 ',',
305 $this->questionpool_request->string('qst_' . $this->getId())
306 );
307 }
308
309 public function getQuestionType(): string
310 {
311 return 'assErrorText';
312 }
313
314 public function getAdditionalTableName(): string
315 {
316 return 'qpl_qst_errortext';
317 }
318
319 public function getAnswerTableName(): string
320 {
321 return 'qpl_a_errortext';
322 }
323
324 public function setErrorsFromParsedErrorText(): void
325 {
326 $current_error_data = $this->getErrorData();
327 $this->errordata = [];
328
329 $has_too_long_errors = false;
330 foreach ($this->getParsedErrorText() as $paragraph) {
331 foreach ($paragraph as $position => $word) {
332 if ($word['error_type'] === 'in_passage'
333 || $word['error_type'] === 'passage_end'
334 || $word['error_type'] === 'none') {
335 continue;
336 }
337
338 $text_wrong = $word['text_wrong'];
339 if (mb_strlen($text_wrong) > self::ERROR_MAX_LENGTH) {
340 $has_too_long_errors = true;
341 continue;
342 }
343
344 list($text_correct, $points) =
345 $this->getAdditionalInformationFromExistingErrorDataByErrorText($current_error_data, $text_wrong);
346 $this->errordata[] = new assAnswerErrorText($text_wrong, $text_correct, $points, $position);
347 }
348 }
349
350 if ($has_too_long_errors) {
351 $this->tpl->setOnScreenMessage(
352 'failure',
353 $this->lng->txt('qst_error_text_too_long')
354 );
355 }
356 }
357
359 {
360 foreach ($this->getParsedErrorText() as $paragraph) {
361 foreach ($paragraph as $position => $word) {
362 if (isset($word['text_wrong'])
363 && ($word['text_wrong'] === $error->getTextWrong()
364 || mb_substr($word['text_wrong'], 0, -1) === $error->getTextWrong()
365 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word['text_wrong'], -1)) === 1)
366 && !array_key_exists($position, $this->generateArrayByPositionFromErrorData())
367 ) {
368 return $error->withPosition($position);
369 }
370 }
371 }
372
373 return $error;
374 }
375
377 {
378 foreach ($this->errordata as $error) {
379 $position = $error->getPosition();
380 foreach ($this->getParsedErrorText() as $key => $paragraph) {
381 if (array_key_exists($position, $paragraph)) {
382 $this->parsed_errortext[$key][$position]['text_correct'] =
383 $error->getTextCorrect();
384 $this->parsed_errortext[$key][$position]['points'] =
385 $error->getPoints();
386 break;
387 }
388 }
389 }
390 }
391
396 public function setErrorData(array $errors): void
397 {
398 $this->errordata = [];
399
400 foreach ($errors as $error) {
401 $answer = $this->addPositionToErrorAnswer($error);
402 $this->errordata[] = $answer;
403 }
404 $this->completeParsedErrorTextFromErrorData();
405 }
406
407 public function removeErrorDataWithoutPosition(): void
408 {
409 foreach ($this->getErrorData() as $index => $error) {
410 if ($error->getPosition() === null) {
411 unset($this->errordata[$index]);
412 }
413 }
414 $this->errordata = array_values($this->errordata);
415 }
416
423 array $current_error_data,
424 string $text_wrong
425 ): array {
426 foreach ($current_error_data as $answer_object) {
427 if (strcmp($answer_object->getTextWrong(), $text_wrong) === 0) {
428 return[
429 $answer_object->getTextCorrect(),
430 $answer_object->getPoints()
431 ];
432 }
433 }
434 return ['', 0.0];
435 }
436
437 public function assembleErrorTextOutput(
438 array $selections,
439 bool $graphical_output = false,
440 bool $show_correct_solution = false,
441 bool $use_link_tags = true,
442 array $correctness_icons = []
443 ): string {
444 $output_array = [];
445 foreach ($this->getParsedErrorText() as $paragraph) {
446 $array_reduce_function = fn(?string $carry, int $position)
447 => $carry . $this->generateOutputStringFromPosition(
448 $position,
449 $selections,
450 $paragraph,
451 $graphical_output,
452 $show_correct_solution,
453 $use_link_tags,
454 $correctness_icons
455 );
456 $output_array[] = '<p>' . trim(array_reduce(array_keys($paragraph), $array_reduce_function)) . '</p>';
457 }
458
459 return implode("\n", $output_array);
460 }
461
463 int $position,
464 array $selections,
465 array $paragraph,
466 bool $graphical_output,
467 bool $show_correct_solution,
468 bool $use_link_tags,
469 array $correctness_icons
470 ): string {
471 $text = $this->getTextForPosition($position, $paragraph, $show_correct_solution);
472 if ($text === '') {
473 return '';
474 }
475 $class = $this->getClassForPosition($position, $show_correct_solution, $selections);
476 $img = $this->getCorrectnessIconForPosition(
477 $position,
478 $graphical_output,
479 $selections,
480 $correctness_icons
481 );
482
483 return ' ' . $this->getErrorTokenHtml($text, $class, $use_link_tags) . $img;
484 }
485
486 private function getTextForPosition(
487 int $position,
488 array $paragraph,
489 bool $show_correct_solution
490 ): string {
491 $v = $paragraph[$position];
492 if ($show_correct_solution === true
493 && ($v['error_type'] === 'in_passage'
494 || $v['error_type'] === 'passage_end')) {
495 return '';
496 }
497 if ($show_correct_solution
498 && ($v['error_type'] === 'passage_start'
499 || $v['error_type'] === 'word')) {
500 return $v['text_correct'] ?? '';
501 }
502
503 return $v['text'];
504 }
505
506 private function getClassForPosition(
507 int $position,
508 bool $show_correct_solution,
509 array $selections
510 ): string {
511 if ($show_correct_solution !== true
512 && in_array($position, $selections['user'])) {
513 return 'ilc_qetitem_ErrorTextSelected';
514 }
515
516 if ($show_correct_solution === true
517 && in_array($position, $selections['best'])) {
518 return 'ilc_qetitem_ErrorTextSelected';
519 }
520
521 return 'ilc_qetitem_ErrorTextItem';
522 }
523
525 int $position,
526 bool $graphical_output,
527 array $selections,
528 array $correctness_icons
529 ): string {
530 if ($graphical_output === true
531 && (in_array($position, $selections['user']) && !in_array($position, $selections['best'])
532 || !in_array($position, $selections['user']) && in_array($position, $selections['best']))) {
533 return $correctness_icons['not_correct'];
534 }
535
536 if ($graphical_output === true
537 && in_array($position, $selections['user']) && in_array($position, $selections['best'])) {
538 return $correctness_icons['correct'];
539 }
540
541 return '';
542 }
543
544 public function createErrorTextExport(array $selections): string
545 {
546 if (!is_array($selections)) {
547 $selections = [];
548 }
549
550 foreach ($this->getParsedErrorText() as $paragraph) {
551 $array_reduce_function = function ($carry, $k) use ($paragraph, $selections) {
552 $text = $paragraph[$k]['text'];
553 if (in_array($k, $selections)) {
554 $text = self::ERROR_WORD_MARKER . $paragraph[$k]['text'] . self::ERROR_WORD_MARKER;
555 }
556 return $carry . ' ' . $text;
557 };
558 $output_array[] = trim(array_reduce(array_keys($paragraph), $array_reduce_function));
559 }
560 return implode("\n", $output_array);
561 }
562
563 public function getBestSelection(bool $with_positive_points_only = true): array
564 {
565 $positions_array = $this->generateArrayByPositionFromErrorData();
566 $selections = [];
567 foreach ($positions_array as $position => $position_data) {
568 if ($position === ''
569 || $with_positive_points_only && $position_data['points'] <= 0) {
570 continue;
571 }
572
573 $selections[] = $position;
574 if ($position_data['length'] > 1) {
575 for ($i = 1;$i < $position_data['length'];$i++) {
576 $selections[] = $position + $i;
577 }
578 }
579 }
580
581 return $selections;
582 }
583
588 protected function getPointsForSelectedPositions(array $selected_word_positions): float
589 {
590 $points = 0;
591 $correct_positions = $this->generateArrayByPositionFromErrorData();
592
593 foreach ($correct_positions as $correct_position => $correct_position_data) {
594 $selected_word_key = array_search($correct_position, $selected_word_positions);
595 if ($selected_word_key === false) {
596 continue;
597 }
598
599 if ($correct_position_data['length'] === 1) {
600 $points += $correct_position_data['points'];
601 unset($selected_word_positions[$selected_word_key]);
602 continue;
603 }
604
605 $passage_complete = true;
606 for ($i = 1;$i < $correct_position_data['length'];$i++) {
607 $selected_passage_element_key = array_search($correct_position + $i, $selected_word_positions);
608 if ($selected_passage_element_key === false) {
609 $passage_complete = false;
610 continue;
611 }
612 unset($selected_word_positions[$selected_passage_element_key]);
613 }
614
615 if ($passage_complete) {
616 $points += $correct_position_data['points'];
617 unset($selected_word_positions[$selected_word_key]);
618 }
619 }
620
621 foreach ($selected_word_positions as $word_position) {
622 if (!array_key_exists($word_position, $correct_positions)) {
623 $points += $this->getPointsWrong();
624 continue;
625 }
626 }
627
628 return $points;
629 }
630
631 public function flushErrorData(): void
632 {
633 $this->errordata = [];
634 }
635
640 public function getErrorData(): array
641 {
642 return $this->errordata;
643 }
644
649 private function getErrorDataAsArrayForJS(): array
650 {
651 $correct_answers = [];
652 foreach ($this->getErrorData() as $index => $answer_obj) {
653 $correct_answers[] = [
654 'answertext_wrong' => $answer_obj->getTextWrong(),
655 'answertext_correct' => $answer_obj->getTextCorrect(),
656 'points' => $answer_obj->getPoints(),
657 'length' => $answer_obj->getLength(),
658 'pos' => $this->getId() . '_' . $answer_obj->getPosition()
659 ];
660 }
661 return $correct_answers;
662 }
663
664 public function getErrorText(): string
665 {
666 return $this->errortext ?? '';
667 }
668
669 public function setErrorText(?string $text): void
670 {
671 $this->errortext = $text ?? '';
672 }
673
674 public function getParsedErrorText(): array
675 {
676 return $this->parsed_errortext;
677 }
678
679 private function getParsedErrorTextForJS(): array
680 {
681 $answers = [];
682 foreach ($this->parsed_errortext as $paragraph) {
683 foreach ($paragraph as $position => $word) {
684 $answers[] = [
685 'answertext' => $word['text'],
686 'order' => $this->getId() . '_' . $position
687 ];
688 }
689 $answers[] = [
690 'answertext' => '###'
691 ];
692 }
693 array_pop($answers);
694
695 return $answers;
696 }
697
698 public function setParsedErrorText(array $parsed_errortext): void
699 {
700 $this->parsed_errortext = $parsed_errortext;
701 }
702
703 public function getTextSize(): float
704 {
705 return $this->textsize;
706 }
707
708 public function setTextSize($a_value): void
709 {
710 // in self-assesment-mode value should always be set (and must not be null)
711 if ($a_value === null) {
712 $a_value = 100;
713 }
714 $this->textsize = $a_value;
715 }
716
717 public function getPointsWrong(): ?float
718 {
719 return $this->points_wrong;
720 }
721
722 public function setPointsWrong($a_value): void
723 {
724 $this->points_wrong = $a_value;
725 }
726
727 public function toJSON(): string
728 {
729 $result = [];
730 $result['id'] = $this->getId();
731 $result['type'] = (string) $this->getQuestionType();
732 $result['title'] = $this->getTitleForHTMLOutput();
733 $result['question'] = $this->formatSAQuestion($this->getQuestion());
734 $result['text'] = ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
735 $result['nr_of_tries'] = $this->getNrOfTries();
736 $result['shuffle'] = $this->getShuffle();
737 $result['feedback'] = [
738 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
739 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
740 ];
741
742 $result['correct_answers'] = $this->getErrorDataAsArrayForJS();
743 $result['answers'] = $this->getParsedErrorTextForJS();
744
745 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
746 $result['mobs'] = $mobs;
747
748 return json_encode($result);
749 }
750
751 public function getOperators(string $expression): array
752 {
754 }
755
756 public function getExpressionTypes(): array
757 {
758 return [
763 ];
764 }
765
766 public function getUserQuestionResult(
767 int $active_id,
768 int $pass
770 $result = new ilUserQuestionResult($this, $active_id, $pass);
771
772 $data = $this->db->queryF(
773 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
774 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
775 )",
776 ["integer", "integer", "integer","integer", "integer", "integer"],
777 [$active_id, $pass, $this->getId(), $active_id, $pass, $this->getId()]
778 );
779
780 while ($row = $this->db->fetchAssoc($data)) {
781 $result->addKeyValue($row["value1"], $row["value1"]);
782 }
783
784 $points = $this->calculateReachedPoints($active_id, $pass);
785 $max_points = $this->getMaximumPoints();
786
787 $result->setReachedPercentage(($points / $max_points) * 100);
788
789 return $result;
790 }
791
792 public function parseErrorText(): void
793 {
794 $text_by_paragraphs = preg_split(self::PARAGRAPH_SPLIT_REGEXP, $this->getErrorText());
795 $text_array = [];
796 $offset = 0;
797 foreach ($text_by_paragraphs as $paragraph) {
798 $text_array[] = $this->addErrorInformationToTextParagraphArray(
799 preg_split(self::WORD_SPLIT_REGEXP, trim($paragraph)),
800 $offset
801 );
802 $offset += count(end($text_array));
803 }
804 $this->setParsedErrorText($text_array);
805 }
806
812 private function addErrorInformationToTextParagraphArray(array $paragraph, int $offset): array
813 {
814 $paragraph_with_error_info = [];
815 $passage_start = null;
816 foreach ($paragraph as $position => $word) {
817 $actual_position = $position + $offset;
818 if ($passage_start !== null
819 && (mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS['end']) === mb_strlen($word) - 2
820 || mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS['end']) === mb_strlen($word) - 3
821 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word, -1)) === 1)) {
822 $actual_word = $this->parsePassageEndWord($word);
823
824 $paragraph_with_error_info[$passage_start]['text_wrong'] .=
825 ' ' . $actual_word;
826 $paragraph_with_error_info[$actual_position] = [
827 'text' => $actual_word,
828 'error_type' => 'passage_end'
829 ];
830 $passage_start = null;
831 continue;
832 }
833 if ($passage_start !== null) {
834 $paragraph_with_error_info[$passage_start]['text_wrong'] .= ' ' . $word;
835 $paragraph_with_error_info[$actual_position] = [
836 'text' => $word,
837 'error_type' => 'in_passage'
838 ];
839 continue;
840 }
841 if (mb_strpos($word, self::ERROR_PARAGRAPH_DELIMITERS['start']) === 0) {
842 $paragraph_with_error_info[$actual_position] = [
843 'text' => substr($word, 2),
844 'text_wrong' => substr($word, 2),
845 'error_type' => 'passage_start',
846 'error_position' => $actual_position,
847 ];
848 $passage_start = $actual_position;
849 continue;
850 }
851 if (mb_strpos($word, self::ERROR_WORD_MARKER) === 0) {
852 $paragraph_with_error_info[$actual_position] = [
853 'text' => substr($word, 1),
854 'text_wrong' => substr($word, 1),
855 'error_type' => 'word',
856 'error_position' => $actual_position,
857 ];
858 continue;
859 }
860
861 $paragraph_with_error_info[$actual_position] = [
862 'text' => $word,
863 'error_type' => 'none',
864 'points' => $this->getPointsWrong()
865 ];
866 }
867
868 return $paragraph_with_error_info;
869 }
870
871 private function parsePassageEndWord(string $word): string
872 {
873 if (mb_substr($word, -2) === self::ERROR_PARAGRAPH_DELIMITERS['end']) {
874 return mb_substr($word, 0, -2);
875 }
876 return mb_substr($word, 0, -3) . mb_substr($word, -1);
877 }
878
886 public function getAvailableAnswerOptions($index = null): ?int
887 {
888 $error_text_array = array_reduce(
889 $this->parsed_errortext,
890 fn($c, $v) => $c + $v
891 );
892
893 if ($index === null) {
894 return $error_text_array;
895 }
896
897 if (array_key_exists($index, $error_text_array)) {
898 return $error_text_array[$index];
899 }
900
901 return null;
902 }
903
904 private function generateArrayByPositionFromErrorData(): array
905 {
906 $array_by_position = [];
907 foreach ($this->errordata as $error) {
908 $array_by_position[$error->getPosition()] = [
909 'length' => $error->getLength(),
910 'points' => $error->getPoints(),
911 'text' => $error->getTextWrong(),
912 'text_correct' => $error->getTextCorrect()
913 ];
914 }
915 ksort($array_by_position);
916 return $array_by_position;
917 }
918
924 private function getErrorTokenHtml($item, $class, $useLinkTags): string
925 {
926 if ($useLinkTags) {
927 return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilLegacyFormElementsUtil::prepareFormOutput(
928 $item
929 )) . '</a>';
930 }
931
932 return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilLegacyFormElementsUtil::prepareFormOutput(
933 $item
934 )) . '</span>';
935 }
936
937 public function toLog(AdditionalInformationGenerator $additional_info): array
938 {
939 $result = [
940 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
941 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
942 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
943 AdditionalInformationGenerator::KEY_QUESTION_ERRORTEXT_ERRORTEXT => ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0),
944 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
945 ->getTrueFalseTagForBool($this->getShuffle()),
946 AdditionalInformationGenerator::KEY_FEEDBACK => [
947 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
948 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
949 ]
950 ];
951
952 $error_data = $this->getErrorData();
953 $result[AdditionalInformationGenerator::KEY_QUESTION_CORRECT_ANSWER_OPTIONS] = array_reduce(
954 array_keys($error_data),
955 static function (array $c, int $k) use ($error_data): array {
956 $c[$k + 1] = [
957 'text_wrong' => $error_data[$k]->getTextWrong(),
958 'text_correct' => $error_data[$k]->getTextCorrect(),
959 'points' => $error_data[$k]->getPoints()
960 ];
961 return $c;
962 },
963 []
964 );
965
966 return $result;
967 }
968
969 protected function solutionValuesToLog(
970 AdditionalInformationGenerator $additional_info,
971 array $solution_values
972 ): string {
973 return $this->createErrorTextExport(
974 array_map(
975 static fn(array $v): string => $v['value1'],
976 $solution_values
977 )
978 );
979 }
980
981 public function solutionValuesToText(array $solution_values): string
982 {
983 return $this->createErrorTextExport(
984 array_map(
985 static fn(array $v): string => $v['value1'],
986 $solution_values
987 )
988 );
989 }
990
991 public function getCorrectSolutionForTextOutput(int $active_id, int $pass): string
992 {
993 return $this->createErrorTextExport($this->getBestSelection());
994 }
995}
return true
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class for error text questions.
setParsedErrorText(array $parsed_errortext)
addErrorInformationToTextParagraphArray(array $paragraph, int $offset)
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
getClassForPosition(int $position, bool $show_correct_solution, array $selections)
loadFromDb($question_id)
Loads the object from the database.
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
MUST convert the given solution values into an array or a string that can be stored in the log.
getOperators(string $expression)
Get all available operations for a specific question.
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
getCorrectSolutionForTextOutput(int $active_id, int $pass)
setErrorData(array $errors)
saveToDb(?int $original_id=null)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
completeParsedErrorTextFromErrorData()
getAdditionalInformationFromExistingErrorDataByErrorText(array $current_error_data, string $text_wrong)
const PARAGRAPH_SPLIT_REGEXP
const ERROR_PARAGRAPH_DELIMITERS
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)
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
setPointsWrong($a_value)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
__construct(string $title='', string $comment='', string $author='', int $owner=-1, string $question='')
getTextForPosition(int $position, array $paragraph, bool $show_correct_solution)
getErrorTokenHtml($item, $class, $useLinkTags)
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)
const FIND_PUNCTUATION_REGEXP
getCorrectnessIconForPosition(int $position, bool $graphical_output, array $selections, array $correctness_icons)
getPointsForSelectedPositions(array $selected_word_positions)
getBestSelection(bool $with_positive_points_only=true)
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
generateArrayByPositionFromErrorData()
getExpressionTypes()
Get all available expression types for a specific question.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
setOriginalId(?int $original_id)
setId(int $id=-1)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
setQuestion(string $question="")
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
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="")
saveQuestionDataToDb(?int $original_id=null)
setPoints(float $points)
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 getOperatorsByExpression(string $expression)
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...
$c
Definition: deliver.php:25
return['delivery_method'=> 'php',]
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...
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
if(!file_exists('../ilias.ini.php'))