ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.assErrorText.php
Go to the documentation of this file.
1 <?php
2 
19 declare(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 
91  public function saveAnswerSpecificDataToDb()
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']);
182  } catch (ilTestQuestionPoolException $e) {
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 
376  private function completeParsedErrorTextFromErrorData(): void
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  }
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  {
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  {
753  return ilOperatorsExpressionMapping::getOperatorsByExpression($expression);
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
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 }
getPointsForSelectedPositions(array $selected_word_positions)
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...
const FIND_PUNCTUATION_REGEXP
setNrOfTries(int $a_nr_of_tries)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
loadFromDb($question_id)
Loads the object from the database.
setOwner(int $owner=-1)
addErrorInformationToTextParagraphArray(array $paragraph, int $offset)
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
ensureNonNegativePoints(float $points)
getCorrectSolutionForTextOutput(int $active_id, int $pass)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getClassForPosition(int $position, bool $show_correct_solution, array $selections)
getTextForPosition(int $position, array $paragraph, bool $show_correct_solution)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
toLog(AdditionalInformationGenerator $additional_info)
static prepareFormOutput($a_str, bool $a_strip=false)
$c
Definition: deliver.php:25
setComment(string $comment="")
const ERROR_PARAGRAPH_DELIMITERS
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
getCorrectnessIconForPosition(int $position, bool $graphical_output, array $selections, array $correctness_icons)
generateArrayByPositionFromErrorData()
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
setErrorData(array $errors)
__construct(string $title='', string $comment='', string $author='', int $owner=-1, string $question='')
createErrorTextExport(array $selections)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
parsePassageEndWord(string $word)
const string $errortext
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
assembleErrorTextOutput(array $selections, bool $graphical_output=false, bool $show_correct_solution=false, bool $use_link_tags=true, array $correctness_icons=[])
const PARAGRAPH_SPLIT_REGEXP
setPointsWrong($a_value)
Class for error text questions.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
getExpressionTypes()
Get all available expression types for a specific question.
setErrorText(?string $text)
addPositionToErrorAnswer(assAnswerErrorText $error)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
setPoints(float $points)
setObjId(int $obj_id=0)
saveQuestionDataToDb(?int $original_id=null)
getBestSelection(bool $with_positive_points_only=true)
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
generateOutputStringFromPosition(int $position, array $selections, array $paragraph, bool $graphical_output, bool $show_correct_solution, bool $use_link_tags, array $correctness_icons)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getSolutionMaxPass(int $active_id)
getOperators(string $expression)
Get all available operations for a specific question.
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setId(int $id=-1)
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
__construct(Container $dic, ilPlugin $plugin)
setOriginalId(?int $original_id)
solutionValuesToText(array $solution_values)
setTitle(string $title="")
saveToDb(?int $original_id=null)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
completeParsedErrorTextFromErrorData()
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
setAuthor(string $author="")
setParsedErrorText(array $parsed_errortext)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
getAdditionalInformationFromExistingErrorDataByErrorText(array $current_error_data, string $text_wrong)
getErrorTokenHtml($item, $class, $useLinkTags)
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
setQuestion(string $question="")