1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
7require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
8require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
9
10
11class assLongMenu extends assQuestion implements ilObjQuestionScoringAdjustable
12{
13    private $answerType;
14    private $long_menu_text;
15    private $json_structure;
16    private $ilDB;
17    private $specificFeedbackSetting;
18    private $minAutoComplete;
19    private $identical_scoring;
20
21    const ANSWER_TYPE_SELECT_VAL = 0;
22    const ANSWER_TYPE_TEXT_VAL = 1;
23    const GAP_PLACEHOLDER = 'Longmenu';
24    const MIN_LENGTH_AUTOCOMPLETE = 3;
25    const MAX_INPUT_FIELDS = 500;
26
27    /** @var array */
28    private $correct_answers = [];
29
30    /** @var array */
31    private $answers = [];
32
33    public function __construct(
34        $title = "",
35        $comment = "",
36        $author = "",
37        $owner = -1,
38        $question = ""
39    ) {
40        global $DIC;
41        require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php';
42        $this->specificFeedbackSetting = ilAssConfigurableMultiOptionQuestionFeedback::FEEDBACK_SETTING_ALL;
43        $this->minAutoComplete = self::MIN_LENGTH_AUTOCOMPLETE;
44        parent::__construct($title, $comment, $author, $owner, $question);
45        $this->ilDB = $DIC->database();
46        $this->identical_scoring = 1;
47    }
48
49    /**
50     * @return mixed
51     */
52    public function getAnswerType()
53    {
54        return $this->answerType;
55    }
56
57    /**
58     * @param mixed $answerType
59     */
60    public function setAnswerType($answerType)
61    {
62        $this->answerType = $answerType;
63    }
64
65    /**
66     * @return mixed
67     */
68    public function getCorrectAnswers()
69    {
70        return $this->correct_answers;
71    }
72
73
74    public function setCorrectAnswers($correct_answers)
75    {
76        $this->correct_answers = $correct_answers;
77    }
78
79    private function buildFolderName()
80    {
81        return ilUtil::getDataDir() . '/assessment/longMenuQuestion/' . $this->getId() . '/' ;
82    }
83
84    public function getAnswerTableName()
85    {
86        return "qpl_a_lome";
87    }
88
89    private function buildFileName($gap_id)
90    {
91        try {
92            $this->assertDirExists();
93            return $this->buildFolderName() . $gap_id . '.txt';
94        } catch (ilException $e) {
95        }
96    }
97
98    public function setLongMenuTextValue($long_menu_text = "")
99    {
100        $this->long_menu_text = $long_menu_text;
101    }
102
103    public function getLongMenuTextValue()
104    {
105        return $this->long_menu_text;
106    }
107
108    public function setAnswers($answers)
109    {
110        $this->answers = $answers;
111    }
112
113    public function getAnswers()
114    {
115        return $this->answers;
116    }
117
118    /**
119     * @return mixed
120     */
121    public function getJsonStructure()
122    {
123        return $this->json_structure;
124    }
125
126    /**
127     * @param mixed $json_structure
128     */
129    public function setJsonStructure($json_structure)
130    {
131        $this->json_structure = $json_structure;
132    }
133
134    public function setSpecificFeedbackSetting($specificFeedbackSetting)
135    {
136        $this->specificFeedbackSetting = $specificFeedbackSetting;
137    }
138
139    public function getSpecificFeedbackSetting()
140    {
141        return $this->specificFeedbackSetting;
142    }
143
144    public function setMinAutoComplete($minAutoComplete)
145    {
146        $this->minAutoComplete = $minAutoComplete;
147    }
148
149    public function getMinAutoComplete()
150    {
151        return $this->minAutoComplete ? $this->minAutoComplete  : self::MIN_LENGTH_AUTOCOMPLETE;
152    }
153
154    public function isComplete()
155    {
156        if (strlen($this->title)
157            && $this->author
158            && $this->long_menu_text
159            && sizeof($this->answers) > 0
160            && sizeof($this->correct_answers) > 0
161            && $this->getPoints() > 0
162        ) {
163            return true;
164        }
165        return false;
166    }
167
168    public function saveToDb($original_id = "")
169    {
170        $this->saveQuestionDataToDb($original_id);
171        $this->saveAdditionalQuestionDataToDb();
172        $this->saveAnswerSpecificDataToDb();
173        parent::saveToDb($original_id);
174    }
175
176    /**
177     * @param ilPropertyFormGUI|null $form
178     * @return bool
179     */
180    public function checkQuestionCustomPart($form = null)
181    {
182        $hidden_text_files = $this->getAnswers();
183        $correct_answers = $this->getCorrectAnswers();
184        $points = array();
185        if (sizeof($correct_answers) == 0 || sizeof($hidden_text_files) == 0) {
186            return false;
187        }
188        if (sizeof($correct_answers) != sizeof($hidden_text_files)) {
189            return false;
190        }
191        foreach ($correct_answers as $key => $correct_answers_row) {
192            if ($this->correctAnswerDoesNotExistInAnswerOptions($correct_answers_row, $hidden_text_files[$key])) {
193                return false;
194            }
195            if (!is_array($correct_answers_row[0]) || sizeof($correct_answers_row[0]) == 0) {
196                return false;
197            }
198            if ($correct_answers_row[1] > 0) {
199                array_push($points, $correct_answers_row[1]);
200            }
201        }
202        if (sizeof($correct_answers) != sizeof($points)) {
203            return false;
204        }
205
206        foreach ($points as $row) {
207            if ($row <= 0) {
208                return false;
209            }
210        }
211        return true;
212    }
213
214    /**
215     * @param $answers
216     * @param $answer_options
217     * @return bool
218     */
219    private function correctAnswerDoesNotExistInAnswerOptions($answers, $answer_options)
220    {
221        foreach ($answers[0] as $key => $answer) {
222            if (!in_array($answer, $answer_options)) {
223                return true;
224            }
225        }
226        return false;
227    }
228
229
230    /**
231     * Returns the maximum points, a learner can reach answering the question
232     *
233     * @access public
234     * @see $points
235     */
236    public function getMaximumPoints()
237    {
238        $sum = 0;
239        $points = $this->getCorrectAnswers();
240        if ($points) {
241            foreach ($points as $add) {
242                $sum += $add[1];
243            }
244        }
245        return $sum;
246    }
247
248    public function saveAdditionalQuestionDataToDb()
249    {
250        // save additional data
251        $this->ilDB->manipulateF(
252            "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
253            array( "integer" ),
254            array( $this->getId() )
255        );
256        $this->ilDB->manipulateF(
257            "INSERT INTO " . $this->getAdditionalTableName(
258            ) . " (question_fi, long_menu_text, feedback_setting, min_auto_complete, identical_scoring) VALUES (%s, %s, %s, %s, %s)",
259            array( "integer", "text", "integer", "integer", "integer"),
260            array(
261                $this->getId(),
262                $this->getLongMenuTextValue(),
263                (int) $this->getSpecificFeedbackSetting(),
264                (int) $this->getMinAutoComplete(),
265                (int) $this->getIdenticalScoring()
266            )
267        );
268
269        $this->createFileFromArray();
270    }
271
272    public function saveAnswerSpecificDataToDb()
273    {
274        $this->clearAnswerSpecificDataFromDb($this->getId());
275        $type_array = $this->getAnswerType();
276        $points = 0;
277        foreach ($this->getCorrectAnswers() as $gap_number => $gap) {
278            foreach ($gap[0] as $position => $answer) {
279                if ($type_array == null) {
280                    $type = $gap[2];
281                } else {
282                    $type = $type_array[$gap_number];
283                }
284                $this->db->replace(
285                    $this->getAnswerTableName(),
286                    array(
287                                'question_fi' => array('integer', (int) $this->getId()),
288                                'gap_number' => array('integer', (int) $gap_number),
289                                'position' => array('integer', (int) $position)
290                        ),
291                    array(
292                                'answer_text' => array('text', $answer),
293                                'points' => array('float', $gap[1]),
294                                'type' => array('integer', (int) $type)
295                        )
296                );
297            }
298            $points += $gap[1];
299        }
300        $this->setPoints($points);
301    }
302
303    private function createFileFromArray()
304    {
305        $array = $this->getAnswers();
306        $this->clearFolder();
307        foreach ($array as $gap => $values) {
308            $file_content = '';
309            if (is_array($values)) {
310                foreach ($values as $key => $value) {
311                    $file_content .= $value . "\n";
312                }
313                $file_content = rtrim($file_content, "\n");
314                $file = fopen($this->buildFileName($gap), "w");
315                fwrite($file, $file_content);
316                fclose($file);
317            }
318        }
319    }
320
321    private function createArrayFromFile()
322    {
323        $files = glob($this->buildFolderName() . '*.txt');
324
325        if ($files === false) {
326            $files = array();
327        }
328
329        $answers = array();
330
331        foreach ($files as $file) {
332            $gap = str_replace('.txt', '', basename($file));
333            $answers[(int) $gap] = explode("\n", file_get_contents($file));
334        }
335        $this->setAnswers($answers);
336        return $answers;
337    }
338
339    private function clearFolder($let_folder_exists = true)
340    {
341        ilUtil::delDir($this->buildFolderName(), $let_folder_exists);
342    }
343
344    private function assertDirExists()
345    {
346        $folder_name = $this->buildFolderName();
347        if (!ilUtil::makeDirParents($folder_name)) {
348            throw new ilException('Cannot create export directory');
349        }
350
351        if (
352            !is_dir($folder_name) ||
353            !is_readable($folder_name) ||
354            !is_writable($folder_name)
355        ) {
356            throw new ilException('Cannot create export directory');
357        }
358    }
359
360    public function loadFromDb($question_id)
361    {
362        $result = $this->ilDB->queryF(
363            "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",
364            array("integer"),
365            array($question_id)
366        );
367        if ($result->numRows() == 1) {
368            $data = $this->ilDB->fetchAssoc($result);
369            $this->setId($question_id);
370            $this->setObjId($data["obj_fi"]);
371            $this->setNrOfTries($data['nr_of_tries']);
372            $this->setTitle($data["title"]);
373            $this->setComment($data["description"]);
374            $this->setOriginalId($data["original_id"]);
375            $this->setAuthor($data["author"]);
376            $this->setPoints($data["points"]);
377            $this->setIdenticalScoring($data["identical_scoring"]);
378            $this->setOwner($data["owner"]);
379            include_once("./Services/RTE/classes/class.ilRTE.php");
380            $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data['question_text'], 1));
381            $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
382            $this->setLongMenuTextValue(ilRTE::_replaceMediaObjectImageSrc($data['long_menu_text'], 1));
383            $this->loadCorrectAnswerData($question_id);
384            $this->setMinAutoComplete($data["min_auto_complete"]);
385            if (isset($data['feedback_setting'])) {
386                $this->setSpecificFeedbackSetting((int) $data['feedback_setting']);
387            }
388
389            try {
390                $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
391            } catch (ilTestQuestionPoolInvalidArgumentException $e) {
392                $this->setLifecycle(ilAssQuestionLifecycle::getDraftInstance());
393            }
394
395            try {
396                $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
397            } catch (ilTestQuestionPoolException $e) {
398            }
399        }
400
401        $this->loadCorrectAnswerData($question_id);
402        $this->createArrayFromFile();
403        parent::loadFromDb($question_id);
404    }
405
406    private function loadCorrectAnswerData($question_id)
407    {
408        $res = $this->db->queryF(
409            "SELECT * FROM {$this->getAnswerTableName()} WHERE question_fi = %s ORDER BY gap_number, position ASC",
410            array('integer'),
411            array($question_id)
412        );
413
414        $correct_answers = array();
415        while ($data = $this->ilDB->fetchAssoc($res)) {
416            $correct_answers[$data['gap_number']][0][$data['position']] = rtrim($data['answer_text']);
417            $correct_answers[$data['gap_number']][1] = $data['points'];
418            $correct_answers[$data['gap_number']][2] = $data['type'];
419        }
420        $this->setJsonStructure(json_encode($correct_answers));
421        $this->setCorrectAnswers($correct_answers);
422    }
423
424    public function getCorrectAnswersForQuestionSolution($question_id)
425    {
426        $correct_answers = array();
427        $res = $this->db->queryF(
428            'SELECT gap_number, answer_text FROM  ' . $this->getAnswerTableName() . ' WHERE question_fi = %s',
429            array('integer'),
430            array($question_id)
431        );
432        while ($data = $this->ilDB->fetchAssoc($res)) {
433            if (array_key_exists($data['gap_number'], $correct_answers)) {
434                $correct_answers[$data['gap_number']] .= ' ' . $this->lng->txt("or") . ' ';
435                $correct_answers[$data['gap_number']] .= rtrim($data['answer_text']);
436            } else {
437                $correct_answers[$data['gap_number']] .= rtrim($data['answer_text']);
438            }
439        }
440        return $correct_answers;
441    }
442
443    private function getCorrectAnswersForGap($question_id, $gap_id)
444    {
445        $correct_answers = array();
446        $res = $this->db->queryF(
447            'SELECT answer_text FROM  ' . $this->getAnswerTableName() . ' WHERE question_fi = %s AND gap_number = %s',
448            array('integer', 'integer'),
449            array($question_id, $gap_id)
450        );
451        while ($data = $this->ilDB->fetchAssoc($res)) {
452            $correct_answers[] = rtrim($data['answer_text']);
453        }
454        return $correct_answers;
455    }
456
457    private function getPointsForGap($question_id, $gap_id)
458    {
459        $points = 0;
460        $res = $this->db->queryF(
461            'SELECT points FROM  ' . $this->getAnswerTableName() . ' WHERE question_fi = %s AND gap_number = %s GROUP BY gap_number, points',
462            array('integer', 'integer'),
463            array($question_id, $gap_id)
464        );
465        while ($data = $this->ilDB->fetchAssoc($res)) {
466            $points = $data['points'];
467        }
468        return $points;
469    }
470
471
472    public function getAnswersObject()
473    {
474        return json_encode($this->createArrayFromFile());
475    }
476
477    public function getCorrectAnswersAsJson()
478    {
479        $this->loadCorrectAnswerData($this->getId());
480        return $this->getJsonStructure();
481    }
482
483    public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
484    {
485        if ($this->id <= 0) {
486            // The question has not been saved. It cannot be duplicated
487            return;
488        }
489
490        // duplicate the question in database
491        $this_id = $this->getId();
492        $thisObjId = $this->getObjId();
493
494        $clone = $this;
495        include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
496        $original_id = assQuestion::_getOriginalId($this->id);
497        $clone->id = -1;
498
499        if ((int) $testObjId > 0) {
500            $clone->setObjId($testObjId);
501        }
502
503        if ($title) {
504            $clone->setTitle($title);
505        }
506
507        if ($author) {
508            $clone->setAuthor($author);
509        }
510        if ($owner) {
511            $clone->setOwner($owner);
512        }
513
514        if ($for_test) {
515            $clone->saveToDb($original_id);
516        } else {
517            $clone->saveToDb();
518        }
519
520        $clone->copyPageOfQuestion($this_id);
521        $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
522        $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
523
524        return $clone->id;
525    }
526
527    public function copyObject($target_questionpool_id, $title = "")
528    {
529        if ($this->id <= 0) {
530            // The question has not been saved. It cannot be duplicated
531            return;
532        }
533        // duplicate the question in database
534        $clone = $this;
535        include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
536        $original_id = assQuestion::_getOriginalId($this->id);
537        $clone->id = -1;
538        $source_questionpool_id = $this->getObjId();
539        $clone->setObjId($target_questionpool_id);
540        if ($title) {
541            $clone->setTitle($title);
542        }
543        $clone->saveToDb();
544
545        $clone->copyPageOfQuestion($original_id);
546        $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
547
548        $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
549
550        return $clone->id;
551    }
552
553    public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
554    {
555        if ($this->id <= 0) {
556            // The question has not been saved. It cannot be duplicated
557            return;
558        }
559
560        include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
561
562        $sourceQuestionId = $this->id;
563        $sourceParentId = $this->getObjId();
564
565        // duplicate the question in database
566        $clone = $this;
567        $clone->id = -1;
568
569        $clone->setObjId($targetParentId);
570
571        if ($targetQuestionTitle) {
572            $clone->setTitle($targetQuestionTitle);
573        }
574
575        $clone->saveToDb();
576        $clone->copyPageOfQuestion($sourceQuestionId);
577        $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
578
579        $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
580
581        return $clone->id;
582    }
583
584
585    /**
586     * Returns the points, a learner has reached answering the question.
587     * The points are calculated from the given answers.
588     *
589     * @param integer $active_id
590     * @param integer $pass
591     * @param boolean $returndetails (deprecated !!)
592     *
593     * @throws ilTestException
594     * @return integer/array $points/$details (array $details is deprecated !!)
595     */
596    public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
597    {
598        if ($returndetails) {
599            throw new ilTestException('return details not implemented for ' . __METHOD__);
600        }
601
602        $found_values = array();
603        if (is_null($pass)) {
604            $pass = $this->getSolutionMaxPass($active_id);
605        }
606        $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
607        while ($data = $this->ilDB->fetchAssoc($result)) {
608            $found_values[(int) $data['value1']] = $data['value2'];
609        }
610
611        $points = $this->calculateReachedPointsForSolution($found_values, $active_id);
612
613        return $points;
614    }
615
616    protected function calculateReachedPointsForSolution($found_values, $active_id = 0)
617    {
618        $points = 0;
619        $solution_values_text = array();
620        foreach ($found_values as $key => $answer) {
621            if ($answer != '') {
622                $correct_answers = $this->getCorrectAnswersForGap($this->id, $key);
623                if (in_array($answer, $correct_answers)) {
624                    $points_gap = $this->getPointsForGap($this->id, $key);
625                    if (!$this->getIdenticalScoring()) {
626                        // check if the same solution text was already entered
627                        if ((in_array($answer, $solution_values_text)) && ($points > 0)) {
628                            $points_gap = 0;
629                        }
630                    }
631                    $points += $points_gap;
632                    array_push($solution_values_text, $answer);
633                }
634            }
635        }
636        return $points;
637    }
638
639    /**
640     * Saves the learners input of the question to the database.
641     *
642     * @access public
643     * @param integer $active_id Active id of the user
644     * @param integer $pass Test pass
645     * @return boolean $status
646     */
647    public function saveWorkingData($active_id, $pass = null, $authorized = true)
648    {
649        if (is_null($pass)) {
650            include_once "./Modules/Test/classes/class.ilObjTest.php";
651            $pass = ilObjTest::_getPass($active_id);
652        }
653
654        $entered_values = 0;
655
656        $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
657            $this->removeCurrentSolution($active_id, $pass, $authorized);
658
659            foreach ($this->getSolutionSubmit() as $val1 => $val2) {
660                $value = ilUtil::stripSlashes($val2, false);
661                if (strlen($value)) {
662                    $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized);
663                    $entered_values++;
664                }
665            }
666        });
667
668        if ($entered_values) {
669            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
670            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
671                assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
672            }
673        } else {
674            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
675            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
676                assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
677            }
678        }
679        return true;
680    }
681
682    // fau: testNav - overridden function lookupForExistingSolutions (specific for long menu question: ignore unselected values)
683    /**
684     * Lookup if an authorized or intermediate solution exists
685     * @param 	int 		$activeId
686     * @param 	int 		$pass
687     * @return 	array		['authorized' => bool, 'intermediate' => bool]
688     */
689    public function lookupForExistingSolutions($activeId, $pass)
690    {
691        global $DIC;
692        $ilDB = $DIC['ilDB'];
693
694        $return = array(
695            'authorized' => false,
696            'intermediate' => false
697        );
698
699        $query = "
700			SELECT authorized, COUNT(*) cnt
701			FROM tst_solutions
702			WHERE active_fi = " . $ilDB->quote($activeId, 'integer') . "
703			AND question_fi = " . $ilDB->quote($this->getId(), 'integer') . "
704			AND pass = " . $ilDB->quote($pass, 'integer') . "
705			AND value2 <> '-1'
706		";
707
708        if ($this->getStep() !== null) {
709            $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
710        }
711
712        $query .= "
713			GROUP BY authorized
714		";
715
716        $result = $ilDB->query($query);
717
718        while ($row = $ilDB->fetchAssoc($result)) {
719            if ($row['authorized']) {
720                $return['authorized'] = $row['cnt'] > 0;
721            } else {
722                $return['intermediate'] = $row['cnt'] > 0;
723            }
724        }
725        return $return;
726    }
727    // fau.
728
729
730    public function getSolutionSubmit()
731    {
732        $solutionSubmit = array();
733        $answer = ilUtil::stripSlashesRecursive($_POST['answer']);
734
735        foreach ($answer as $key => $value) {
736            $solutionSubmit[$key] = $value;
737        }
738
739        return $solutionSubmit;
740    }
741
742    protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
743    {
744        if (array_key_exists('answer', $_POST)) {
745            $previewSession->setParticipantsSolution($_POST['answer']);
746        } else {
747            $previewSession->setParticipantsSolution(null);
748        }
749    }
750
751    /**
752     * Returns the question type of the question
753     *
754     * @return integer The question type of the question
755     */
756    public function getQuestionType()
757    {
758        return "assLongMenu";
759    }
760
761    /**
762     * Returns the name of the additional question data table in the database
763     *
764     * @return string The additional table name
765     */
766    public function getAdditionalTableName()
767    {
768        return 'qpl_qst_lome';
769    }
770
771    /**
772     * Collects all text in the question which could contain media objects
773     * which were created with the Rich Text Editor
774     */
775    public function getRTETextWithMediaObjects()
776    {
777        return parent::getRTETextWithMediaObjects() . $this->getLongMenuTextValue();
778    }
779
780    /**
781     * {@inheritdoc}
782     */
783    public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
784    {
785        parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
786
787        $solution = $this->getSolutionValues($active_id, $pass);
788
789        $i = 1;
790        foreach ($this->getCorrectAnswers() as $gap_index => $gap) {
791            $worksheet->setCell($startrow + $i, 0, $this->lng->txt('assLongMenu') . " $i");
792            $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
793            foreach ($solution as $solutionvalue) {
794                if ($gap_index == $solutionvalue["value1"]) {
795                    switch ($gap[2]) {
796                        case self::ANSWER_TYPE_SELECT_VAL:
797                            $value = $solutionvalue["value2"];
798                            if ($value == -1) {
799                                $value = '';
800                            }
801                            $worksheet->setCell($startrow + $i, 1, $value);
802                            break;
803                        case self::ANSWER_TYPE_TEXT_VAL:
804                            $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
805                            break;
806                    }
807                }
808            }
809            $i++;
810        }
811
812        return $startrow + $i + 1;
813    }
814
815    /**
816     * Get the user solution for a question by active_id and the test pass
817     *
818     * @param int $active_id
819     * @param int $pass
820     *
821     * @return ilUserQuestionResult
822     */
823    public function getUserQuestionResult($active_id, $pass)
824    {
825        $result = new ilUserQuestionResult($this, $active_id, $pass);
826
827        $points = $this->calculateReachedPoints($active_id, $pass);
828        $max_points = $this->getMaximumPoints();
829
830        $result->setReachedPercentage(($points / $max_points) * 100);
831
832        return $result;
833    }
834
835    /**
836     * If index is null, the function returns an array with all anwser options
837     * Else it returns the specific answer option
838     *
839     * @param null|int $index
840     *
841     * @return array|ASS_AnswerSimple
842     */
843    public function getAvailableAnswerOptions($index = null)
844    {
845        return $this->createArrayFromFile();
846    }
847
848    public function isShuffleAnswersEnabled()
849    {
850        return false;
851    }
852
853    public function clearAnswerSpecificDataFromDb($question_id)
854    {
855        $this->ilDB->manipulateF(
856            'DELETE FROM ' . $this->getAnswerTableName() . ' WHERE question_fi = %s',
857            array( 'integer' ),
858            array( $question_id )
859        );
860    }
861
862    public function delete($original_id)
863    {
864        parent::delete($original_id);
865        $this->clearFolder(false);
866    }
867
868    /**
869     * @param ilAssSelfAssessmentMigrator $migrator
870     */
871    protected function lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
872    {
873        $this->setLongMenuTextValue($migrator->migrateToLmContent($this->getLongMenuTextValue()));
874    }
875
876    /**
877     * Returns a JSON representation of the question
878     */
879    public function toJSON()
880    {
881        include_once("./Services/RTE/classes/class.ilRTE.php");
882        $result = array();
883        $result['id'] = (int) $this->getId();
884        $result['type'] = (string) $this->getQuestionType();
885        $result['title'] = (string) $this->getTitle();
886        $replaced_quesiton_text = $this->getLongMenuTextValue();
887        $result['question'] = $this->formatSAQuestion($this->getQuestion());
888        $result['lmtext'] = $this->formatSAQuestion($replaced_quesiton_text);
889        $result['nr_of_tries'] = (int) $this->getNrOfTries();
890        $result['shuffle'] = (bool) $this->getShuffle();
891        $result['feedback'] = array(
892            'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
893            'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
894        );
895
896        $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
897        $result['answers'] = $this->getAnswers();
898        $result['correct_answers'] = $this->getCorrectAnswers();
899        $result['mobs'] = $mobs;
900        return json_encode($result);
901    }
902
903    public function getIdenticalScoring()
904    {
905        return ($this->identical_scoring) ? 1 : 0;
906    }
907
908    /**
909     * @param $a_identical_scoring
910     */
911    public function setIdenticalScoring($a_identical_scoring)
912    {
913        $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
914    }
915}
916