1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/**
5 * Class ilTestScoring
6 *
7 * This class holds a mechanism to get the scoring for
8 * - a test,
9 * - a user in a test,
10 * - a pass in a users passes in a test, or
11 * - a question in a pass in a users passes in a test.
12 *
13 * Warning:
14 * Please use carefully, this is one of the classes that may cause funny spikes on your servers load graph on large
15 * datasets in the test.
16 *
17 * @author		Maximilian Becker <mbecker@databay.de>
18 *
19 * @version		$Id$
20 *
21 * @ingroup 	ModulesTest
22 */
23class ilTestScoring
24{
25    /** @var ilObjTest $test */
26    protected $test;
27
28    /** @var bool $preserve_manual_scores */
29    protected $preserve_manual_scores;
30
31    private $recalculatedPasses;
32
33    /**
34     * @var int
35     */
36    protected $questionId = 0;
37
38    public function __construct(ilObjTest $test)
39    {
40        $this->test = $test;
41        $this->preserve_manual_scores = false;
42
43        $this->recalculatedPasses = array();
44    }
45
46    /**
47     * @param boolean $preserve_manual_scores
48     */
49    public function setPreserveManualScores($preserve_manual_scores)
50    {
51        $this->preserve_manual_scores = $preserve_manual_scores;
52    }
53
54    /**
55     * @return boolean
56     */
57    public function getPreserveManualScores()
58    {
59        return $this->preserve_manual_scores;
60    }
61
62    /**
63     * @return int
64     */
65    public function getQuestionId()
66    {
67        return $this->questionId;
68    }
69
70    /**
71     * @param int $questionId
72     */
73    public function setQuestionId(int $questionId)
74    {
75        $this->questionId = $questionId;
76    }
77
78    public function recalculateSolutions()
79    {
80        $participants = $this->test->getCompleteEvaluationData(false)->getParticipants();
81        if (is_array($participants)) {
82            require_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
83            foreach ($participants as $active_id => $userdata) {
84                if (is_object($userdata) && is_array($userdata->getPasses())) {
85                    $this->recalculatePasses($userdata, $active_id);
86                }
87                assQuestion::_updateTestResultCache($active_id);
88            }
89        }
90    }
91
92    /**
93     * Updates passed status of the Test
94     *
95     * @param $active_id
96     * @param $pass
97     */
98    public function recalculateSolution($active_id, $pass)
99    {
100        $user_data = $this
101            ->test
102            ->getCompleteEvaluationData(false)
103            ->getParticipant($active_id)
104            ->getPass($pass);
105
106        $this->recalculatePass($user_data, $active_id, $pass);
107        assQuestion::_updateTestResultCache($active_id);
108    }
109
110    /**
111     * @param $userdata
112     * @param $active_id
113     */
114    public function recalculatePasses($userdata, $active_id)
115    {
116        $passes = $userdata->getPasses();
117        foreach ($passes as $pass => $passdata) {
118            if (is_object($passdata)) {
119                $this->recalculatePass($passdata, $active_id, $pass);
120                $this->addRecalculatedPassByActive($active_id, $pass);
121            }
122        }
123    }
124
125    /**
126     * @param $passdata
127     * @param $active_id
128     * @param $pass
129     */
130    public function recalculatePass($passdata, $active_id, $pass)
131    {
132        $questions = $passdata->getAnsweredQuestions();
133        if (is_array($questions)) {
134            foreach ($questions as $questiondata) {
135                if ($this->getQuestionId() && $this->getQuestionId() != $questiondata['id']) {
136                    continue;
137                }
138
139                $question_gui = $this->test->createQuestionGUI("", $questiondata['id']);
140                $this->recalculateQuestionScore($question_gui, $active_id, $pass, $questiondata);
141            }
142        }
143    }
144
145    /**
146     * @param $question_gui
147     * @param $active_id
148     * @param $pass
149     * @param $questiondata
150     */
151    public function recalculateQuestionScore($question_gui, $active_id, $pass, $questiondata)
152    {
153        /** @var assQuestion $question_gui */
154        if (is_object($question_gui)) {
155            $reached = $question_gui->object->calculateReachedPoints($active_id, $pass);
156            $actual_reached = $question_gui->object->adjustReachedPointsByScoringOptions($reached, $active_id, $pass);
157
158            if ($this->preserve_manual_scores == true && $questiondata['manual'] == '1') {
159                // Do we need processing here?
160            } else {
161                assQuestion::setForcePassResultUpdateEnabled(true);
162
163                assQuestion::_setReachedPoints(
164                    $active_id,
165                    $questiondata['id'],
166                    $actual_reached,
167                    $question_gui->object->getMaximumPoints(),
168                    $pass,
169                    false,
170                    true
171                );
172
173                assQuestion::setForcePassResultUpdateEnabled(false);
174            }
175        }
176    }
177
178    /**
179     * @return string HTML with the best solution output.
180     */
181    public function calculateBestSolutionForTest()
182    {
183        $solution = '';
184        foreach ($this->test->getAllQuestions() as $question) {
185            /** @var AssQuestionGUI $question_gui */
186            $question_gui = $this->test->createQuestionGUI("", $question['question_id']);
187            $solution .= $question_gui->getSolutionOutput(0, null, true, true, false, false, true, false);
188        }
189
190        return $solution;
191    }
192
193    public function resetRecalculatedPassesByActives()
194    {
195        $this->recalculatedPasses = array();
196    }
197
198    public function getRecalculatedPassesByActives()
199    {
200        return $this->recalculatedPasses;
201    }
202
203    public function addRecalculatedPassByActive($activeId, $pass)
204    {
205        if (!is_array($this->recalculatedPasses[$activeId])) {
206            $this->recalculatedPasses[$activeId] = array();
207        }
208
209        $this->recalculatedPasses[$activeId][] = $pass;
210    }
211
212    public function removeAllQuestionResults($questionId)
213    {
214        global $DIC; /* @var ILIAS\DI\Container $DIC */
215
216        $query = "DELETE FROM tst_test_result WHERE question_fi = %s";
217        $DIC->database()->manipulateF($query, array('integer'), array($questionId));
218    }
219
220    public function updatePassAndTestResults($activeIds)
221    {
222        global $DIC; /* @var ILIAS\DI\Container $DIC */
223
224        foreach ($activeIds as $activeId) {
225            $passSelector = new ilTestPassesSelector($DIC->database(), $this->test);
226            $passSelector->setActiveId($activeId);
227
228            foreach ($passSelector->getExistingPasses() as $pass) {
229                assQuestion::_updateTestPassResults($activeId, $pass, $this->test->areObligationsEnabled());
230            }
231
232            assQuestion::_updateTestResultCache($activeId);
233        }
234    }
235
236    /**
237     * @return int
238     */
239    public function getNumManualScorings()
240    {
241        global $DIC; /* @var ILIAS\DI\Container $DIC */
242
243        $query = "
244			SELECT COUNT(*) num_manual_scorings
245			FROM tst_test_result tres
246
247			INNER JOIN tst_active tact
248			ON tact.active_id = tres.active_fi
249			AND tact.test_fi = %s
250
251			WHERE tres.manual = 1
252		";
253
254        $types = array('integer');
255        $values = array($this->test->getTestId());
256
257        if ($this->getQuestionId()) {
258            $query .= "
259				AND tres.question_fi = %s
260			";
261
262            $types[] = 'integer';
263            $values[] = $this->getQuestionId();
264        }
265
266        $res = $DIC->database()->queryF($query, $types, $values);
267
268        while ($row = $DIC->database()->fetchAssoc($res)) {
269            return (int) $row['num_manual_scorings'];
270        }
271
272        return 0;
273    }
274}
275