ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assKprimChoice.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/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
6 require_once 'Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
7 
15 {
17 
19 
20  const ANSWER_TYPE_SINGLE_LINE = 'singleLine';
21  const ANSWER_TYPE_MULTI_LINE = 'multiLine';
22 
23  const OPTION_LABEL_RIGHT_WRONG = 'right_wrong';
24  const OPTION_LABEL_PLUS_MINUS = 'plus_minus';
25  const OPTION_LABEL_APPLICABLE_OR_NOT = 'applicable_or_not';
26  const OPTION_LABEL_ADEQUATE_OR_NOT = 'adequate_or_not';
27  const OPTION_LABEL_CUSTOM = 'customlabel';
28 
29  const DEFAULT_THUMB_SIZE = 150;
30  const THUMB_PREFIX = 'thumb.';
31 
33 
34  private $answerType;
35 
36  private $thumbSize;
37 
39 
40  private $optionLabel;
41 
43 
45 
47 
48  private $answers;
49 
50  public function __construct($title = '', $comment = '', $author = '', $owner = -1, $question = '')
51  {
53 
54  $this->shuffleAnswersEnabled = true;
55  $this->answerType = self::ANSWER_TYPE_SINGLE_LINE;
56  $this->thumbSize = self::DEFAULT_THUMB_SIZE;
57  $this->scorePartialSolutionEnabled = true;
58  $this->optionLabel = self::OPTION_LABEL_RIGHT_WRONG;
59  $this->customTrueOptionLabel = '';
60  $this->customFalseOptionLabel = '';
61 
62  require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php';
64 
65  $this->answers = array();
66 
67  $this->setPoints('');
68  }
69 
70  public function getQuestionType()
71  {
72  return 'assKprimChoice';
73  }
74 
75  public function getAdditionalTableName()
76  {
77  return "qpl_qst_kprim";
78  }
79 
80  public function getAnswerTableName()
81  {
82  return "qpl_a_kprim";
83  }
84 
86  {
87  $this->shuffleAnswersEnabled = $shuffleAnswersEnabled;
88  }
89 
90  public function isShuffleAnswersEnabled()
91  {
93  }
94 
95  public function setAnswerType($answerType)
96  {
97  $this->answerType = $answerType;
98  }
99 
100  public function getAnswerType()
101  {
102  return $this->answerType;
103  }
104 
105  public function setThumbSize($thumbSize)
106  {
107  $this->thumbSize = $thumbSize;
108  }
109 
110  public function getThumbSize()
111  {
112  return $this->thumbSize;
113  }
114 
116  {
117  $this->scorePartialSolutionEnabled = $scorePartialSolutionEnabled;
118  }
119 
121  {
123  }
124 
125  public function setOptionLabel($optionLabel)
126  {
127  $this->optionLabel = $optionLabel;
128  }
129 
130  public function getOptionLabel()
131  {
132  return $this->optionLabel;
133  }
134 
136  {
137  $this->customTrueOptionLabel = $customTrueOptionLabel;
138  }
139 
140  public function getCustomTrueOptionLabel()
141  {
143  }
144 
146  {
147  $this->customFalseOptionLabel = $customFalseOptionLabel;
148  }
149 
150  public function getCustomFalseOptionLabel()
151  {
153  }
154 
156  {
157  $this->specificFeedbackSetting = $specificFeedbackSetting;
158  }
159 
160  public function getSpecificFeedbackSetting()
161  {
163  }
164 
165  public function setAnswers($answers)
166  {
167  $this->answers = $answers;
168  }
169 
170  public function getAnswers()
171  {
172  return $this->answers;
173  }
174 
175  public function getAnswer($position)
176  {
177  foreach($this->getAnswers() as $answer)
178  {
179  if($answer->getPosition() == $position)
180  {
181  return $answer;
182  }
183  }
184 
185  return null;
186  }
187 
188  public function addAnswer(ilAssKprimChoiceAnswer $answer)
189  {
190  $this->answers[] = $answer;
191  }
192 
193  public function loadFromDb($questionId)
194  {
195  $res = $this->db->queryF($this->buildQuestionDataQuery(), array('integer'), array($questionId));
196 
197  while($data = $this->db->fetchAssoc($res))
198  {
199  $this->setId($questionId);
200 
201  $this->setOriginalId($data['original_id']);
202 
203  $this->setObjId($data['obj_fi']);
204 
205  $this->setTitle($data['title']);
206  $this->setNrOfTries($data['nr_of_tries']);
207  $this->setComment($data['description']);
208  $this->setAuthor($data['author']);
209  $this->setPoints($data['points']);
210  $this->setOwner($data['owner']);
211  $this->setEstimatedWorkingTimeFromDurationString($data['working_time']);
212  $this->setLastChange($data['tstamp']);
213  require_once 'Services/RTE/classes/class.ilRTE.php';
214  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data['question_text'], 1));
215 
216  $this->setShuffleAnswersEnabled((bool)$data['shuffle_answers']);
217 
218  if( $this->isValidAnswerType($data['answer_type']) )
219  {
220  $this->setAnswerType($data['answer_type']);
221  }
222 
223  if( is_numeric($data['thumb_size']) )
224  {
225  $this->setThumbSize((int)$data['thumb_size']);
226  }
227 
228  if( $this->isValidOptionLabel($data['opt_label']) )
229  {
230  $this->setOptionLabel($data['opt_label']);
231  }
232 
233  $this->setCustomTrueOptionLabel($data['custom_true']);
234  $this->setCustomFalseOptionLabel($data['custom_false']);
235 
236  if( $data['score_partsol'] !== null )
237  {
238  $this->setScorePartialSolutionEnabled((bool)$data['score_partsol']);
239  }
240 
241  if( isset($data['feedback_setting']) )
242  {
243  $this->setSpecificFeedbackSetting((int)$data['feedback_setting']);
244  }
245 
246  try
247  {
248  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
249  }
251  {
252  }
253  }
254 
255  $this->loadAnswerData($questionId);
256 
257  parent::loadFromDb($questionId);
258  }
259 
260  private function loadAnswerData($questionId)
261  {
262  global $ilDB;
263 
264  $res = $this->db->queryF(
265  "SELECT * FROM {$this->getAnswerTableName()} WHERE question_fi = %s ORDER BY position ASC",
266  array('integer'), array($questionId)
267  );
268 
269  require_once 'Modules/TestQuestionPool/classes/class.ilAssKprimChoiceAnswer.php';
270  require_once 'Services/RTE/classes/class.ilRTE.php';
271 
272  while($data = $ilDB->fetchAssoc($res))
273  {
274  $answer = new ilAssKprimChoiceAnswer();
275 
276  $answer->setPosition($data['position']);
277 
278  $answer->setAnswertext(ilRTE::_replaceMediaObjectImageSrc($data['answertext'], 1));
279 
280  $answer->setImageFile($data['imagefile']);
281  $answer->setThumbPrefix($this->getThumbPrefix());
282  $answer->setImageFsDir($this->getImagePath());
283  $answer->setImageWebDir($this->getImagePathWeb());
284 
285  $answer->setCorrectness($data['correctness']);
286 
287  $this->answers[$answer->getPosition()] = $answer;
288  }
289 
290  for( $i = count($this->answers); $i < self::NUM_REQUIRED_ANSWERS; $i++ )
291  {
292  $answer = new ilAssKprimChoiceAnswer();
293 
294  $answer->setPosition($i);
295 
296  $this->answers[$answer->getPosition()] = $answer;
297  }
298  }
299 
300  public function saveToDb($originalId = '')
301  {
302  $this->saveQuestionDataToDb($originalId);
303 
306 
307  parent::saveToDb($originalId);
308  }
309 
311  {
312  $this->db->replace(
313  $this->getAdditionalTableName(),
314  array(
315  'question_fi' => array('integer', $this->getId())
316  ),
317  array(
318  'shuffle_answers' => array('integer', (int)$this->isShuffleAnswersEnabled()),
319  'answer_type' => array('text', $this->getAnswerType()),
320  'thumb_size' => array('integer', (int)$this->getThumbSize()),
321  'opt_label' => array('text', $this->getOptionLabel()),
322  'custom_true' => array('text', $this->getCustomTrueOptionLabel()),
323  'custom_false' => array('text', $this->getCustomFalseOptionLabel()),
324  'score_partsol' => array('integer', (int)$this->isScorePartialSolutionEnabled()),
325  'feedback_setting' => array('integer', (int)$this->getSpecificFeedbackSetting())
326  )
327  );
328  }
329 
330  public function saveAnswerSpecificDataToDb()
331  {
332  foreach($this->getAnswers() as $answer)
333  {
334  $this->db->replace(
335  $this->getAnswerTableName(),
336  array(
337  'question_fi' => array('integer', (int)$this->getId()),
338  'position' => array('integer', (int)$answer->getPosition())
339  ),
340  array(
341  'answertext' => array('text', $answer->getAnswertext()),
342  'imagefile' => array('text', $answer->getImageFile()),
343  'correctness' => array('integer', (int)$answer->getCorrectness())
344  )
345  );
346  }
347 
348  $this->rebuildThumbnails();
349  }
350 
351  public function isComplete()
352  {
353  foreach( array($this->title, $this->author, $this->question) as $text )
354  {
355  if( !strlen($text) )
356  {
357  return false;
358  }
359  }
360 
361  if( $this->getMaximumPoints() <= 0 )
362  {
363  return false;
364  }
365 
366  foreach( $this->getAnswers() as $answer )
367  {
368  /* @var ilAssKprimChoiceAnswer $answer */
369 
370  if( is_null($answer->getCorrectness()) )
371  {
372  return false;
373  }
374 
375  if( !strlen($answer->getAnswertext()) && !strlen($answer->getImageFile()) )
376  {
377  return false;
378  }
379  }
380 
381  return true;
382  }
383 
392  public function saveWorkingData($active_id, $pass = NULL)
393  {
395  global $ilDB;
396 
397  if (is_null($pass))
398  {
399  include_once "./Modules/Test/classes/class.ilObjTest.php";
400  $pass = ilObjTest::_getPass($active_id);
401  }
402 
403  $entered_values = 0;
404 
405  $this->getProcessLocker()->requestUserSolutionUpdateLock();
406 
407  $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
408  array('integer','integer','integer'),
409  array($active_id, $this->getId(), $pass)
410  );
411  $solutionSubmit = $this->getSolutionSubmit();
412 
413  foreach($solutionSubmit as $answerIndex => $answerValue)
414  {
415  $next_id = $ilDB->nextId('tst_solutions');
416  $ilDB->insert("tst_solutions", array(
417  "solution_id" => array("integer", $next_id),
418  "active_fi" => array("integer", $active_id),
419  "question_fi" => array("integer", $this->getId()),
420  "value1" => array("clob", (int)$answerIndex),
421  "value2" => array("clob", (int)$answerValue),
422  "pass" => array("integer", $pass),
423  "tstamp" => array("integer", time())
424  ));
425  $entered_values++;
426  }
427 
428  $this->getProcessLocker()->releaseUserSolutionUpdateLock();
429 
430  if ($entered_values)
431  {
432  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
434  {
435  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
436  }
437  }
438  else
439  {
440  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
442  {
443  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
444  }
445  }
446 
447  return true;
448  }
449 
458  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered)
459  {
460  // nothing to do
461  }
462 
473  public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
474  {
475  if( $returndetails )
476  {
477  throw new ilTestException('return details not implemented for '.__METHOD__);
478  }
479 
480  global $ilDB;
481 
482  $found_values = array();
483  if (is_null($pass))
484  {
485  $pass = $this->getSolutionMaxPass($active_id);
486  }
487  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
488  array('integer','integer','integer'),
489  array($active_id, $this->getId(), $pass)
490  );
491  while ($data = $ilDB->fetchAssoc($result))
492  {
493  $found_values[(int)$data['value1']] = (int)$data['value2'];
494  }
495 
496  $points = $this->calculateReachedPointsForSolution($found_values, $active_id);
497 
498  return $points;
499  }
500 
501  public function getValidAnswerTypes()
502  {
503  return array(self::ANSWER_TYPE_SINGLE_LINE, self::ANSWER_TYPE_MULTI_LINE);
504  }
505 
507  {
508  $validTypes = $this->getValidAnswerTypes();
509  return in_array($answerType, $validTypes);
510  }
511 
513  {
515  }
516 
522  {
523  return array(
524  self::ANSWER_TYPE_SINGLE_LINE => $lng->txt('answers_singleline'),
525  self::ANSWER_TYPE_MULTI_LINE => $lng->txt('answers_multiline')
526  );
527  }
528 
529  public function getValidOptionLabels()
530  {
531  return array(
532  self::OPTION_LABEL_RIGHT_WRONG,
533  self::OPTION_LABEL_PLUS_MINUS,
534  self::OPTION_LABEL_APPLICABLE_OR_NOT,
535  self::OPTION_LABEL_ADEQUATE_OR_NOT,
536  self::OPTION_LABEL_CUSTOM
537  );
538  }
539 
541  {
542  return array(
543  self::OPTION_LABEL_RIGHT_WRONG => $lng->txt('option_label_right_wrong'),
544  self::OPTION_LABEL_PLUS_MINUS => $lng->txt('option_label_plus_minus'),
545  self::OPTION_LABEL_APPLICABLE_OR_NOT => $lng->txt('option_label_applicable_or_not'),
546  self::OPTION_LABEL_ADEQUATE_OR_NOT => $lng->txt('option_label_adequate_or_not'),
547  self::OPTION_LABEL_CUSTOM => $lng->txt('option_label_custom')
548  );
549  }
550 
552  {
553  $validLabels = $this->getValidOptionLabels();
554  return in_array($optionLabel, $validLabels);
555  }
556 
558  {
559  switch($optionLabel)
560  {
561  case self::OPTION_LABEL_RIGHT_WRONG:
562  return $lng->txt('option_label_right');
563 
564  case self::OPTION_LABEL_PLUS_MINUS:
565  return $lng->txt('option_label_plus');
566 
567  case self::OPTION_LABEL_APPLICABLE_OR_NOT:
568  return $lng->txt('option_label_applicable');
569 
570  case self::OPTION_LABEL_ADEQUATE_OR_NOT:
571  return $lng->txt('option_label_adequate');
572 
573  case self::OPTION_LABEL_CUSTOM:
574  return $this->getCustomTrueOptionLabel();
575  }
576  }
577 
579  {
580  switch($optionLabel)
581  {
582  case self::OPTION_LABEL_RIGHT_WRONG:
583  return $lng->txt('option_label_wrong');
584 
585  case self::OPTION_LABEL_PLUS_MINUS:
586  return $lng->txt('option_label_minus');
587 
588  case self::OPTION_LABEL_APPLICABLE_OR_NOT:
589  return $lng->txt('option_label_not_applicable');
590 
591  case self::OPTION_LABEL_ADEQUATE_OR_NOT:
592  return $lng->txt('option_label_not_adequate');
593 
594  case self::OPTION_LABEL_CUSTOM:
595  return $this->getCustomFalseOptionLabel();
596  }
597  }
598 
600  {
601  return sprintf(
602  $lng->txt('kprim_instruction_text'),
605  );
606  }
607 
608  public function isCustomOptionLabel($labelValue)
609  {
610  return $labelValue == self::OPTION_LABEL_CUSTOM;
611  }
612 
613  public function getThumbPrefix()
614  {
615  return self::THUMB_PREFIX;
616  }
617 
618  public function rebuildThumbnails()
619  {
620  if( $this->isSingleLineAnswerType($this->getAnswerType()) && $this->getThumbSize() )
621  {
622  foreach ($this->getAnswers() as $answer)
623  {
624  if (strlen($answer->getImageFile()))
625  {
626  $this->generateThumbForFile($answer->getImageFsDir(), $answer->getImageFile());
627  }
628  }
629  }
630  }
631 
632  protected function generateThumbForFile($path, $file)
633  {
634  $filename = $path . $file;
635  if (@file_exists($filename))
636  {
637  $thumbpath = $path . $this->getThumbPrefix() . $file;
638  $path_info = @pathinfo($filename);
639  $ext = "";
640  switch (strtoupper($path_info['extension']))
641  {
642  case 'PNG':
643  $ext = 'PNG';
644  break;
645  case 'GIF':
646  $ext = 'GIF';
647  break;
648  default:
649  $ext = 'JPEG';
650  break;
651  }
652  ilUtil::convertImage($filename, $thumbpath, $ext, $this->getThumbSize());
653  }
654  }
655 
656  public function handleFileUploads($answers, $files)
657  {
658  foreach($answers as $answer)
659  {
660  /* @var ilAssKprimChoiceAnswer $answer */
661 
662  if( !isset($files[$answer->getPosition()]) )
663  {
664  continue;
665  }
666 
667  $this->handleFileUpload($answer, $files[$answer->getPosition()]);
668  }
669  }
670 
671  private function handleFileUpload(ilAssKprimChoiceAnswer $answer, $fileData)
672  {
673  $imagePath = $this->getImagePath();
674 
675  if( !file_exists($imagePath) )
676  {
677  ilUtil::makeDirParents($imagePath);
678  }
679 
680  $filename = $this->createNewImageFileName($fileData['name'], true);
681 
682  $answer->setImageFsDir($imagePath);
683  $answer->setImageFile($filename);
684 
685  if( !ilUtil::moveUploadedFile($fileData['tmp_name'], $fileData['name'], $answer->getImageFsPath()) )
686  {
687  return 2;
688  }
689 
690  return 0;
691  }
692 
693  public function removeAnswerImage($position)
694  {
695  $answer = $this->getAnswer($position);
696 
697  if( file_exists($answer->getImageFsPath()) )
698  {
699  unlink($answer->getImageFsPath());
700  }
701 
702  if( file_exists($answer->getThumbFsPath()) )
703  {
704  unlink($answer->getThumbFsPath());
705  }
706 
707  $answer->setImageFile(null);
708  }
709 
710  protected function getSolutionSubmit()
711  {
712  $solutionSubmit = array();
713  foreach($_POST as $key => $value)
714  {
715  $matches = null;
716 
717  if(preg_match("/^kprim_choice_result_(\d+)/", $key, $matches))
718  {
719  if(strlen($value))
720  {
721  $solutionSubmit[$matches[1]] = $value;
722  }
723  }
724  }
725  return $solutionSubmit;
726  }
727 
728  protected function calculateReachedPointsForSolution($found_values, $active_id = 0)
729  {
730  $numCorrect = 0;
731 
732  foreach($this->getAnswers() as $key => $answer)
733  {
734  if( !isset($found_values[$answer->getPosition()]) )
735  {
736  continue;
737  }
738 
739  if( $found_values[$answer->getPosition()] == $answer->getCorrectness() )
740  {
741  $numCorrect++;
742  }
743  }
744 
745  if( $numCorrect >= self::NUM_REQUIRED_ANSWERS )
746  {
747  $points = $this->getPoints();
748  }
749  elseif( $this->isScorePartialSolutionEnabled() && $numCorrect >= self::PARTIAL_SCORING_NUM_CORRECT_ANSWERS )
750  {
751  $points = $this->getPoints() / 2;
752  }
753  else
754  {
755  $points = 0;
756  }
757 
758  if($active_id)
759  {
760  include_once "./Modules/Test/classes/class.ilObjTest.php";
761  $mc_scoring = ilObjTest::_getMCScoring($active_id);
762  if(($mc_scoring == 0) && (count($found_values) == 0))
763  {
764  $points = 0;
765  }
766  }
767  return $points;
768  }
769 
770  public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
771  {
772  if ($this->id <= 0)
773  {
774  // The question has not been saved. It cannot be duplicated
775  return;
776  }
777  // duplicate the question in database
778  $this_id = $this->getId();
779  $thisObjId = $this->getObjId();
780 
781  $clone = $this;
782  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
784  $clone->id = -1;
785 
786  if( (int)$testObjId > 0 )
787  {
788  $clone->setObjId($testObjId);
789  }
790 
791  if ($title)
792  {
793  $clone->setTitle($title);
794  }
795 
796  if ($author)
797  {
798  $clone->setAuthor($author);
799  }
800  if ($owner)
801  {
802  $clone->setOwner($owner);
803  }
804 
805  if ($for_test)
806  {
807  $clone->saveToDb($original_id);
808  }
809  else
810  {
811  $clone->saveToDb();
812  }
813 
814  // copy question page content
815  $clone->copyPageOfQuestion($this_id);
816  // copy XHTML media objects
817  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
818  // duplicate the images
819  $clone->cloneAnswerImages($this_id, $thisObjId, $clone->getId(), $clone->getObjId());
820 
821  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
822 
823  return $clone->id;
824  }
825 
826  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
827  {
828  if ($this->id <= 0)
829  {
830  // The question has not been saved. It cannot be duplicated
831  return;
832  }
833 
834  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
835 
836  $sourceQuestionId = $this->id;
837  $sourceParentId = $this->getObjId();
838 
839  // duplicate the question in database
840  $clone = $this;
841  $clone->id = -1;
842 
843  $clone->setObjId($targetParentId);
844 
845  if ($targetQuestionTitle)
846  {
847  $clone->setTitle($targetQuestionTitle);
848  }
849 
850  $clone->saveToDb();
851  // copy question page content
852  $clone->copyPageOfQuestion($sourceQuestionId);
853  // copy XHTML media objects
854  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
855  // duplicate the image
856  $clone->cloneAnswerImages($sourceQuestionId, $sourceParentId, $clone->getId(), $clone->getObjId());
857 
858  $clone->onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $clone->getId());
859 
860  return $clone->id;
861  }
862 
866  public function copyObject($target_questionpool_id, $title = "")
867  {
868  if ($this->id <= 0)
869  {
870  // The question has not been saved. It cannot be duplicated
871  return;
872  }
873  // duplicate the question in database
874  $clone = $this;
875  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
877  $clone->id = -1;
878  $source_questionpool_id = $this->getObjId();
879  $clone->setObjId($target_questionpool_id);
880  if ($title)
881  {
882  $clone->setTitle($title);
883  }
884  $clone->saveToDb();
885  // copy question page content
886  $clone->copyPageOfQuestion($original_id);
887  // copy XHTML media objects
888  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
889  // duplicate the image
890  $clone->cloneAnswerImages($original_id, $source_questionpool_id, $clone->getId(), $clone->getObjId());
891 
892  $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
893 
894  return $clone->id;
895  }
896 
897  protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
898  {
899  parent::beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId);
900 
901  $question = self::_instanciateQuestion($origQuestionId);
902 
903  foreach($question->getAnswers() as $answer)
904  {
905  $question->removeAnswerImage($answer->getPosition());
906  }
907  }
908 
909  protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
910  {
911  parent::afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId);
912 
913  $this->cloneAnswerImages($dupQuestionId, $dupParentObjId, $origQuestionId, $origParentObjId);
914  }
915 
916  protected function cloneAnswerImages($sourceQuestionId, $sourceParentId, $targetQuestionId, $targetParentId)
917  {
918  global $ilLog;
919 
920  $sourcePath = $this->buildImagePath($sourceQuestionId, $sourceParentId);
921  $targetPath = $this->buildImagePath($targetQuestionId, $targetParentId);
922 
923  foreach($this->getAnswers() as $answer)
924  {
925  $filename = $answer->getImageFile();
926 
927  if (strlen($filename))
928  {
929  if (!file_exists($targetPath))
930  {
931  ilUtil::makeDirParents($targetPath);
932  }
933 
934  if (!@copy($sourcePath.$filename, $targetPath.$filename))
935  {
936  $ilLog->write("image could not be duplicated!!!!", $ilLog->ERROR);
937  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
938  }
939 
940  if (@file_exists($sourcePath.$this->getThumbPrefix().$filename))
941  {
942  if (!@copy($sourcePath.$this->getThumbPrefix().$filename, $targetPath.$this->getThumbPrefix().$filename))
943  {
944  $ilLog->write("image thumbnail could not be duplicated!!!!", $ilLog->ERROR);
945  $ilLog->write("object: " . print_r($this, TRUE), $ilLog->ERROR);
946  }
947  }
948  }
949  }
950  }
951 
952  protected function getRTETextWithMediaObjects()
953  {
954  $combinedText = parent::getRTETextWithMediaObjects();
955 
956  foreach($this->getAnswers() as $answer)
957  {
958  $combinedText .= $answer->getAnswertext();
959  }
960 
961  return $combinedText;
962  }
963 
967  public function toJSON()
968  {
969  $this->lng->loadLanguageModule('assessment');
970 
971  require_once './Services/RTE/classes/class.ilRTE.php';
972  $result = array();
973  $result['id'] = (int) $this->getId();
974  $result['type'] = (string) $this->getQuestionType();
975  $result['title'] = (string) $this->getTitle();
976  $result['question'] = $this->formatSAQuestion($this->getQuestion());
977  $result['instruction'] = $this->getInstructionTextTranslation(
978  $this->lng, $this->getOptionLabel()
979  );
980  $result['nr_of_tries'] = (int) $this->getNrOfTries();
981  $result['shuffle'] = (bool) $this->isShuffleAnswersEnabled();
982  $result['feedback'] = array(
983  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
984  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
985  );
986 
987  $result['trueOptionLabel'] = $this->getTrueOptionLabelTranslation($this->lng, $this->getOptionLabel());
988  $result['falseOptionLabel'] = $this->getFalseOptionLabelTranslation($this->lng, $this->getOptionLabel());
989 
990  $result['num_allowed_failures'] = $this->getNumAllowedFailures();
991 
992  $answers = array();
993  $has_image = false;
994 
995  foreach( $this->getAnswers() as $key => $answer )
996  {
997  if( strlen((string)$answer->getImageFile()) )
998  {
999  $has_image = true;
1000  }
1001 
1002  $answers[] = array(
1003  'answertext' => (string) $this->formatSAQuestion($answer->getAnswertext()),
1004  'correctness' => (bool) $answer->getCorrectness(),
1005  'order' => (int)$answer->getPosition(),
1006  'image' => (string)$answer->getImageFile(),
1007  'feedback' => ilRTE::_replaceMediaObjectImageSrc(
1008  $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), $key), 0
1009  )
1010  );
1011  }
1012 
1013  $result['answers'] = $answers;
1014 
1015  if($has_image)
1016  {
1017  $result['path'] = $this->getImagePathWeb();
1018  $result['thumb'] = $this->getThumbSize();
1019  }
1020 
1021  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1022  $result['mobs'] = $mobs;
1023 
1024  return json_encode($result);
1025  }
1026 
1027  private function getNumAllowedFailures()
1028  {
1029  if( $this->isScorePartialSolutionEnabled() )
1030  {
1031  return self::NUM_REQUIRED_ANSWERS - self::PARTIAL_SCORING_NUM_CORRECT_ANSWERS;
1032  }
1033 
1034  return 0;
1035  }
1036 
1037  public static function isObligationPossible($questionId)
1038  {
1039  return true;
1040  }
1041 
1042  public function isAnswered($active_id, $pass = null)
1043  {
1044  $numExistingSolutionRecords = assQuestion::getNumExistingSolutionRecords($active_id, $pass, $this->getId());
1045 
1046  return $numExistingSolutionRecords >= 4;
1047  }
1048 
1052  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
1053  {
1054  require_once 'Services/Excel/classes/class.ilExcelUtils.php';
1055 
1056  $solution = $this->getSolutionValues($active_id, $pass);
1057 
1058  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
1059  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
1060  $i = 1;
1061  foreach($this->getAnswers() as $id => $answer)
1062  {
1063  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($answer->getAnswertext()), $format_bold);
1064  $correctness = FALSE;
1065  foreach($solution as $solutionvalue)
1066  {
1067  if($id == $solutionvalue['value1'])
1068  {
1069  $correctness = $solutionvalue['value2'];
1070  break;
1071  }
1072  }
1073  $worksheet->write($startrow + $i, 1, $correctness);
1074  $i++;
1075  }
1076  return $startrow + $i + 1;
1077  }
1078 
1079  public function moveAnswerDown($position)
1080  {
1081  if( $position < 0 || $position >= (self::NUM_REQUIRED_ANSWERS - 1) )
1082  {
1083  return false;
1084  }
1085 
1086  for($i = 0, $max = count($this->answers); $i < $max; $i++)
1087  {
1088  if( $i == $position )
1089  {
1090  $movingAnswer = $this->answers[$i];
1091  $targetAnswer = $this->answers[ $i + 1 ];
1092 
1093  $movingAnswer->setPosition( $position + 1 );
1094  $targetAnswer->setPosition( $position );
1095 
1096  $this->answers[ $i + 1 ] = $movingAnswer;
1097  $this->answers[$i] = $targetAnswer;
1098  }
1099  }
1100  }
1101 
1102  public function moveAnswerUp($position)
1103  {
1104  if( $position <= 0 || $position > (self::NUM_REQUIRED_ANSWERS - 1) )
1105  {
1106  return false;
1107  }
1108 
1109  for($i = 0, $max = count($this->answers); $i < $max; $i++)
1110  {
1111  if( $i == $position )
1112  {
1113  $movingAnswer = $this->answers[$i];
1114  $targetAnswer = $this->answers[ $i - 1 ];
1115 
1116  $movingAnswer->setPosition( $position - 1 );
1117  $targetAnswer->setPosition( $position );
1118 
1119  $this->answers[ $i - 1 ] = $movingAnswer;
1120  $this->answers[$i] = $targetAnswer;
1121  }
1122  }
1123 
1124  return true;
1125  }
1126 }