ILIAS  release_5-0 Revision 5.0.0-1144-gc4397b1f870
class.assErrorText.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
7require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
8require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
9require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
10
24{
25 protected $errortext;
26 protected $textsize;
27 protected $errordata;
28 protected $points_wrong;
29
41 function __construct(
42 $title = '',
43 $comment = '',
44 $author = '',
45 $owner = -1,
46 $question = ''
47 )
48 {
49 parent::__construct($title, $comment, $author, $owner, $question);
50 $this->errortext = '';
51 $this->textsize = 100.0;
52 $this->errordata = array();
53 }
54
60 public function isComplete()
61 {
62 if (strlen($this->title)
63 && ($this->author)
64 && ($this->question)
65 && ($this->getMaximumPoints() > 0))
66 {
67 return true;
68 }
69 else
70 {
71 return false;
72 }
73 }
74
79 public function saveToDb($original_id = "")
80 {
84 parent::saveToDb();
85 }
86
88 {
89 global $ilDB;
90 $ilDB->manipulateF( "DELETE FROM qpl_a_errortext WHERE question_fi = %s",
91 array( 'integer' ),
92 array( $this->getId() )
93 );
94
95 $sequence = 0;
96 foreach ($this->errordata as $object)
97 {
98 $next_id = $ilDB->nextId( 'qpl_a_errortext' );
99 $ilDB->manipulateF( "INSERT INTO qpl_a_errortext (answer_id, question_fi, text_wrong, text_correct, points, sequence) VALUES (%s, %s, %s, %s, %s, %s)",
100 array( 'integer', 'integer', 'text', 'text', 'float', 'integer' ),
101 array(
102 $next_id,
103 $this->getId(),
104 $object->text_wrong,
105 $object->text_correct,
106 $object->points,
107 $sequence++
108 )
109 );
110 }
111 }
112
119 {
120 global $ilDB;
121 // save additional data
122 $ilDB->manipulateF( "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
123 array( "integer" ),
124 array( $this->getId() )
125 );
126
127 $ilDB->manipulateF("INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, errortext, textsize, points_wrong) VALUES (%s, %s, %s, %s)",
128 array("integer", "text", "float", "float"),
129 array(
130 $this->getId(),
131 $this->getErrorText(),
132 $this->getTextSize(),
133 $this->getPointsWrong()
134 )
135 );
136 }
137
144 public function loadFromDb($question_id)
145 {
146 global $ilDB;
147
148 $result = $ilDB->queryF("SELECT qpl_questions.*, " . $this->getAdditionalTableName() . ".* FROM qpl_questions LEFT JOIN " . $this->getAdditionalTableName() . " ON " . $this->getAdditionalTableName() . ".question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s",
149 array("integer"),
150 array($question_id)
151 );
152 if ($result->numRows() == 1)
153 {
154 $data = $ilDB->fetchAssoc($result);
155 $this->setId($question_id);
156 $this->setObjId($data["obj_fi"]);
157 $this->setTitle($data["title"]);
158 $this->setComment($data["description"]);
159 $this->setOriginalId($data["original_id"]);
160 $this->setNrOfTries($data['nr_of_tries']);
161 $this->setAuthor($data["author"]);
162 $this->setPoints($data["points"]);
163 $this->setOwner($data["owner"]);
164 include_once("./Services/RTE/classes/class.ilRTE.php");
165 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
166 $this->setErrorText($data["errortext"]);
167 $this->setTextSize($data["textsize"]);
168 $this->setPointsWrong($data["points_wrong"]);
169 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
170
171 try
172 {
173 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
174 }
176 {
177 }
178 }
179
180 $result = $ilDB->queryF("SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
181 array('integer'),
182 array($question_id)
183 );
184 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
185 if ($result->numRows() > 0)
186 {
187 while ($data = $ilDB->fetchAssoc($result))
188 {
189 array_push($this->errordata, new assAnswerErrorText($data["text_wrong"], $data["text_correct"], $data["points"]));
190 }
191 }
192
193 parent::loadFromDb($question_id);
194 }
195
199 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
200 {
201 if ($this->id <= 0)
202 {
203 // The question has not been saved. It cannot be duplicated
204 return;
205 }
206 // duplicate the question in database
207 $this_id = $this->getId();
208 $thisObjId = $this->getObjId();
209
210 $clone = $this;
211 include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
213 $clone->id = -1;
214
215 if( (int)$testObjId > 0 )
216 {
217 $clone->setObjId($testObjId);
218 }
219
220 if ($title)
221 {
222 $clone->setTitle($title);
223 }
224
225 if ($author)
226 {
227 $clone->setAuthor($author);
228 }
229 if ($owner)
230 {
231 $clone->setOwner($owner);
232 }
233
234 if ($for_test)
235 {
236 $clone->saveToDb($original_id);
237 }
238 else
239 {
240 $clone->saveToDb();
241 }
242 // copy question page content
243 $clone->copyPageOfQuestion($this_id);
244 // copy XHTML media objects
245 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
246
247 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
248 return $clone->id;
249 }
250
254 public function copyObject($target_questionpool_id, $title = "")
255 {
256 if ($this->id <= 0)
257 {
258 // The question has not been saved. It cannot be duplicated
259 return;
260 }
261 // duplicate the question in database
262
263 $thisId = $this->getId();
264 $thisObjId = $this->getObjId();
265
266 $clone = $this;
267 include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
269 $clone->id = -1;
270 $clone->setObjId($target_questionpool_id);
271 if ($title)
272 {
273 $clone->setTitle($title);
274 }
275 $clone->saveToDb();
276
277 // copy question page content
278 $clone->copyPageOfQuestion($original_id);
279 // copy XHTML media objects
280 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
281
282 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
283
284 return $clone->id;
285 }
286
287 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
288 {
289 if ($this->id <= 0)
290 {
291 // The question has not been saved. It cannot be duplicated
292 return;
293 }
294
295 include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
296
297 $sourceQuestionId = $this->id;
298 $sourceParentId = $this->getObjId();
299
300 // duplicate the question in database
301 $clone = $this;
302 $clone->id = -1;
303
304 $clone->setObjId($targetParentId);
305
306 if ($targetQuestionTitle)
307 {
308 $clone->setTitle($targetQuestionTitle);
309 }
310
311 $clone->saveToDb();
312 // copy question page content
313 $clone->copyPageOfQuestion($sourceQuestionId);
314 // copy XHTML media objects
315 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
316
317 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
318
319 return $clone->id;
320 }
321
327 public function getMaximumPoints()
328 {
329 $maxpoints = 0.0;
330 foreach ($this->errordata as $object)
331 {
332 if ($object->points > 0) $maxpoints += $object->points;
333 }
334 return $maxpoints;
335 }
336
347 public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
348 {
349 if( $returndetails )
350 {
351 throw new ilTestException('return details not implemented for '.__METHOD__);
352 }
353
354 global $ilDB;
355
356 /* First get the positions which were selected by the user. */
357 $positions = array();
358 if (is_null($pass)) {
359 $pass = $this->getSolutionMaxPass($active_id);
360 }
361 $result = $this->getCurrentSolutionResultSet($active_id, $pass);
362
363 while ($row = $ilDB->fetchAssoc($result)) {
364 array_push($positions, $row['value1']);
365 }
366 $points = $this->getPointsForSelectedPositions($positions);
367 return $points;
368 }
369
371 {
372 return $this->getPointsForSelectedPositions($previewSession->getParticipantsSolution());
373 }
374
383 public function saveWorkingData($active_id, $pass = NULL)
384 {
385 global $ilDB;
386 global $ilUser;
387
388 if (is_null($pass))
389 {
390 include_once "./Modules/Test/classes/class.ilObjTest.php";
391 $pass = ilObjTest::_getPass($active_id);
392 }
393
394 $this->getProcessLocker()->requestUserSolutionUpdateLock();
395
396 $affectedRows = $this->removeCurrentSolution($active_id, $pass);
397
398 $entered_values = false;
399 if (strlen($_POST["qst_" . $this->getId()]))
400 {
401 $selected = split(",", $_POST["qst_" . $this->getId()]);
402 foreach ($selected as $position)
403 {
404 $affectedRows = $this->saveCurrentSolution($active_id, $pass, $position, null);
405 }
406 $entered_values = true;
407 }
408
409 $this->getProcessLocker()->releaseUserSolutionUpdateLock();
410
411 if ($entered_values)
412 {
413 include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
415 {
416 $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
417 }
418 }
419 else
420 {
421 include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
423 {
424 $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
425 }
426 }
427
428 return true;
429 }
430
431 public function savePreviewData(ilAssQuestionPreviewSession $previewSession)
432 {
433 if( strlen($_POST["qst_" . $this->getId()]) )
434 {
435 $selection = explode(',', $_POST["qst_{$this->getId()}"]);
436 }
437 else
438 {
439 $selection = array();
440 }
441
442 $previewSession->setParticipantsSolution($selection);
443 }
444
453 protected function reworkWorkingData($active_id, $pass, $obligationsAnswered)
454 {
455 // nothing to rework!
456 }
457
463 public function getQuestionType()
464 {
465 return "assErrorText";
466 }
467
473 public function getAdditionalTableName()
474 {
475 return "qpl_qst_errortext";
476 }
477
483 public function getAnswerTableName()
484 {
485 return "qpl_a_errortext";
486 }
487
493 {
494 $text = parent::getRTETextWithMediaObjects();
495 return $text;
496 }
497
509 public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
510 {
511 include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
512 $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
513 $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
514
515 $i= 0;
516 $selections = array();
517 $solutions =& $this->getSolutionValues($active_id, $pass);
518 if (is_array($solutions))
519 {
520 foreach ($solutions as $solution)
521 {
522 array_push($selections, $solution['value1']);
523 }
524 $errortext_value = join(",", $selections);
525 }
526 $errortext = $this->createErrorTextExport($selections);
527 $i++;
528 $worksheet->writeString($startrow+$i, 0, ilExcelUtils::_convert_text($errortext));
529 $i++;
530 return $startrow + $i + 1;
531 }
532
545 public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
546 {
547 include_once "./Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php";
548 $import = new assErrorTextImport($this);
549 $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
550 }
551
558 public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
559 {
560 include_once "./Modules/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php";
561 $export = new assErrorTextExport($this);
562 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
563 }
564
570 public function getBestSolution($active_id, $pass)
571 {
572 $user_solution = array();
573 return $user_solution;
574 }
575
576 public function getErrorsFromText($a_text = "")
577 {
578 if (strlen($a_text) == 0)
579 $a_text = $this->getErrorText();
580
581 /* Workaround to allow '(' and ')' in passages.
582 The beginning- and ending- Passage delimiters are
583 replaced by a ~ (Tilde) symbol. */
584 $a_text = str_replace(array("((", "))"), array("~", "~"), $a_text);
585
586 /* Match either Passage delimited by double brackets
587 or single words marked with a hash (#). */
588 $r_passage = "/(~([^~]+)~|#([^\s]+))/";
589
590 preg_match_all($r_passage, $a_text, $matches);
591
592 if (is_array($matches[0]) && !empty($matches[0])) {
593 /* At least one match. */
594
595 /* We need only groups 2 and 3, respectively representing
596 passage matches and single word matches. */
597 $matches = array_intersect_key($matches, array(2 => '', 3 => ''));
598
599 /* Remove empty values. */
600 $matches[2] = array_diff($matches[2], array(''));
601 $matches[3] = array_diff($matches[3], array(''));
602
603 return array(
604 "passages" => $matches[2],
605 "words" => $matches[3],);
606 }
607
608 return array();
609 }
610
611 public function setErrorData($a_data)
612 {
613 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
614 $temp = $this->errordata;
615 $this->errordata = array();
616 foreach ($a_data as $err_type => $errors) {
617 /* Iterate through error types (Passages|single words) */
618
619 foreach ($errors as $idx => $error) {
620 /* Iterate through errors of this type. */
621 $text_correct = "";
622 $points = 0.0;
623 foreach ($temp as $object) {
624 if (strcmp($object->text_wrong, $error) == 0) {
625 $text_correct = $object->text_correct;
626 $points = $object->points;
627 continue;
628 }
629 }
630 $this->errordata[$idx] = new assAnswerErrorText($error, $text_correct, $points);
631 }
632 }
633 ksort($this->errordata);
634 }
635
636 public function createErrorTextOutput($selections = null, $graphicalOutput = false, $correct_solution = false)
637 {
638 $counter = 0;
639 $errorcounter = 0;
640 include_once "./Services/Utilities/classes/class.ilStr.php";
641 if (!is_array($selections)) $selections = array();
642 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
643
644 foreach ($textarray as $textidx => $text)
645 {
646 $in_passage = false;
647 $passage_end = false;
648 $items = preg_split("/\s+/", $text);
649 foreach ($items as $idx => $item)
650 {
651 $img = '';
652
653 if(
654 ($posHash = strpos($item, '#')) === 0 ||
655 ($posOpeningBrackets = strpos($item, '((')) === 0 ||
656 ($posClosingBrackets = strpos($item, '))')) !== false
657 )
658 {
659 /* (Word|Passage)-Marking delimiter found. */
660
661 if($posHash !== false)
662 {
663 $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
664 $passage_end = false;
665 }
666 else if($posOpeningBrackets !== false)
667 {
668 $in_passage = true;
669 $passage_start_idx = $counter;
670 $items_in_passage = array();
671 $passage_end = false;
672 $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
673
674 /* Sometimes a closing bracket group needs
675 to be removed as well. */
676 if(strpos($item, '))') !== false)
677 {
678 $item = str_replace("))", "", $item);
679 $passage_end = true;
680 }
681 }
682 else
683 {
684 $passage_end = true;
685 $item = str_replace("))", "", $item);
686 }
687
688 if($correct_solution && !$in_passage)
689 {
690 $errorobject = $this->errordata[$errorcounter];
691 if (is_object($errorobject) )
692 {
693 $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
694 }
695 $errorcounter++;
696 }
697 }
698
699 if($in_passage && !$passage_end)
700 {
701 $items_in_passage[$idx] = $item;
702 $items[$idx] = '';
703 $counter++;
704 continue;
705 }
706
707 if($in_passage && $passage_end)
708 {
709 $in_passage = false;
710 $passage_end = false;
711 if($correct_solution)
712 {
713 $class = '';
714 if($this->isTokenSelected($counter, $selections))
715 {
716 $class = "sel";
717 }
718
719 $errorobject = $this->errordata[$errorcounter];
720 if (is_object($errorobject) )
721 {
722 $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
723 }
724 $errorcounter++;
725 $items[$idx] = '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>' . $img;
726 $counter++;
727 continue;
728 }
729
730 $group_selected = true;
731 if($graphicalOutput)
732 {
733 $start_idx = $passage_start_idx;
734 foreach($items_in_passage as $tmp_idx => $tmp_item)
735 {
736 if(!$this->isTokenSelected($start_idx, $selections))
737 {
738 $group_selected = false;
739 break;
740 }
741
742 ++$start_idx;
743 }
744 if($group_selected)
745 {
746 if(!$this->isTokenSelected($counter, $selections))
747 {
748 $group_selected = false;
749 }
750 }
751 }
752
753 $item_stack = array();
754 $start_idx = $passage_start_idx;
755 foreach($items_in_passage as $tmp_idx => $tmp_item)
756 {
757 $class = '';
758 if($this->isTokenSelected($start_idx, $selections))
759 {
760 $class = "sel";
761 }
762 $item_stack[] = '<a class="' . $class . '" href="#">' . ilUtil::prepareFormOutput($tmp_item) . '</a>' . $img;
763 $start_idx++;
764 }
765 $class = '';
766 if($this->isTokenSelected($counter, $selections))
767 {
768 $class = "sel";
769 }
770 if($graphicalOutput)
771 {
772 if ($group_selected)
773 {
774 $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
775 }
776 else
777 {
778 $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
779 }
780 }
781
782 $item_stack[] = '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>' . $img;
783 $item_stack = trim(implode(" ", $item_stack));
784 $item_stack = strlen($item_stack) ? $item_stack : '&nbsp;';
785
786 if($graphicalOutput)
787 {
788 $items[$idx] = '<span class="selGroup">'.$item_stack.'</span>';
789 }
790 else
791 {
792 $items[$idx] = $item_stack;
793 }
794
795 $counter++;
796 continue;
797 }
798
799 // Errors markes with #, group errors (()) are handled above
800 $class = '';
801 $img = '';
802 if($this->isTokenSelected($counter, $selections))
803 {
804 $class = "sel";
805 if($graphicalOutput)
806 {
807 if ($this->getPointsForSelectedPositions(array($counter)) > 0)
808 {
809 $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
810 }
811 else
812 {
813 $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
814 }
815 }
816 }
817
818 $items[$idx] = '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>' . $img;
819 $counter++;
820 }
821 $textarray[$textidx] = '<p>' . implode(" ", $items) . '</p>';
822 }
823
824 return implode("\n", $textarray);
825 }
826
827 protected function isTokenSelected($counter, array $selection)
828 {
829 foreach($selection as $data)
830 {
831 if( !is_array($data) )
832 {
833 if($counter == $data)
834 {
835 return true;
836 }
837 }
838 elseif( in_array($counter, $data) )
839 {
840 return true;
841 }
842 }
843
844 return false;
845 }
846
847 public function createErrorTextExport($selections = null)
848 {
849 $counter = 0;
850 $errorcounter = 0;
851 include_once "./Services/Utilities/classes/class.ilStr.php";
852 if (!is_array($selections)) $selections = array();
853 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
854 foreach ($textarray as $textidx => $text)
855 {
856 $items = preg_split("/\s+/", $text);
857 foreach ($items as $idx => $item)
858 {
859 if (($posHash = strpos($item, '#')) === 0
860 || ($posOpeningBrackets = strpos($item, '((')) === 0
861 || ($posClosingBrackets = strpos($item, '))')) !== false) {
862 /* (Word|Passage)-Marking delimiter found. */
863
864 if ($posHash !== false)
865 $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
866 elseif ($posOpeningBrackets !== false) {
867 $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
868
869 /* Sometimes a closing bracket group needs
870 to be removed as well. */
871 if (strpos($item, '))') !== false)
872 $item = ilStr::substr($item, 0, ilStr::strlen($item) - 2);
873 }
874 else{
875 $appendComma = "";
876 if ($item{$posClosingBrackets+2} == ',')
877 $appendComma = ",";
878
879 $item = ilStr::substr($item, 0, $posClosingBrackets) . $appendComma;
880 }
881 }
882
883 $word = "";
884 if (in_array($counter, $selections))
885 {
886 $word .= '#';
887 }
888 $word .= ilUtil::prepareFormOutput($item);
889 if (in_array($counter, $selections))
890 {
891 $word .= '#';
892 }
893 $items[$idx] = $word;
894 $counter++;
895 }
896 $textarray[$textidx] = join($items, " ");
897 }
898 return join($textarray, "\n");
899 }
900
901 public function getBestSelection($withPositivePointsOnly = true)
902 {
903 $passages = array();
904 $words = array();
905 $counter = 0;
906 $errorcounter = 0;
907 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
908 foreach ($textarray as $textidx => $text)
909 {
910 $items = preg_split("/\s+/", $text);
911 $inPassage = false;
912 foreach ($items as $word)
913 {
914 $points = $this->getPointsWrong();
915 $isErrorItem = false;
916 if (strpos($word, '#') === 0)
917 {
918 /* Word selection detected */
919 $errorobject = $this->errordata[$errorcounter];
920 if (is_object($errorobject))
921 {
922 $points = $errorobject->points;
923 $isErrorItem = true;
924 }
925 $errorcounter++;
926 }
927 elseif (($posOpeningBracket = strpos($word, '((')) === 0
928 || ($posClosingBracket = strpos($word, '))')) !== false
929 || $inPassage)
930 {
931 /* Passage selection detected */
932
933 if ($posOpeningBracket !== false)
934 {
935 $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
936 $inPassage = true;
937 }
938 elseif ($posClosingBracket !== false)
939 {
940 $inPassage = false;
941 $cur_pidx = count($passages) - 1;
942 $passages[$cur_pidx]['end_pos'] = $counter;
943
944 $errorobject = $this->errordata[$errorcounter];
945 if (is_object($errorobject))
946 {
947 $passages[$cur_pidx]['score'] = $errorobject->points;
948 $passages[$cur_pidx]['isError'] = true;
949 }
950
951 $errorcounter++;
952 }
953
954 $cur_pidx = count($passages) - 1;
955 $passages[$cur_pidx]['cnt_words']++;
956 $points = 0;
957 }
958
959 $words[$counter] = array("word" => $word, "points" => $points, "isError" => $isErrorItem);
960 $counter++;
961 }
962 }
963
964 $selections = array();
965 foreach ($passages as $cnt => $pdata)
966 {
967 if (!$withPositivePointsOnly && $pdata['isError'] || $withPositivePointsOnly && $pdata['score'] > 0)
968 {
969 $indexes = range($pdata['begin_pos'], $pdata['end_pos']);
970 $selections[$pdata['begin_pos']] = $indexes;
971 }
972 }
973
974 foreach ($words as $idx => $word)
975 {
976 if (!$withPositivePointsOnly && $word['isError'] || $withPositivePointsOnly && $word['points'] > 0)
977 {
978 $selections[$idx] = array($idx);
979 }
980 }
981
982 ksort($selections);
983
984 $selections = array_values($selections);
985
986 return $selections;
987 }
988
989 protected function getPointsForSelectedPositions($positions)
990 {
991 $passages = array();
992 $words = array();
993 $counter = 0;
994 $errorcounter = 0;
995 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
996 foreach ($textarray as $textidx => $text)
997 {
998 $items = preg_split("/\s+/", $text);
999 $inPassage = false;
1000 foreach ($items as $word)
1001 {
1002 $points = $this->getPointsWrong();
1003 if (strpos($word, '#') === 0)
1004 {
1005 /* Word selection detected */
1006 $errorobject = $this->errordata[$errorcounter];
1007 if (is_object($errorobject))
1008 {
1009 $points = $errorobject->points;
1010 }
1011 $errorcounter++;
1012 }
1013 elseif (($posOpeningBracket = strpos($word, '((')) === 0
1014 || ($posClosingBracket = strpos($word, '))')) !== false
1015 || $inPassage)
1016 {
1017 /* Passage selection detected */
1018
1019 if ($posOpeningBracket !== false)
1020 {
1021 $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
1022 $inPassage = true;
1023 }
1024 elseif ($posClosingBracket !== false)
1025 {
1026 $inPassage = false;
1027 $cur_pidx = count($passages) - 1;
1028 $passages[$cur_pidx]['end_pos'] = $counter;
1029
1030 $errorobject = $this->errordata[$errorcounter];
1031 if (is_object($errorobject))
1032 {
1033 $passages[$cur_pidx]['score'] = $errorobject->points;
1034 }
1035 $errorcounter++;
1036 }
1037
1038 $cur_pidx = count($passages) - 1;
1039 $passages[$cur_pidx]['cnt_words']++;
1040 $points = 0;
1041 }
1042
1043 $words[$counter] = array("word" => $word, "points" => $points);
1044 $counter++;
1045 }
1046 }
1047
1048 /* Calculate reached points */
1049 $total = 0;
1050 foreach ($positions as $position)
1051 {
1052 /* First iterate through positions
1053 to identify single-word-selections. */
1054
1055 $total += $words[$position]['points'];
1056 }
1057
1058 foreach ($passages as $cnt => $p_data)
1059 {
1060 /* Iterate through configured passages to check
1061 wether the entire passage is selected or not.
1062 The total points is incremented by the passage's
1063 score only if the entire passage is selected. */
1064 $isSelected = in_array($p_data['begin_pos'], $positions);
1065
1066 for ($i = 0; $i < $p_data['cnt_words']; $i++)
1067 {
1068 $current_pos = $p_data['begin_pos'] + $i;
1069 $isSelected = $isSelected && in_array($current_pos, $positions);
1070 }
1071
1072 $total += $isSelected ? $p_data['score'] : 0;
1073 }
1074
1075 return $total;
1076 }
1077
1081 public function flushErrorData()
1082 {
1083 $this->errordata = array();
1084 }
1085
1086 public function addErrorData($text_wrong, $text_correct, $points)
1087 {
1088 include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
1089 array_push($this->errordata, new assAnswerErrorText($text_wrong, $text_correct, $points));
1090 }
1091
1097 public function getErrorData()
1098 {
1099 return $this->errordata;
1100 }
1101
1107 public function getErrorText()
1108 {
1109 return $this->errortext;
1110 }
1111
1117 public function setErrorText($a_value)
1118 {
1119 $this->errortext = $a_value;
1120 }
1121
1127 public function getTextSize()
1128 {
1129 return $this->textsize;
1130 }
1131
1137 public function setTextSize($a_value)
1138 {
1139 // in self-assesment-mode value should always be set (and must not be null)
1140 if($a_value === null)
1141 {
1142 $a_value = 100;
1143 }
1144 $this->textsize = $a_value;
1145 }
1146
1152 public function getPointsWrong()
1153 {
1154 return $this->points_wrong;
1155 }
1156
1162 public function setPointsWrong($a_value)
1163 {
1164 $this->points_wrong = $a_value;
1165 }
1166
1170 public function __get($value)
1171 {
1172 switch ($value)
1173 {
1174 case "errortext":
1175 return $this->getErrorText();
1176 break;
1177 case "textsize":
1178 return $this->getTextSize();
1179 break;
1180 case "points_wrong":
1181 return $this->getPointsWrong();
1182 break;
1183 default:
1184 return parent::__get($value);
1185 break;
1186 }
1187 }
1188
1192 public function __set($key, $value)
1193 {
1194 switch ($key)
1195 {
1196 case "errortext":
1197 $this->setErrorText($value);
1198 break;
1199 case "textsize":
1200 $this->setTextSize($value);
1201 break;
1202 case "points_wrong":
1203 $this->setPointsWrong($value);
1204 break;
1205 default:
1206 parent::__set($key, $value);
1207 break;
1208 }
1209 }
1210
1211
1215 public function toJSON()
1216 {
1217 include_once("./Services/RTE/classes/class.ilRTE.php");
1218 $result = array();
1219 $result['id'] = (int) $this->getId();
1220 $result['type'] = (string) $this->getQuestionType();
1221 $result['title'] = (string) $this->getTitle();
1222 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1223 $result['text'] = (string) ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
1224 $result['nr_of_tries'] = (int) $this->getNrOfTries();
1225 $result['shuffle'] = (bool) $this->getShuffle();
1226 $result['feedback'] = array(
1227 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1228 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1229 );
1230
1231 $answers = array();
1232 foreach ($this->getErrorData() as $idx => $answer_obj)
1233 {
1234 array_push($answers, array(
1235 "answertext_wrong" => (string) $answer_obj->text_wrong,
1236 "answertext_correct" => (string) $answer_obj->text_correct,
1237 "points" => (float)$answer_obj->points,
1238 "order" => (int)$idx+1
1239 ));
1240 }
1241 $result['correct_answers'] = $answers;
1242
1243 $answers = array();
1244 $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
1245 foreach ($textarray as $textidx => $text)
1246 {
1247 $items = preg_split("/\s+/", trim($text));
1248 foreach ($items as $idx => $item)
1249 {
1250 if(substr($item, 0, 1) == "#")
1251 {
1252 $item = substr($item, 1);
1253
1254 // #14115 - add position to correct answer
1255 foreach($result["correct_answers"] as $aidx => $answer)
1256 {
1257 if($answer["answertext_wrong"] == $item && !$answer["pos"])
1258 {
1259 $result["correct_answers"][$aidx]["pos"] = $this->getId()."_".$textidx."_".($idx+1);
1260 break;
1261 }
1262 }
1263 }
1264 array_push($answers, array(
1265 "answertext" => (string) ilUtil::prepareFormOutput($item),
1266 "order" => $this->getId()."_".$textidx."_".($idx+1)
1267 ));
1268 }
1269 if($textidx != sizeof($textarray)-1)
1270 {
1271 array_push($answers, array(
1272 "answertext" => "###",
1273 "order" => $this->getId()."_".$textidx."_".($idx+2)
1274 ));
1275 }
1276 }
1277 $result['answers'] = $answers;
1278
1279 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1280 $result['mobs'] = $mobs;
1281
1282 return json_encode($result);
1283 }
1284
1293 public function getOperators($expression)
1294 {
1295 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1297 }
1298
1303 public function getExpressionTypes()
1304 {
1305 return array(
1310 );
1311 }
1312
1321 public function getUserQuestionResult($active_id, $pass)
1322 {
1324 global $ilDB;
1325 $result = new ilUserQuestionResult($this, $active_id, $pass);
1326
1327 $data = $ilDB->queryF(
1328 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1329 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1330 )",
1331 array("integer", "integer", "integer","integer", "integer", "integer"),
1332 array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1333 );
1334
1335 while($row = $ilDB->fetchAssoc($data))
1336 {
1337 $result->addKeyValue($row["value1"], $row["value1"]);
1338 }
1339
1340 $points = $this->calculateReachedPoints($active_id, $pass);
1341 $max_points = $this->getMaximumPoints();
1342
1343 $result->setReachedPercentage(($points/$max_points) * 100);
1344
1345 return $result;
1346 }
1347
1356 public function getAvailableAnswerOptions($index = null)
1357 {
1358 $error_text_array = explode(' ', $this->errortext);
1359
1360 if($index !== null)
1361 {
1362 if(array_key_exists($index, $error_text_array))
1363 {
1364 return $error_text_array[$index];
1365 }
1366 return null;
1367 }
1368 else
1369 {
1370 return $error_text_array;
1371 }
1372 }
1373}
$result
Class for error text answers.
Class for error text question exports.
Class for error text question imports.
Class for error text questions.
getErrorData()
Get error data.
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
Returns a QTI xml representation of the question and sets the internal domxml variable with the DOM X...
setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
Creates an Excel worksheet for the detailed cumulated results of this question.
flushErrorData()
Flush error data.
loadFromDb($question_id)
Loads the object from the database.
getTextSize()
Set text size in percent.
getOperators($expression)
Get all available operations for a specific question.
__construct( $title='', $comment='', $author='', $owner=-1, $question='')
assErorText constructor
saveWorkingData($active_id, $pass=NULL)
Saves the learners input of the question to the database.
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
getErrorsFromText($a_text="")
__get($value)
Object getter.
createErrorTextExport($selections=null)
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates the object.
savePreviewData(ilAssQuestionPreviewSession $previewSession)
__set($key, $value)
Object setter.
getErrorText()
Get error text.
toJSON()
Returns a JSON representation of the question.
setTextSize($a_value)
Set text size in percent.
setErrorText($a_value)
Set error text.
getAnswerTableName()
Returns the name of the answer table in the database.
saveToDb($original_id="")
Saves a the object to the database.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
copyObject($target_questionpool_id, $title="")
Copies an object.
setPointsWrong($a_value)
Set wrong points.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
getBestSelection($withPositivePointsOnly=true)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
getPointsForSelectedPositions($positions)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getAdditionalTableName()
Returns the name of the additional question data table in the database.
calculateReachedPoints($active_id, $pass=NULL, $returndetails=FALSE)
Returns the points, a learner has reached answering the question.
getPointsWrong()
Get wrong points.
isTokenSelected($counter, array $selection)
isComplete()
Returns true, if a single choice question is complete for use.
getBestSolution($active_id, $pass)
Returns the best solution for a given pass of a participant.
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
Creates a question from a QTI file.
reworkWorkingData($active_id, $pass, $obligationsAnswered)
Reworks the allready saved working data if neccessary.
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
addErrorData($text_wrong, $text_correct, $points)
getExpressionTypes()
Get all available expression types for a specific question.
createErrorTextOutput($selections=null, $graphicalOutput=false, $correct_solution=false)
getQuestionType()
Returns the question type of the question.
Abstract basic class which is to be extended by the concrete assessment question type classes.
& getSolutionValues($active_id, $pass=NULL)
Loads solutions of a given user from the database an returns it.
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
setId($id=-1)
Sets the id of the assQuestion object.
setOriginalId($original_id)
setObjId($obj_id=0)
Set the object id of the container object.
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
saveQuestionDataToDb($original_id="")
getId()
Gets the id of the assQuestion object.
getObjId()
Get the object id of the container object.
setTitle($title="")
Sets the title string of the assQuestion object.
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second.
setAuthor($author="")
Sets the authors name of the assQuestion object.
getShuffle()
Gets the shuffle flag.
logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
getTitle()
Gets the title string of the assQuestion object.
setPoints($a_points)
Sets the maximum available points for the question.
setComment($comment="")
Sets the comment string of the assQuestion object.
setNrOfTries($a_nr_of_tries)
getQuestion()
Gets the question string of the question object.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
setQuestion($question="")
Sets the question string of the question object.
_convert_text($a_text, $a_target="has been removed")
_getLogLanguage()
retrieve the log language for assessment logging
_enabledAssessmentLogging()
check wether assessment logging is enabled or not
_getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
_getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
Base Exception for all Exceptions relating to Modules/Test.
Class ilUserQuestionResult.
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
static prepareFormOutput($a_str, $a_strip=false)
prepares string output for html forms @access public
$_POST['username']
Definition: cron.php:12
Class iQuestionCondition.
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
Interface ilObjAnswerScoringAdjustable.
Interface ilObjQuestionScoringAdjustable.
global $ilDB
$mobs
$errors
global $ilUser
Definition: imgupload.php:15