ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
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 
4 require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
7 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
8 require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
9 require_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 
87  public function saveAnswerSpecificDataToDb()
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, $authorizedSolution = true, $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, $authorizedSolution);
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  $reachedPoints = $this->getPointsForSelectedPositions($previewSession->getParticipantsSolution());
373  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
374  return $this->ensureNonNegativePoints($reachedPoints);
375  }
376 
385  public function saveWorkingData($active_id, $pass = NULL, $authorized = true)
386  {
387  global $ilDB;
388  global $ilUser;
389 
390  if (is_null($pass))
391  {
392  include_once "./Modules/Test/classes/class.ilObjTest.php";
393  $pass = ilObjTest::_getPass($active_id);
394  }
395 
396  $entered_values = false;
397 
398  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function() use (&$entered_values, $active_id, $pass, $authorized) {
399 
400  $this->removeCurrentSolution($active_id, $pass, $authorized);
401 
402  if(strlen($_POST["qst_" . $this->getId()]))
403  {
404  $selected = explode(",", $_POST["qst_" . $this->getId()]);
405  foreach ($selected as $position)
406  {
407  $this->saveCurrentSolution($active_id, $pass, $position, null, $authorized);
408  }
409  $entered_values = true;
410  }
411 
412  });
413 
414  if ($entered_values)
415  {
416  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
418  {
419  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
420  }
421  }
422  else
423  {
424  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
426  {
427  assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
428  }
429  }
430 
431  return true;
432  }
433 
434  public function savePreviewData(ilAssQuestionPreviewSession $previewSession)
435  {
436  if( strlen($_POST["qst_" . $this->getId()]) )
437  {
438  $selection = explode(',', $_POST["qst_{$this->getId()}"]);
439  }
440  else
441  {
442  $selection = array();
443  }
444 
445  $previewSession->setParticipantsSolution($selection);
446  }
447 
451  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
452  {
453  // nothing to rework!
454  }
455 
461  public function getQuestionType()
462  {
463  return "assErrorText";
464  }
465 
471  public function getAdditionalTableName()
472  {
473  return "qpl_qst_errortext";
474  }
475 
481  public function getAnswerTableName()
482  {
483  return "qpl_a_errortext";
484  }
485 
490  public function getRTETextWithMediaObjects()
491  {
492  $text = parent::getRTETextWithMediaObjects();
493  return $text;
494  }
495 
499  public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
500  {
501  parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
502 
503  $i= 0;
504  $selections = array();
505  $solutions =& $this->getSolutionValues($active_id, $pass);
506  if (is_array($solutions))
507  {
508  foreach ($solutions as $solution)
509  {
510  array_push($selections, $solution['value1']);
511  }
512  $errortext_value = join(",", $selections);
513  }
514  $errortext = $this->createErrorTextExport($selections);
515  $i++;
516  $worksheet->setCell($startrow+$i, 0, $errortext);
517  $i++;
518 
519  return $startrow + $i + 1;
520  }
521 
534  public function fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
535  {
536  include_once "./Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php";
537  $import = new assErrorTextImport($this);
538  $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
539  }
540 
547  public function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = false, $test_output = false, $force_image_references = false)
548  {
549  include_once "./Modules/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php";
550  $export = new assErrorTextExport($this);
551  return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
552  }
553 
559  public function getBestSolution($active_id, $pass)
560  {
561  $user_solution = array();
562  return $user_solution;
563  }
564 
565  public function getErrorsFromText($a_text = "")
566  {
567  if (strlen($a_text) == 0)
568  $a_text = $this->getErrorText();
569 
570  /* Workaround to allow '(' and ')' in passages.
571  The beginning- and ending- Passage delimiters are
572  replaced by a ~ (Tilde) symbol. */
573  $a_text = str_replace(array("((", "))"), array("~", "~"), $a_text);
574 
575  /* Match either Passage delimited by double brackets
576  or single words marked with a hash (#). */
577  $r_passage = "/(~([^~]+)~|#([^\s]+))/";
578 
579  preg_match_all($r_passage, $a_text, $matches);
580 
581  if (is_array($matches[0]) && !empty($matches[0])) {
582  /* At least one match. */
583 
584  /* We need only groups 2 and 3, respectively representing
585  passage matches and single word matches. */
586  $matches = array_intersect_key($matches, array(2 => '', 3 => ''));
587 
588  /* Remove empty values. */
589  $matches[2] = array_diff($matches[2], array(''));
590  $matches[3] = array_diff($matches[3], array(''));
591 
592  return array(
593  "passages" => $matches[2],
594  "words" => $matches[3],);
595  }
596 
597  return array();
598  }
599 
600  public function setErrorData($a_data)
601  {
602  include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
603  $temp = $this->errordata;
604  $this->errordata = array();
605  foreach ($a_data as $err_type => $errors) {
606  /* Iterate through error types (Passages|single words) */
607 
608  foreach ($errors as $idx => $error) {
609  /* Iterate through errors of this type. */
610  $text_correct = "";
611  $points = 0.0;
612  foreach ($temp as $object) {
613  if (strcmp($object->text_wrong, $error) == 0) {
614  $text_correct = $object->text_correct;
615  $points = $object->points;
616  continue;
617  }
618  }
619  $this->errordata[$idx] = new assAnswerErrorText($error, $text_correct, $points);
620  }
621  }
622  ksort($this->errordata);
623  }
624 
625  public function createErrorTextOutput($selections = null, $graphicalOutput = false, $correct_solution = false, $use_link_tags = true)
626  {
627  $counter = 0;
628  $errorcounter = 0;
629  include_once "./Services/Utilities/classes/class.ilStr.php";
630  if (!is_array($selections)) $selections = array();
631  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
632 
633  foreach ($textarray as $textidx => $text)
634  {
635  $in_passage = false;
636  $passage_end = false;
637  $items = preg_split("/\s+/", $text);
638  foreach ($items as $idx => $item)
639  {
640  $img = '';
641 
642  if(
643  ($posHash = strpos($item, '#')) === 0 ||
644  ($posOpeningBrackets = strpos($item, '((')) === 0 ||
645  ($posClosingBrackets = strpos($item, '))')) !== false
646  )
647  {
648  /* (Word|Passage)-Marking delimiter found. */
649 
650  if($posHash !== false)
651  {
652  $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
653  $passage_end = false;
654  }
655  else if($posOpeningBrackets !== false)
656  {
657  $in_passage = true;
658  $passage_start_idx = $counter;
659  $items_in_passage = array();
660  $passage_end = false;
661  $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
662 
663  /* Sometimes a closing bracket group needs
664  to be removed as well. */
665  if(strpos($item, '))') !== false)
666  {
667  $item = str_replace("))", "", $item);
668  $passage_end = true;
669  }
670  }
671  else
672  {
673  $passage_end = true;
674  $item = str_replace("))", "", $item);
675  }
676 
677  if($correct_solution && !$in_passage)
678  {
679  $errorobject = $this->errordata[$errorcounter];
680  if (is_object($errorobject) )
681  {
682  $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
683  }
684  $errorcounter++;
685  }
686  }
687 
688  if($in_passage && !$passage_end)
689  {
690  $items_in_passage[$idx] = $item;
691  $items[$idx] = '';
692  $counter++;
693  continue;
694  }
695 
696  if($in_passage && $passage_end)
697  {
698  $in_passage = false;
699  $passage_end = false;
700  if($correct_solution)
701  {
702  $class = ( $this->isTokenSelected($counter, $selections) ?
703  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
704  );
705 
706  $errorobject = $this->errordata[$errorcounter];
707  if (is_object($errorobject) )
708  {
709  $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
710  }
711  $errorcounter++;
712  $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
713  $counter++;
714  continue;
715  }
716 
717  $group_selected = true;
718  if($graphicalOutput)
719  {
720  $start_idx = $passage_start_idx;
721  foreach($items_in_passage as $tmp_idx => $tmp_item)
722  {
723  if(!$this->isTokenSelected($start_idx, $selections))
724  {
725  $group_selected = false;
726  break;
727  }
728 
729  ++$start_idx;
730  }
731  if($group_selected)
732  {
733  if(!$this->isTokenSelected($counter, $selections))
734  {
735  $group_selected = false;
736  }
737  }
738  }
739 
740  $item_stack = array();
741  $start_idx = $passage_start_idx;
742  foreach($items_in_passage as $tmp_idx => $tmp_item)
743  {
744  $class = ( $this->isTokenSelected($counter, $selections) ?
745  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
746  );
747  $item_stack[] = $this->getErrorTokenHtml($tmp_item, $class, $use_link_tags) . $img;
748  $start_idx++;
749  }
750  $class = ( $this->isTokenSelected($counter, $selections) ?
751  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
752  );
753  if($graphicalOutput)
754  {
755  if ($group_selected)
756  {
757  $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
758  }
759  else
760  {
761  $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
762  }
763  }
764 
765  $item_stack[] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
766  $item_stack = trim(implode(" ", $item_stack));
767  $item_stack = strlen($item_stack) ? $item_stack : '&nbsp;';
768 
769  if($graphicalOutput)
770  {
771  $items[$idx] = '<span class="selGroup">'.$item_stack.'</span>';
772  }
773  else
774  {
775  $items[$idx] = $item_stack;
776  }
777 
778  $counter++;
779  continue;
780  }
781 
782  // Errors markes with #, group errors (()) are handled above
783  $class = 'ilc_qetitem_ErrorTextItem';
784  $img = '';
785  if($this->isTokenSelected($counter, $selections))
786  {
787  $class = "ilc_qetitem_ErrorTextSelected";
788  if($graphicalOutput)
789  {
791  {
792  $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
793  }
794  else
795  {
796  $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
797  }
798  }
799  }
800 
801  $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
802  $counter++;
803  }
804  $textarray[$textidx] = '<p>' . implode(" ", $items) . '</p>';
805  }
806 
807  return implode("\n", $textarray);
808  }
809 
810  protected function isTokenSelected($counter, array $selection)
811  {
812  foreach($selection as $data)
813  {
814  if( !is_array($data) )
815  {
816  if($counter == $data)
817  {
818  return true;
819  }
820  }
821  elseif( in_array($counter, $data) )
822  {
823  return true;
824  }
825  }
826 
827  return false;
828  }
829 
830  public function createErrorTextExport($selections = null)
831  {
832  $counter = 0;
833  $errorcounter = 0;
834  include_once "./Services/Utilities/classes/class.ilStr.php";
835  if (!is_array($selections)) $selections = array();
836  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
837  foreach ($textarray as $textidx => $text)
838  {
839  $items = preg_split("/\s+/", $text);
840  foreach ($items as $idx => $item)
841  {
842  if (($posHash = strpos($item, '#')) === 0
843  || ($posOpeningBrackets = strpos($item, '((')) === 0
844  || ($posClosingBrackets = strpos($item, '))')) !== false) {
845  /* (Word|Passage)-Marking delimiter found. */
846 
847  if ($posHash !== false)
848  $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
849  elseif ($posOpeningBrackets !== false) {
850  $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
851 
852  /* Sometimes a closing bracket group needs
853  to be removed as well. */
854  if (strpos($item, '))') !== false)
855  $item = ilStr::substr($item, 0, ilStr::strlen($item) - 2);
856  }
857  else{
858  $appendComma = "";
859  if ($item{$posClosingBrackets+2} == ',')
860  $appendComma = ",";
861 
862  $item = ilStr::substr($item, 0, $posClosingBrackets) . $appendComma;
863  }
864  }
865 
866  $word = "";
867  if (in_array($counter, $selections))
868  {
869  $word .= '#';
870  }
871  $word .= ilUtil::prepareFormOutput($item);
872  if (in_array($counter, $selections))
873  {
874  $word .= '#';
875  }
876  $items[$idx] = $word;
877  $counter++;
878  }
879  $textarray[$textidx] = join($items, " ");
880  }
881  return join($textarray, "\n");
882  }
883 
884  public function getBestSelection($withPositivePointsOnly = true)
885  {
886  $passages = array();
887  $words = array();
888  $counter = 0;
889  $errorcounter = 0;
890  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
891  foreach ($textarray as $textidx => $text)
892  {
893  $items = preg_split("/\s+/", $text);
894  $inPassage = false;
895  foreach ($items as $word)
896  {
897  $points = $this->getPointsWrong();
898  $isErrorItem = false;
899  if (strpos($word, '#') === 0)
900  {
901  /* Word selection detected */
902  $errorobject = $this->errordata[$errorcounter];
903  if (is_object($errorobject))
904  {
905  $points = $errorobject->points;
906  $isErrorItem = true;
907  }
908  $errorcounter++;
909  }
910  elseif (($posOpeningBracket = strpos($word, '((')) === 0
911  || ($posClosingBracket = strpos($word, '))')) !== false
912  || $inPassage)
913  {
914  /* Passage selection detected */
915 
916  if ($posOpeningBracket !== false)
917  {
918  $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
919  $inPassage = true;
920  }
921  elseif ($posClosingBracket !== false)
922  {
923  $inPassage = false;
924  $cur_pidx = count($passages) - 1;
925  $passages[$cur_pidx]['end_pos'] = $counter;
926 
927  $errorobject = $this->errordata[$errorcounter];
928  if (is_object($errorobject))
929  {
930  $passages[$cur_pidx]['score'] = $errorobject->points;
931  $passages[$cur_pidx]['isError'] = true;
932  }
933 
934  $errorcounter++;
935  }
936 
937  $cur_pidx = count($passages) - 1;
938  $passages[$cur_pidx]['cnt_words']++;
939  $points = 0;
940  }
941 
942  $words[$counter] = array("word" => $word, "points" => $points, "isError" => $isErrorItem);
943  $counter++;
944  }
945  }
946 
947  $selections = array();
948  foreach ($passages as $cnt => $pdata)
949  {
950  if (!$withPositivePointsOnly && $pdata['isError'] || $withPositivePointsOnly && $pdata['score'] > 0)
951  {
952  $indexes = range($pdata['begin_pos'], $pdata['end_pos']);
953  $selections[$pdata['begin_pos']] = $indexes;
954  }
955  }
956 
957  foreach ($words as $idx => $word)
958  {
959  if (!$withPositivePointsOnly && $word['isError'] || $withPositivePointsOnly && $word['points'] > 0)
960  {
961  $selections[$idx] = array($idx);
962  }
963  }
964 
965  ksort($selections);
966 
967  $selections = array_values($selections);
968 
969  return $selections;
970  }
971 
972  protected function getPointsForSelectedPositions($positions)
973  {
974  $passages = array();
975  $words = array();
976  $counter = 0;
977  $errorcounter = 0;
978  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
979  foreach ($textarray as $textidx => $text)
980  {
981  $items = preg_split("/\s+/", $text);
982  $inPassage = false;
983  foreach ($items as $word)
984  {
985  $points = $this->getPointsWrong();
986  if (strpos($word, '#') === 0)
987  {
988  /* Word selection detected */
989  $errorobject = $this->errordata[$errorcounter];
990  if (is_object($errorobject))
991  {
992  $points = $errorobject->points;
993  }
994  $errorcounter++;
995  }
996  elseif (($posOpeningBracket = strpos($word, '((')) === 0
997  || ($posClosingBracket = strpos($word, '))')) !== false
998  || $inPassage)
999  {
1000  /* Passage selection detected */
1001 
1002  if ($posOpeningBracket !== false)
1003  {
1004  $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
1005  $inPassage = true;
1006  }
1007  elseif ($posClosingBracket !== false)
1008  {
1009  $inPassage = false;
1010  $cur_pidx = count($passages) - 1;
1011  $passages[$cur_pidx]['end_pos'] = $counter;
1012 
1013  $errorobject = $this->errordata[$errorcounter];
1014  if (is_object($errorobject))
1015  {
1016  $passages[$cur_pidx]['score'] = $errorobject->points;
1017  }
1018  $errorcounter++;
1019  }
1020 
1021  $cur_pidx = count($passages) - 1;
1022  $passages[$cur_pidx]['cnt_words']++;
1023  $points = 0;
1024  }
1025 
1026  $words[$counter] = array("word" => $word, "points" => $points);
1027  $counter++;
1028  }
1029  }
1030 
1031  /* Calculate reached points */
1032  $total = 0;
1033  foreach ($positions as $position)
1034  {
1035  /* First iterate through positions
1036  to identify single-word-selections. */
1037 
1038  $total += $words[$position]['points'];
1039  }
1040 
1041  foreach ($passages as $cnt => $p_data)
1042  {
1043  /* Iterate through configured passages to check
1044  wether the entire passage is selected or not.
1045  The total points is incremented by the passage's
1046  score only if the entire passage is selected. */
1047  $isSelected = in_array($p_data['begin_pos'], $positions);
1048 
1049  for ($i = 0; $i < $p_data['cnt_words']; $i++)
1050  {
1051  $current_pos = $p_data['begin_pos'] + $i;
1052  $isSelected = $isSelected && in_array($current_pos, $positions);
1053  }
1054 
1055  $total += $isSelected ? $p_data['score'] : 0;
1056  }
1057 
1058  return $total;
1059  }
1060 
1064  public function flushErrorData()
1065  {
1066  $this->errordata = array();
1067  }
1068 
1069  public function addErrorData($text_wrong, $text_correct, $points)
1070  {
1071  include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
1072  array_push($this->errordata, new assAnswerErrorText($text_wrong, $text_correct, $points));
1073  }
1074 
1080  public function getErrorData()
1081  {
1082  return $this->errordata;
1083  }
1084 
1090  public function getErrorText()
1091  {
1092  return $this->errortext;
1093  }
1094 
1100  public function setErrorText($a_value)
1101  {
1102  $this->errortext = $a_value;
1103  }
1104 
1110  public function getTextSize()
1111  {
1112  return $this->textsize;
1113  }
1114 
1120  public function setTextSize($a_value)
1121  {
1122  // in self-assesment-mode value should always be set (and must not be null)
1123  if($a_value === null)
1124  {
1125  $a_value = 100;
1126  }
1127  $this->textsize = $a_value;
1128  }
1129 
1135  public function getPointsWrong()
1136  {
1137  return $this->points_wrong;
1138  }
1139 
1145  public function setPointsWrong($a_value)
1146  {
1147  $this->points_wrong = $a_value;
1148  }
1149 
1153  public function __get($value)
1154  {
1155  switch ($value)
1156  {
1157  case "errortext":
1158  return $this->getErrorText();
1159  break;
1160  case "textsize":
1161  return $this->getTextSize();
1162  break;
1163  case "points_wrong":
1164  return $this->getPointsWrong();
1165  break;
1166  default:
1167  return parent::__get($value);
1168  break;
1169  }
1170  }
1171 
1175  public function __set($key, $value)
1176  {
1177  switch ($key)
1178  {
1179  case "errortext":
1180  $this->setErrorText($value);
1181  break;
1182  case "textsize":
1183  $this->setTextSize($value);
1184  break;
1185  case "points_wrong":
1186  $this->setPointsWrong($value);
1187  break;
1188  default:
1189  parent::__set($key, $value);
1190  break;
1191  }
1192  }
1193 
1194 
1198  public function toJSON()
1199  {
1200  include_once("./Services/RTE/classes/class.ilRTE.php");
1201  $result = array();
1202  $result['id'] = (int) $this->getId();
1203  $result['type'] = (string) $this->getQuestionType();
1204  $result['title'] = (string) $this->getTitle();
1205  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1207  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1208  $result['shuffle'] = (bool) $this->getShuffle();
1209  $result['feedback'] = array(
1210  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1211  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1212  );
1213 
1214  $answers = array();
1215  foreach ($this->getErrorData() as $idx => $answer_obj)
1216  {
1217  array_push($answers, array(
1218  "answertext_wrong" => (string) $answer_obj->text_wrong,
1219  "answertext_correct" => (string) $answer_obj->text_correct,
1220  "points" => (float)$answer_obj->points,
1221  "order" => (int)$idx+1
1222  ));
1223  }
1224  $result['correct_answers'] = $answers;
1225 
1226  $answers = array();
1227  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
1228  foreach ($textarray as $textidx => $text)
1229  {
1230  $items = preg_split("/\s+/", trim($text));
1231  foreach ($items as $idx => $item)
1232  {
1233  if(substr($item, 0, 1) == "#")
1234  {
1235  $item = substr($item, 1);
1236 
1237  // #14115 - add position to correct answer
1238  foreach($result["correct_answers"] as $aidx => $answer)
1239  {
1240  if($answer["answertext_wrong"] == $item && !$answer["pos"])
1241  {
1242  $result["correct_answers"][$aidx]["pos"] = $this->getId()."_".$textidx."_".($idx+1);
1243  break;
1244  }
1245  }
1246  }
1247  array_push($answers, array(
1248  "answertext" => (string) ilUtil::prepareFormOutput($item),
1249  "order" => $this->getId()."_".$textidx."_".($idx+1)
1250  ));
1251  }
1252  if($textidx != sizeof($textarray)-1)
1253  {
1254  array_push($answers, array(
1255  "answertext" => "###",
1256  "order" => $this->getId()."_".$textidx."_".($idx+2)
1257  ));
1258  }
1259  }
1260  $result['answers'] = $answers;
1261 
1262  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1263  $result['mobs'] = $mobs;
1264 
1265  return json_encode($result);
1266  }
1267 
1276  public function getOperators($expression)
1277  {
1278  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1280  }
1281 
1286  public function getExpressionTypes()
1287  {
1288  return array(
1293  );
1294  }
1295 
1304  public function getUserQuestionResult($active_id, $pass)
1305  {
1307  global $ilDB;
1308  $result = new ilUserQuestionResult($this, $active_id, $pass);
1309 
1310  $data = $ilDB->queryF(
1311  "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1312  SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1313  )",
1314  array("integer", "integer", "integer","integer", "integer", "integer"),
1315  array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1316  );
1317 
1318  while($row = $ilDB->fetchAssoc($data))
1319  {
1320  $result->addKeyValue($row["value1"], $row["value1"]);
1321  }
1322 
1323  $points = $this->calculateReachedPoints($active_id, $pass);
1324  $max_points = $this->getMaximumPoints();
1325 
1326  $result->setReachedPercentage(($points/$max_points) * 100);
1327 
1328  return $result;
1329  }
1330 
1339  public function getAvailableAnswerOptions($index = null)
1340  {
1341  $error_text_array = explode(' ', $this->errortext);
1342 
1343  if($index !== null)
1344  {
1345  if(array_key_exists($index, $error_text_array))
1346  {
1347  return $error_text_array[$index];
1348  }
1349  return null;
1350  }
1351  else
1352  {
1353  return $error_text_array;
1354  }
1355  }
1356 
1362  private function getErrorTokenHtml($item, $class, $useLinkTags)
1363  {
1364  if($useLinkTags)
1365  {
1366  return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>';
1367  }
1368 
1369  return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</span>';
1370  }
1371 }
getErrorText()
Get error text.
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
getId()
Gets the id of the assQuestion object.
static prepareFormOutput($a_str, $a_strip=false)
prepares string output for html forms public
$error
Definition: Error.php:17
getErrorData()
Get error data.
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
$worksheet
reworkWorkingData($active_id, $pass, $obligationsAnswered, $authorized)
{}
Class iQuestionCondition.
setTextSize($a_value)
Set text size in percent.
toJSON()
Returns a JSON representation of the question.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
$result
getTextSize()
Set text size in percent.
createErrorTextExport($selections=null)
Abstract basic class which is to be extended by the concrete assessment question type classes...
loadFromDb($question_id)
Loads the object from the database.
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
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...
__get($value)
Object getter.
createErrorTextOutput($selections=null, $graphicalOutput=false, $correct_solution=false, $use_link_tags=true)
ensureNonNegativePoints($points)
saveToDb($original_id="")
Saves a the object to the database.
isTokenSelected($counter, array $selection)
getErrorsFromText($a_text="")
setId($id=-1)
Sets the id of the assQuestion object.
setErrorText($a_value)
Set error text.
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second. ...
Add rich text string
The name of the decorator.
copyObject($target_questionpool_id, $title="")
Copies an object.
getQuestionType()
Returns the question type of the question.
Class for error text answers.
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
setNrOfTries($a_nr_of_tries)
fromXML(&$item, &$questionpool_id, &$tst_id, &$tst_object, &$question_counter, &$import_mapping)
Creates a question from a QTI file.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
Class for error text question exports.
Resolve range
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...
__construct( $title='', $comment='', $author='', $owner=-1, $question='')
assErorText constructor
getObjId()
Get the object id of the container object.
$total
Definition: Utf8Test.php:87
getShuffle()
Gets the shuffle flag.
Base Exception for all Exceptions relating to Modules/Test.
getPointsWrong()
Get wrong points.
$counter
isComplete()
Returns true, if a single choice question is complete for use.
static _getLogLanguage()
retrieve the log language for assessment logging
setAuthor($author="")
Sets the authors name of the assQuestion object.
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
getPointsForSelectedPositions($positions)
$mobs
Class ilUserQuestionResult.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
getBestSolution($active_id, $pass)
Returns the best solution for a given pass of a participant.
setPointsWrong($a_value)
Set wrong points.
saveWorkingData($active_id, $pass=NULL, $authorized=true)
Saves the learners input of the question to the database.
getOperators($expression)
Get all available operations for a specific question.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
Class for error text questions.
Interface ilObjAnswerScoringAdjustable.
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates the object.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
getQuestion()
Gets the question string of the question object.
getExpressionTypes()
Get all available expression types for a specific question.
$ilUser
Definition: imgupload.php:18
savePreviewData(ilAssQuestionPreviewSession $previewSession)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
Create styles array
The data for the language used.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
setPoints($a_points)
Sets the maximum available points for the question.
saveQuestionDataToDb($original_id="")
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
{}
$errors
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
setQuestion($question="")
Sets the question string of the question object.
Interface ilObjQuestionScoringAdjustable.
removeCurrentSolution($active_id, $pass, $authorized=true)
getBestSelection($withPositivePointsOnly=true)
global $ilDB
setOriginalId($original_id)
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass...
$text
__set($key, $value)
Object setter.
getSolutionValues($active_id, $pass=NULL, $authorized=true)
Loads solutions of a given user from the database an returns it.
getTitle()
Gets the title string of the assQuestion object.
calculateReachedPoints($active_id, $pass=NULL, $authorizedSolution=true, $returndetails=FALSE)
Returns the points, a learner has reached answering the question.
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
addErrorData($text_wrong, $text_correct, $points)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
setTitle($title="")
Sets the title string of the assQuestion object.
setObjId($obj_id=0)
Set the object id of the container object.
flushErrorData()
Flush error data.
setComment($comment="")
Sets the comment string of the assQuestion object.
getAnswerTableName()
Returns the name of the answer table in the database.
$_POST["username"]
getErrorTokenHtml($item, $class, $useLinkTags)
Class for error text question imports.
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.