ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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  return $this->getPointsForSelectedPositions($previewSession->getParticipantsSolution());
373  }
374 
383  public function saveWorkingData($active_id, $pass = NULL, $authorized = true)
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, $authorized);
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, $authorized);
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 
492  public function getRTETextWithMediaObjects()
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, $use_link_tags = true)
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 = ( $this->isTokenSelected($counter, $selections) ?
714  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
715  );
716 
717  $errorobject = $this->errordata[$errorcounter];
718  if (is_object($errorobject) )
719  {
720  $item = strlen($errorobject->text_correct) ? $errorobject->text_correct : '&nbsp;';
721  }
722  $errorcounter++;
723  $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
724  $counter++;
725  continue;
726  }
727 
728  $group_selected = true;
729  if($graphicalOutput)
730  {
731  $start_idx = $passage_start_idx;
732  foreach($items_in_passage as $tmp_idx => $tmp_item)
733  {
734  if(!$this->isTokenSelected($start_idx, $selections))
735  {
736  $group_selected = false;
737  break;
738  }
739 
740  ++$start_idx;
741  }
742  if($group_selected)
743  {
744  if(!$this->isTokenSelected($counter, $selections))
745  {
746  $group_selected = false;
747  }
748  }
749  }
750 
751  $item_stack = array();
752  $start_idx = $passage_start_idx;
753  foreach($items_in_passage as $tmp_idx => $tmp_item)
754  {
755  $class = ( $this->isTokenSelected($counter, $selections) ?
756  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
757  );
758  $item_stack[] = $this->getErrorTokenHtml($tmp_item, $class, $use_link_tags) . $img;
759  $start_idx++;
760  }
761  $class = ( $this->isTokenSelected($counter, $selections) ?
762  "ilc_qetitem_ErrorTextSelected" : "ilc_qetitem_ErrorTextItem"
763  );
764  if($graphicalOutput)
765  {
766  if ($group_selected)
767  {
768  $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
769  }
770  else
771  {
772  $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
773  }
774  }
775 
776  $item_stack[] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
777  $item_stack = trim(implode(" ", $item_stack));
778  $item_stack = strlen($item_stack) ? $item_stack : '&nbsp;';
779 
780  if($graphicalOutput)
781  {
782  $items[$idx] = '<span class="selGroup">'.$item_stack.'</span>';
783  }
784  else
785  {
786  $items[$idx] = $item_stack;
787  }
788 
789  $counter++;
790  continue;
791  }
792 
793  // Errors markes with #, group errors (()) are handled above
794  $class = 'ilc_qetitem_ErrorTextItem';
795  $img = '';
796  if($this->isTokenSelected($counter, $selections))
797  {
798  $class = "ilc_qetitem_ErrorTextSelected";
799  if($graphicalOutput)
800  {
801  if ($this->getPointsForSelectedPositions(array($counter)) > 0)
802  {
803  $img = ' <img src="' . ilUtil::getImagePath("icon_ok.svg") . '" alt="' . $this->lng->txt("answer_is_right") . '" title="' . $this->lng->txt("answer_is_right") . '" /> ';
804  }
805  else
806  {
807  $img = ' <img src="' . ilUtil::getImagePath("icon_not_ok.svg") . '" alt="' . $this->lng->txt("answer_is_wrong") . '" title="' . $this->lng->txt("answer_is_wrong") . '" /> ';
808  }
809  }
810  }
811 
812  $items[$idx] = $this->getErrorTokenHtml($item, $class, $use_link_tags) . $img;
813  $counter++;
814  }
815  $textarray[$textidx] = '<p>' . implode(" ", $items) . '</p>';
816  }
817 
818  return implode("\n", $textarray);
819  }
820 
821  protected function isTokenSelected($counter, array $selection)
822  {
823  foreach($selection as $data)
824  {
825  if( !is_array($data) )
826  {
827  if($counter == $data)
828  {
829  return true;
830  }
831  }
832  elseif( in_array($counter, $data) )
833  {
834  return true;
835  }
836  }
837 
838  return false;
839  }
840 
841  public function createErrorTextExport($selections = null)
842  {
843  $counter = 0;
844  $errorcounter = 0;
845  include_once "./Services/Utilities/classes/class.ilStr.php";
846  if (!is_array($selections)) $selections = array();
847  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
848  foreach ($textarray as $textidx => $text)
849  {
850  $items = preg_split("/\s+/", $text);
851  foreach ($items as $idx => $item)
852  {
853  if (($posHash = strpos($item, '#')) === 0
854  || ($posOpeningBrackets = strpos($item, '((')) === 0
855  || ($posClosingBrackets = strpos($item, '))')) !== false) {
856  /* (Word|Passage)-Marking delimiter found. */
857 
858  if ($posHash !== false)
859  $item = ilStr::substr($item, 1, ilStr::strlen($item) - 1);
860  elseif ($posOpeningBrackets !== false) {
861  $item = ilStr::substr($item, 2, ilStr::strlen($item) - 2);
862 
863  /* Sometimes a closing bracket group needs
864  to be removed as well. */
865  if (strpos($item, '))') !== false)
866  $item = ilStr::substr($item, 0, ilStr::strlen($item) - 2);
867  }
868  else{
869  $appendComma = "";
870  if ($item{$posClosingBrackets+2} == ',')
871  $appendComma = ",";
872 
873  $item = ilStr::substr($item, 0, $posClosingBrackets) . $appendComma;
874  }
875  }
876 
877  $word = "";
878  if (in_array($counter, $selections))
879  {
880  $word .= '#';
881  }
882  $word .= ilUtil::prepareFormOutput($item);
883  if (in_array($counter, $selections))
884  {
885  $word .= '#';
886  }
887  $items[$idx] = $word;
888  $counter++;
889  }
890  $textarray[$textidx] = join($items, " ");
891  }
892  return join($textarray, "\n");
893  }
894 
895  public function getBestSelection($withPositivePointsOnly = true)
896  {
897  $passages = array();
898  $words = array();
899  $counter = 0;
900  $errorcounter = 0;
901  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
902  foreach ($textarray as $textidx => $text)
903  {
904  $items = preg_split("/\s+/", $text);
905  $inPassage = false;
906  foreach ($items as $word)
907  {
908  $points = $this->getPointsWrong();
909  $isErrorItem = false;
910  if (strpos($word, '#') === 0)
911  {
912  /* Word selection detected */
913  $errorobject = $this->errordata[$errorcounter];
914  if (is_object($errorobject))
915  {
916  $points = $errorobject->points;
917  $isErrorItem = true;
918  }
919  $errorcounter++;
920  }
921  elseif (($posOpeningBracket = strpos($word, '((')) === 0
922  || ($posClosingBracket = strpos($word, '))')) !== false
923  || $inPassage)
924  {
925  /* Passage selection detected */
926 
927  if ($posOpeningBracket !== false)
928  {
929  $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
930  $inPassage = true;
931  }
932  elseif ($posClosingBracket !== false)
933  {
934  $inPassage = false;
935  $cur_pidx = count($passages) - 1;
936  $passages[$cur_pidx]['end_pos'] = $counter;
937 
938  $errorobject = $this->errordata[$errorcounter];
939  if (is_object($errorobject))
940  {
941  $passages[$cur_pidx]['score'] = $errorobject->points;
942  $passages[$cur_pidx]['isError'] = true;
943  }
944 
945  $errorcounter++;
946  }
947 
948  $cur_pidx = count($passages) - 1;
949  $passages[$cur_pidx]['cnt_words']++;
950  $points = 0;
951  }
952 
953  $words[$counter] = array("word" => $word, "points" => $points, "isError" => $isErrorItem);
954  $counter++;
955  }
956  }
957 
958  $selections = array();
959  foreach ($passages as $cnt => $pdata)
960  {
961  if (!$withPositivePointsOnly && $pdata['isError'] || $withPositivePointsOnly && $pdata['score'] > 0)
962  {
963  $indexes = range($pdata['begin_pos'], $pdata['end_pos']);
964  $selections[$pdata['begin_pos']] = $indexes;
965  }
966  }
967 
968  foreach ($words as $idx => $word)
969  {
970  if (!$withPositivePointsOnly && $word['isError'] || $withPositivePointsOnly && $word['points'] > 0)
971  {
972  $selections[$idx] = array($idx);
973  }
974  }
975 
976  ksort($selections);
977 
978  $selections = array_values($selections);
979 
980  return $selections;
981  }
982 
983  protected function getPointsForSelectedPositions($positions)
984  {
985  $passages = array();
986  $words = array();
987  $counter = 0;
988  $errorcounter = 0;
989  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
990  foreach ($textarray as $textidx => $text)
991  {
992  $items = preg_split("/\s+/", $text);
993  $inPassage = false;
994  foreach ($items as $word)
995  {
996  $points = $this->getPointsWrong();
997  if (strpos($word, '#') === 0)
998  {
999  /* Word selection detected */
1000  $errorobject = $this->errordata[$errorcounter];
1001  if (is_object($errorobject))
1002  {
1003  $points = $errorobject->points;
1004  }
1005  $errorcounter++;
1006  }
1007  elseif (($posOpeningBracket = strpos($word, '((')) === 0
1008  || ($posClosingBracket = strpos($word, '))')) !== false
1009  || $inPassage)
1010  {
1011  /* Passage selection detected */
1012 
1013  if ($posOpeningBracket !== false)
1014  {
1015  $passages[] = array('begin_pos' => $counter, 'cnt_words' => 0);
1016  $inPassage = true;
1017  }
1018  elseif ($posClosingBracket !== false)
1019  {
1020  $inPassage = false;
1021  $cur_pidx = count($passages) - 1;
1022  $passages[$cur_pidx]['end_pos'] = $counter;
1023 
1024  $errorobject = $this->errordata[$errorcounter];
1025  if (is_object($errorobject))
1026  {
1027  $passages[$cur_pidx]['score'] = $errorobject->points;
1028  }
1029  $errorcounter++;
1030  }
1031 
1032  $cur_pidx = count($passages) - 1;
1033  $passages[$cur_pidx]['cnt_words']++;
1034  $points = 0;
1035  }
1036 
1037  $words[$counter] = array("word" => $word, "points" => $points);
1038  $counter++;
1039  }
1040  }
1041 
1042  /* Calculate reached points */
1043  $total = 0;
1044  foreach ($positions as $position)
1045  {
1046  /* First iterate through positions
1047  to identify single-word-selections. */
1048 
1049  $total += $words[$position]['points'];
1050  }
1051 
1052  foreach ($passages as $cnt => $p_data)
1053  {
1054  /* Iterate through configured passages to check
1055  wether the entire passage is selected or not.
1056  The total points is incremented by the passage's
1057  score only if the entire passage is selected. */
1058  $isSelected = in_array($p_data['begin_pos'], $positions);
1059 
1060  for ($i = 0; $i < $p_data['cnt_words']; $i++)
1061  {
1062  $current_pos = $p_data['begin_pos'] + $i;
1063  $isSelected = $isSelected && in_array($current_pos, $positions);
1064  }
1065 
1066  $total += $isSelected ? $p_data['score'] : 0;
1067  }
1068 
1069  return $total;
1070  }
1071 
1075  public function flushErrorData()
1076  {
1077  $this->errordata = array();
1078  }
1079 
1080  public function addErrorData($text_wrong, $text_correct, $points)
1081  {
1082  include_once "./Modules/TestQuestionPool/classes/class.assAnswerErrorText.php";
1083  array_push($this->errordata, new assAnswerErrorText($text_wrong, $text_correct, $points));
1084  }
1085 
1091  public function getErrorData()
1092  {
1093  return $this->errordata;
1094  }
1095 
1101  public function getErrorText()
1102  {
1103  return $this->errortext;
1104  }
1105 
1111  public function setErrorText($a_value)
1112  {
1113  $this->errortext = $a_value;
1114  }
1115 
1121  public function getTextSize()
1122  {
1123  return $this->textsize;
1124  }
1125 
1131  public function setTextSize($a_value)
1132  {
1133  // in self-assesment-mode value should always be set (and must not be null)
1134  if($a_value === null)
1135  {
1136  $a_value = 100;
1137  }
1138  $this->textsize = $a_value;
1139  }
1140 
1146  public function getPointsWrong()
1147  {
1148  return $this->points_wrong;
1149  }
1150 
1156  public function setPointsWrong($a_value)
1157  {
1158  $this->points_wrong = $a_value;
1159  }
1160 
1164  public function __get($value)
1165  {
1166  switch ($value)
1167  {
1168  case "errortext":
1169  return $this->getErrorText();
1170  break;
1171  case "textsize":
1172  return $this->getTextSize();
1173  break;
1174  case "points_wrong":
1175  return $this->getPointsWrong();
1176  break;
1177  default:
1178  return parent::__get($value);
1179  break;
1180  }
1181  }
1182 
1186  public function __set($key, $value)
1187  {
1188  switch ($key)
1189  {
1190  case "errortext":
1191  $this->setErrorText($value);
1192  break;
1193  case "textsize":
1194  $this->setTextSize($value);
1195  break;
1196  case "points_wrong":
1197  $this->setPointsWrong($value);
1198  break;
1199  default:
1200  parent::__set($key, $value);
1201  break;
1202  }
1203  }
1204 
1205 
1209  public function toJSON()
1210  {
1211  include_once("./Services/RTE/classes/class.ilRTE.php");
1212  $result = array();
1213  $result['id'] = (int) $this->getId();
1214  $result['type'] = (string) $this->getQuestionType();
1215  $result['title'] = (string) $this->getTitle();
1216  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1217  $result['text'] = (string) ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0);
1218  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1219  $result['shuffle'] = (bool) $this->getShuffle();
1220  $result['feedback'] = array(
1221  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1222  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1223  );
1224 
1225  $answers = array();
1226  foreach ($this->getErrorData() as $idx => $answer_obj)
1227  {
1228  array_push($answers, array(
1229  "answertext_wrong" => (string) $answer_obj->text_wrong,
1230  "answertext_correct" => (string) $answer_obj->text_correct,
1231  "points" => (float)$answer_obj->points,
1232  "order" => (int)$idx+1
1233  ));
1234  }
1235  $result['correct_answers'] = $answers;
1236 
1237  $answers = array();
1238  $textarray = preg_split("/[\n\r]+/", $this->getErrorText());
1239  foreach ($textarray as $textidx => $text)
1240  {
1241  $items = preg_split("/\s+/", trim($text));
1242  foreach ($items as $idx => $item)
1243  {
1244  if(substr($item, 0, 1) == "#")
1245  {
1246  $item = substr($item, 1);
1247 
1248  // #14115 - add position to correct answer
1249  foreach($result["correct_answers"] as $aidx => $answer)
1250  {
1251  if($answer["answertext_wrong"] == $item && !$answer["pos"])
1252  {
1253  $result["correct_answers"][$aidx]["pos"] = $this->getId()."_".$textidx."_".($idx+1);
1254  break;
1255  }
1256  }
1257  }
1258  array_push($answers, array(
1259  "answertext" => (string) ilUtil::prepareFormOutput($item),
1260  "order" => $this->getId()."_".$textidx."_".($idx+1)
1261  ));
1262  }
1263  if($textidx != sizeof($textarray)-1)
1264  {
1265  array_push($answers, array(
1266  "answertext" => "###",
1267  "order" => $this->getId()."_".$textidx."_".($idx+2)
1268  ));
1269  }
1270  }
1271  $result['answers'] = $answers;
1272 
1273  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1274  $result['mobs'] = $mobs;
1275 
1276  return json_encode($result);
1277  }
1278 
1287  public function getOperators($expression)
1288  {
1289  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1291  }
1292 
1297  public function getExpressionTypes()
1298  {
1299  return array(
1304  );
1305  }
1306 
1315  public function getUserQuestionResult($active_id, $pass)
1316  {
1318  global $ilDB;
1319  $result = new ilUserQuestionResult($this, $active_id, $pass);
1320 
1321  $data = $ilDB->queryF(
1322  "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1323  SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1324  )",
1325  array("integer", "integer", "integer","integer", "integer", "integer"),
1326  array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1327  );
1328 
1329  while($row = $ilDB->fetchAssoc($data))
1330  {
1331  $result->addKeyValue($row["value1"], $row["value1"]);
1332  }
1333 
1334  $points = $this->calculateReachedPoints($active_id, $pass);
1335  $max_points = $this->getMaximumPoints();
1336 
1337  $result->setReachedPercentage(($points/$max_points) * 100);
1338 
1339  return $result;
1340  }
1341 
1350  public function getAvailableAnswerOptions($index = null)
1351  {
1352  $error_text_array = explode(' ', $this->errortext);
1353 
1354  if($index !== null)
1355  {
1356  if(array_key_exists($index, $error_text_array))
1357  {
1358  return $error_text_array[$index];
1359  }
1360  return null;
1361  }
1362  else
1363  {
1364  return $error_text_array;
1365  }
1366  }
1367 
1373  private function getErrorTokenHtml($item, $class, $useLinkTags)
1374  {
1375  if($useLinkTags)
1376  {
1377  return '<a class="' . $class . '" href="#">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</a>';
1378  }
1379 
1380  return '<span class="' . $class . '">' . ($item == '&nbsp;' ? $item : ilUtil::prepareFormOutput($item)) . '</span>';
1381  }
1382 }
getErrorText()
Get error text.
getId()
Gets the id of the assQuestion object.
static prepareFormOutput($a_str, $a_strip=false)
prepares string output for html forms public
getErrorData()
Get error data.
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
Class iQuestionCondition.
setTextSize($a_value)
Set text size in percent.
toJSON()
Returns a JSON representation of the question.
$_POST['username']
Definition: cron.php:12
reworkWorkingData($active_id, $pass, $obligationsAnswered)
Reworks the allready saved working data if neccessary.
$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...
_getPass($active_id)
Retrieves the actual pass of a given user for a given test.
loadFromDb($question_id)
Loads the object from the database.
_convert_text($a_text, $a_target="has been removed")
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)
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. ...
copyObject($target_questionpool_id, $title="")
Copies an object.
getQuestionType()
Returns the question type of the question.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true)
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)
_enabledAssessmentLogging()
check wether assessment logging is enabled or not
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.
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.
getShuffle()
Gets the shuffle flag.
Base Exception for all Exceptions relating to Modules/Test.
getPointsWrong()
Get wrong points.
isComplete()
Returns true, if a single choice question is complete for use.
setAuthor($author="")
Sets the authors name of the assQuestion object.
$data
getPointsForSelectedPositions($positions)
$mobs
Class ilUserQuestionResult.
removeCurrentSolution($active_id, $pass, $authorized=true, $ignoredSolutionIds=array())
setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
Creates an Excel worksheet for the detailed cumulated results of this question.
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.
savePreviewData(ilAssQuestionPreviewSession $previewSession)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
_getLogLanguage()
retrieve the log language for assessment logging
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
_getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
setPoints($a_points)
Sets the maximum available points for the question.
saveQuestionDataToDb($original_id="")
$errors
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
global $ilUser
Definition: imgupload.php:15
setQuestion($question="")
Sets the question string of the question object.
Interface ilObjQuestionScoringAdjustable.
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.
logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
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.
getErrorTokenHtml($item, $class, $useLinkTags)
Class for error text question imports.
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.