ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
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 if ($position === null) {
381 continue;
382 }
383
384 foreach ($this->getParsedErrorText() as $key => $paragraph) {
385 if (array_key_exists($position, $paragraph)) {
386 $this->parsed_errortext[$key][$position]['text_correct'] =
387 $error->getTextCorrect();
388 $this->parsed_errortext[$key][$position]['points'] =
389 $error->getPoints();
390 break;
391 }
392 }
393 }
394 }
395
400 public function setErrorData(array $errors): void
401 {
402 $this->errordata = [];
403
404 foreach ($errors as $error) {
405 $answer = $this->addPositionToErrorAnswer($error);
406 $this->errordata[] = $answer;
407 }
408 $this->completeParsedErrorTextFromErrorData();
409 }
410
411 public function removeErrorDataWithoutPosition(): void
412 {
413 foreach ($this->getErrorData() as $index => $error) {
414 if ($error->getPosition() === null) {
415 unset($this->errordata[$index]);
416 }
417 }
418 $this->errordata = array_values($this->errordata);
419 }
420
427 array $current_error_data,
428 string $text_wrong
429 ): array {
430 foreach ($current_error_data as $answer_object) {
431 if (strcmp($answer_object->getTextWrong(), $text_wrong) === 0) {
432 return[
433 $answer_object->getTextCorrect(),
434 $answer_object->getPoints()
435 ];
436 }
437 }
438 return ['', 0.0];
439 }
440
441 public function assembleErrorTextOutput(
442 array $selections,
443 bool $graphical_output = false,
444 bool $show_correct_solution = false,
445 bool $use_link_tags = true,
446 array $correctness_icons = []
447 ): string {
448 $output_array = [];
449 foreach ($this->getParsedErrorText() as $paragraph) {
450 $array_reduce_function = fn(?string $carry, int $position)
451 => $carry . $this->generateOutputStringFromPosition(
452 $position,
453 $selections,
454 $paragraph,
455 $graphical_output,
456 $show_correct_solution,
457 $use_link_tags,
458 $correctness_icons
459 );
460 $output_array[] = '<p>' . trim(array_reduce(array_keys($paragraph), $array_reduce_function)) . '</p>';
461 }
462
463 return implode("\n", $output_array);
464 }
465
467 int $position,
468 array $selections,
469 array $paragraph,
470 bool $graphical_output,
471 bool $show_correct_solution,
472 bool $use_link_tags,
473 array $correctness_icons
474 ): string {
475 $text = $this->getTextForPosition($position, $paragraph, $show_correct_solution);
476 if ($text === '') {
477 return '';
478 }
479 $class = $this->getClassForPosition($position, $show_correct_solution, $selections);
480 $img = $this->getCorrectnessIconForPosition(
481 $position,
482 $graphical_output,
483 $selections,
484 $correctness_icons
485 );
486
487 return ' ' . $this->getErrorTokenHtml($text, $class, $use_link_tags) . $img;
488 }
489
490 private function getTextForPosition(
491 int $position,
492 array $paragraph,
493 bool $show_correct_solution
494 ): string {
495 $v = $paragraph[$position];
496 if ($show_correct_solution === true
497 && ($v['error_type'] === 'in_passage'
498 || $v['error_type'] === 'passage_end')) {
499 return '';
500 }
501 if ($show_correct_solution
502 && ($v['error_type'] === 'passage_start'
503 || $v['error_type'] === 'word')) {
504 return $v['text_correct'] ?? '';
505 }
506
507 return $v['text'];
508 }
509
510 private function getClassForPosition(
511 int $position,
512 bool $show_correct_solution,
513 array $selections
514 ): string {
515 if ($show_correct_solution !== true
516 && in_array($position, $selections['user'])) {
517 return 'ilc_qetitem_ErrorTextSelected';
518 }
519
520 if ($show_correct_solution === true
521 && in_array($position, $selections['best'])) {
522 return 'ilc_qetitem_ErrorTextSelected';
523 }
524
525 return 'ilc_qetitem_ErrorTextItem';
526 }
527
529 int $position,
530 bool $graphical_output,
531 array $selections,
532 array $correctness_icons
533 ): string {
534 if ($graphical_output === true
535 && (in_array($position, $selections['user']) && !in_array($position, $selections['best'])
536 || !in_array($position, $selections['user']) && in_array($position, $selections['best']))) {
537 return $correctness_icons['not_correct'];
538 }
539
540 if ($graphical_output === true
541 && in_array($position, $selections['user']) && in_array($position, $selections['best'])) {
542 return $correctness_icons['correct'];
543 }
544
545 return '';
546 }
547
548 public function createErrorTextExport(array $selections): string
549 {
550 if (!is_array($selections)) {
551 $selections = [];
552 }
553
554 foreach ($this->getParsedErrorText() as $paragraph) {
555 $array_reduce_function = function ($carry, $k) use ($paragraph, $selections) {
556 $text = $paragraph[$k]['text'];
557 if (in_array($k, $selections)) {
558 $text = self::ERROR_WORD_MARKER . $paragraph[$k]['text'] . self::ERROR_WORD_MARKER;
559 }
560 return $carry . ' ' . $text;
561 };
562 $output_array[] = trim(array_reduce(array_keys($paragraph), $array_reduce_function));
563 }
564 return implode("\n", $output_array);
565 }
566
567 public function getBestSelection(bool $with_positive_points_only = true): array
568 {
569 $positions_array = $this->generateArrayByPositionFromErrorData();
570 $selections = [];
571 foreach ($positions_array as $position => $position_data) {
572 if ($position === ''
573 || $with_positive_points_only && $position_data['points'] <= 0) {
574 continue;
575 }
576
577 $selections[] = $position;
578 if ($position_data['length'] > 1) {
579 for ($i = 1;$i < $position_data['length'];$i++) {
580 $selections[] = $position + $i;
581 }
582 }
583 }
584
585 return $selections;
586 }
587
592 protected function getPointsForSelectedPositions(array $selected_word_positions): float
593 {
594 $points = 0;
595 $correct_positions = $this->generateArrayByPositionFromErrorData();
596
597 foreach ($correct_positions as $correct_position => $correct_position_data) {
598 $selected_word_key = array_search($correct_position, $selected_word_positions);
599 if ($selected_word_key === false) {
600 continue;
601 }
602
603 if ($correct_position_data['length'] === 1) {
604 $points += $correct_position_data['points'];
605 unset($selected_word_positions[$selected_word_key]);
606 continue;
607 }
608
609 $passage_complete = true;
610 for ($i = 1;$i < $correct_position_data['length'];$i++) {
611 $selected_passage_element_key = array_search($correct_position + $i, $selected_word_positions);
612 if ($selected_passage_element_key === false) {
613 $passage_complete = false;
614 continue;
615 }
616 unset($selected_word_positions[$selected_passage_element_key]);
617 }
618
619 if ($passage_complete) {
620 $points += $correct_position_data['points'];
621 unset($selected_word_positions[$selected_word_key]);
622 }
623 }
624
625 foreach ($selected_word_positions as $word_position) {
626 if (!array_key_exists($word_position, $correct_positions)) {
627 $points += $this->getPointsWrong();
628 continue;
629 }
630 }
631
632 return $points;
633 }
634
635 public function flushErrorData(): void
636 {
637 $this->errordata = [];
638 }
639
644 public function getErrorData(): array
645 {
646 return $this->errordata;
647 }
648
653 private function getErrorDataAsArrayForJS(): array
654 {
655 $correct_answers = [];
656 foreach ($this->getErrorData() as $index => $answer_obj) {
657 $correct_answers[] = [
658 'answertext_wrong' => $answer_obj->getTextWrong(),
659 'answertext_correct' => $answer_obj->getTextCorrect(),
660 'points' => $answer_obj->getPoints(),
661 'length' => $answer_obj->getLength(),
662 'pos' => $this->getId() . '_' . $answer_obj->getPosition()
663 ];
664 }
665 return $correct_answers;
666 }
667
668 public function getErrorText(): string
669 {
670 return $this->errortext ?? '';
671 }
672
673 public function setErrorText(?string $text): void
674 {
675 $this->errortext = $text ?? '';
676 }
677
678 public function getParsedErrorText(): array
679 {
680 return $this->parsed_errortext;
681 }
682
683 private function getParsedErrorTextForJS(): array
684 {
685 $answers = [];
686 foreach ($this->parsed_errortext as $paragraph) {
687 foreach ($paragraph as $position => $word) {
688 $answers[] = [
689 'answertext' => $word['text'],
690 'order' => $this->getId() . '_' . $position
691 ];
692 }
693 $answers[] = [
694 'answertext' => '###'
695 ];
696 }
697 array_pop($answers);
698
699 return $answers;
700 }
701
702 public function setParsedErrorText(array $parsed_errortext): void
703 {
704 $this->parsed_errortext = $parsed_errortext;
705 }
706
707 public function getTextSize(): float
708 {
709 return $this->textsize;
710 }
711
712 public function setTextSize($a_value): void
713 {
714 // in self-assesment-mode value should always be set (and must not be null)
715 if ($a_value === null) {
716 $a_value = 100;
717 }
718 $this->textsize = $a_value;
719 }
720
721 public function getPointsWrong(): ?float
722 {
723 return $this->points_wrong;
724 }
725
726 public function setPointsWrong($a_value): void
727 {
728 $this->points_wrong = $a_value;
729 }
730
731 public function toJSON(): string
732 {
733 $result = [];
734 $result['id'] = $this->getId();
735 $result['type'] = (string) $this->getQuestionType();
736 $result['title'] = $this->getTitleForHTMLOutput();
737 $result['question'] = $this->formatSAQuestion($this->getQuestion());
738 $result['text'] = ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
739 $result['nr_of_tries'] = $this->getNrOfTries();
740 $result['shuffle'] = $this->getShuffle();
741 $result['feedback'] = [
742 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
743 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
744 ];
745
746 $result['correct_answers'] = $this->getErrorDataAsArrayForJS();
747 $result['answers'] = $this->getParsedErrorTextForJS();
748
749 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
750 $result['mobs'] = $mobs;
751
752 return json_encode($result);
753 }
754
755 public function getOperators(string $expression): array
756 {
758 }
759
760 public function getExpressionTypes(): array
761 {
762 return [
767 ];
768 }
769
770 public function getUserQuestionResult(
771 int $active_id,
772 int $pass
774 $result = new ilUserQuestionResult($this, $active_id, $pass);
775
776 $data = $this->db->queryF(
777 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
778 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
779 )",
780 ["integer", "integer", "integer","integer", "integer", "integer"],
781 [$active_id, $pass, $this->getId(), $active_id, $pass, $this->getId()]
782 );
783
784 while ($row = $this->db->fetchAssoc($data)) {
785 $result->addKeyValue($row["value1"], $row["value1"]);
786 }
787
788 $points = $this->calculateReachedPoints($active_id, $pass);
789 $max_points = $this->getMaximumPoints();
790
791 $result->setReachedPercentage(($points / $max_points) * 100);
792
793 return $result;
794 }
795
796 public function parseErrorText(): void
797 {
798 $text_by_paragraphs = preg_split(self::PARAGRAPH_SPLIT_REGEXP, $this->getErrorText());
799 $text_array = [];
800 $offset = 0;
801 foreach ($text_by_paragraphs as $paragraph) {
802 $text_array[] = $this->addErrorInformationToTextParagraphArray(
803 preg_split(self::WORD_SPLIT_REGEXP, trim($paragraph)),
804 $offset
805 );
806 $offset += count(end($text_array));
807 }
808 $this->setParsedErrorText($text_array);
809 }
810
816 private function addErrorInformationToTextParagraphArray(array $paragraph, int $offset): array
817 {
818 $paragraph_with_error_info = [];
819 $passage_start = null;
820 foreach ($paragraph as $position => $word) {
821 $actual_position = $position + $offset;
822 if ($passage_start !== null
823 && (mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS['end']) === mb_strlen($word) - 2
824 || mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS['end']) === mb_strlen($word) - 3
825 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word, -1)) === 1)) {
826 $actual_word = $this->parsePassageEndWord($word);
827
828 $paragraph_with_error_info[$passage_start]['text_wrong'] .=
829 ' ' . $actual_word;
830 $paragraph_with_error_info[$actual_position] = [
831 'text' => $actual_word,
832 'error_type' => 'passage_end'
833 ];
834 $passage_start = null;
835 continue;
836 }
837 if ($passage_start !== null) {
838 $paragraph_with_error_info[$passage_start]['text_wrong'] .= ' ' . $word;
839 $paragraph_with_error_info[$actual_position] = [
840 'text' => $word,
841 'error_type' => 'in_passage'
842 ];
843 continue;
844 }
845 if (mb_strpos($word, self::ERROR_PARAGRAPH_DELIMITERS['start']) === 0) {
846 $paragraph_with_error_info[$actual_position] = [
847 'text' => substr($word, 2),
848 'text_wrong' => substr($word, 2),
849 'error_type' => 'passage_start',
850 'error_position' => $actual_position,
851 ];
852 $passage_start = $actual_position;
853 continue;
854 }
855 if (mb_strpos($word, self::ERROR_WORD_MARKER) === 0) {
856 $paragraph_with_error_info[$actual_position] = [
857 'text' => substr($word, 1),
858 'text_wrong' => substr($word, 1),
859 'error_type' => 'word',
860 'error_position' => $actual_position,
861 ];
862 continue;
863 }
864
865 $paragraph_with_error_info[$actual_position] = [
866 'text' => $word,
867 'error_type' => 'none',
868 'points' => $this->getPointsWrong()
869 ];
870 }
871
872 return $paragraph_with_error_info;
873 }
874
875 private function parsePassageEndWord(string $word): string
876 {
877 if (mb_substr($word, -2) === self::ERROR_PARAGRAPH_DELIMITERS['end']) {
878 return mb_substr($word, 0, -2);
879 }
880 return mb_substr($word, 0, -3) . mb_substr($word, -1);
881 }
882
890 public function getAvailableAnswerOptions($index = null): ?int
891 {
892 $error_text_array = array_reduce(
893 $this->parsed_errortext,
894 fn($c, $v) => $c + $v
895 );
896
897 if ($index === null) {
898 return $error_text_array;
899 }
900
901 if (array_key_exists($index, $error_text_array)) {
902 return $error_text_array[$index];
903 }
904
905 return null;
906 }
907
908 private function generateArrayByPositionFromErrorData(): array
909 {
910 $array_by_position = [];
911 foreach ($this->errordata as $error) {
912 $position = $error->getPosition();
913 if ($position === null) {
914 continue;
915 }
916 $array_by_position[$position] = [
917 'length' => $error->getLength(),
918 'points' => $error->getPoints(),
919 'text' => $error->getTextWrong(),
920 'text_correct' => $error->getTextCorrect()
921 ];
922 }
923 ksort($array_by_position);
924 return $array_by_position;
925 }
926
932 private function getErrorTokenHtml($item, $class, $useLinkTags): string
933 {
934 if ($useLinkTags) {
935 return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilLegacyFormElementsUtil::prepareFormOutput(
936 $item
937 )) . '</a>';
938 }
939
940 return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilLegacyFormElementsUtil::prepareFormOutput(
941 $item
942 )) . '</span>';
943 }
944
945 public function toLog(AdditionalInformationGenerator $additional_info): array
946 {
947 $result = [
948 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
949 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
950 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
951 AdditionalInformationGenerator::KEY_QUESTION_ERRORTEXT_ERRORTEXT => ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0),
952 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
953 ->getTrueFalseTagForBool($this->getShuffle()),
954 AdditionalInformationGenerator::KEY_FEEDBACK => [
955 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
956 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
957 ]
958 ];
959
960 $error_data = $this->getErrorData();
961 $result[AdditionalInformationGenerator::KEY_QUESTION_CORRECT_ANSWER_OPTIONS] = array_reduce(
962 array_keys($error_data),
963 static function (array $c, int $k) use ($error_data): array {
964 $c[$k + 1] = [
965 'text_wrong' => $error_data[$k]->getTextWrong(),
966 'text_correct' => $error_data[$k]->getTextCorrect(),
967 'points' => $error_data[$k]->getPoints()
968 ];
969 return $c;
970 },
971 []
972 );
973
974 return $result;
975 }
976
977 protected function solutionValuesToLog(
978 AdditionalInformationGenerator $additional_info,
979 array $solution_values
980 ): string {
981 return $this->createErrorTextExport(
982 array_map(
983 static fn(array $v): string => $v['value1'],
984 $solution_values
985 )
986 );
987 }
988
989 public function solutionValuesToText(array $solution_values): string
990 {
991 return $this->createErrorTextExport(
992 array_map(
993 static fn(array $v): string => $v['value1'],
994 $solution_values
995 )
996 );
997 }
998
999 public function getCorrectSolutionForTextOutput(int $active_id, int $pass): string
1000 {
1001 return $this->createErrorTextExport($this->getBestSelection());
1002 }
1003}
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|false $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'))
$text
Definition: xapiexit.php:21