1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4
5/**
6 * @author		Björn Heyser <bheyser@databay.de>
7 * @version		$Id$
8 *
9 * @package     Modules/Test
10 */
11class ilTestPassesSelector
12{
13    protected $db;
14
15    protected $testOBJ;
16
17    private $activeId;
18
19    private $lastFinishedPass = null;
20
21    private $passes = null;
22
23    private $testPassedOnceCache = array();
24
25    public function __construct(ilDBInterface $db, ilObjTest $testOBJ)
26    {
27        $this->db = $db;
28        $this->testOBJ = $testOBJ;
29    }
30
31    public function getActiveId()
32    {
33        return $this->activeId;
34    }
35
36    public function setActiveId($activeId)
37    {
38        $this->activeId = $activeId;
39    }
40
41    public function getLastFinishedPass()
42    {
43        return $this->lastFinishedPass;
44    }
45
46    public function setLastFinishedPass($lastFinishedPass)
47    {
48        $lastFinishedPass = $lastFinishedPass === null ? -1 : $lastFinishedPass;
49        $this->lastFinishedPass = $lastFinishedPass;
50    }
51
52    private function passesLoaded()
53    {
54        return is_array($this->passes);
55    }
56
57    private function ensureLoadedPasses()
58    {
59        if (!$this->passesLoaded()) {
60            $this->loadPasses();
61        }
62    }
63    private function loadPasses()
64    {
65        $query = "
66			SELECT DISTINCT tst_pass_result.* FROM tst_pass_result
67			LEFT JOIN tst_test_result
68			ON tst_pass_result.pass = tst_test_result.pass
69			AND tst_pass_result.active_fi = tst_test_result.active_fi
70			WHERE tst_pass_result.active_fi = %s
71			ORDER BY tst_pass_result.pass
72		";
73
74        $res = $this->db->queryF(
75            $query,
76            array('integer'),
77            array($this->getActiveId())
78        );
79
80        $this->passes = array();
81
82        while ($row = $this->db->fetchAssoc($res)) {
83            $this->passes[$row['pass']] = $row;
84        }
85    }
86
87    private function getLazyLoadedPasses()
88    {
89        $this->ensureLoadedPasses();
90        return $this->passes;
91    }
92
93    public function loadLastFinishedPass()
94    {
95        $query = "
96			SELECT last_finished_pass FROM tst_active WHERE active_id = %s
97		";
98
99        $res = $this->db->queryF(
100            $query,
101            array('integer'),
102            array($this->getActiveId())
103        );
104
105        while ($row = $this->db->fetchAssoc($res)) {
106            $this->setLastFinishedPass($row['last_finished_pass']);
107        }
108    }
109
110    public function getExistingPasses()
111    {
112        return array_keys($this->getLazyLoadedPasses());
113    }
114
115    public function hasExistingPasses()
116    {
117        return (bool) count($this->getExistingPasses());
118    }
119
120    public function getNumExistingPasses()
121    {
122        return count($this->getExistingPasses());
123    }
124
125    public function openPassExists()
126    {
127        return count($this->getExistingPasses()) > count($this->getClosedPasses());
128    }
129
130    public function getClosedPasses()
131    {
132        $existingPasses = $this->getExistingPasses();
133        $closedPasses = $this->fetchClosedPasses($existingPasses);
134
135        return $closedPasses;
136    }
137
138    public function getReportablePasses()
139    {
140        $existingPasses = $this->getExistingPasses();
141
142        $reportablePasses = $this->fetchReportablePasses($existingPasses);
143
144        return $reportablePasses;
145    }
146
147    public function hasReportablePasses()
148    {
149        return (bool) count($this->getReportablePasses());
150    }
151
152    private function fetchReportablePasses($existingPasses)
153    {
154        $lastPass = $this->fetchLastPass($existingPasses);
155
156        $reportablePasses = array();
157
158        foreach ($existingPasses as $pass) {
159            if ($this->isReportablePass($lastPass, $pass)) {
160                $reportablePasses[] = $pass;
161            }
162        }
163
164        return $reportablePasses;
165    }
166
167    private function fetchClosedPasses($existingPasses)
168    {
169        $closedPasses = array();
170
171        foreach ($existingPasses as $pass) {
172            if ($this->isClosedPass($pass)) {
173                $closedPasses[] = $pass;
174            }
175        }
176
177        return $closedPasses;
178    }
179
180    private function fetchLastPass($existingPasses)
181    {
182        $lastPass = null;
183
184        foreach ($existingPasses as $pass) {
185            if ($lastPass === null || $pass > $lastPass) {
186                $lastPass = $pass;
187            }
188        }
189
190        return $lastPass;
191    }
192
193    private function isReportablePass($lastPass, $pass)
194    {
195        switch ($this->testOBJ->getScoreReporting()) {
196            case ilObjTest::SCORE_REPORTING_IMMIDIATLY:
197
198                return true;
199
200            case ilObjTest::SCORE_REPORTING_DATE:
201
202                return $this->isReportingDateReached();
203
204            case ilObjTest::SCORE_REPORTING_FINISHED:
205
206                if ($pass < $lastPass) {
207                    return true;
208                }
209
210                return $this->isClosedPass($pass);
211
212            case ilObjTest::SCORE_REPORTING_AFTER_PASSED:
213
214                if (!$this->hasTestPassedOnce($this->getActiveId())) {
215                    return false;
216                }
217
218                return $this->isClosedPass($pass);
219        }
220
221        return false;
222    }
223
224    private function checkLastFinishedPassInitialised()
225    {
226        if ($this->getLastFinishedPass() === null) {
227            throw new ilTestException('invalid object state: last finished pass was not set!');
228        }
229    }
230
231    private function isClosedPass($pass)
232    {
233        $this->checkLastFinishedPassInitialised();
234
235        if ($pass <= $this->getLastFinishedPass()) {
236            return true;
237        }
238
239        if ($this->isProcessingTimeReached($pass)) {
240            return true;
241        }
242
243        return false;
244    }
245
246    private function isReportingDateReached()
247    {
248        $reg = '/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/';
249        $date = $this->testOBJ->getReportingDate();
250        $matches = null;
251
252        if (!preg_match($reg, $date, $matches)) {
253            return false;
254        }
255
256        $repTS = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
257
258        return time() >= $repTS;
259    }
260
261    private function isProcessingTimeReached($pass)
262    {
263        if (!$this->testOBJ->getEnableProcessingTime()) {
264            return false;
265        }
266
267        $startingTime = $this->testOBJ->getStartingTimeOfUser($this->getActiveId(), $pass);
268
269        if ($startingTime === false) {
270            return false;
271        }
272
273        return $this->testOBJ->isMaxProcessingTimeReached($startingTime, $this->getActiveId());
274    }
275
276    /**
277     * @return int timestamp
278     */
279    public function getLastFinishedPassTimestamp()
280    {
281        if ($this->getLastFinishedPass() === null) {
282            return null;
283        }
284
285        $passes = $this->getLazyLoadedPasses();
286        return $passes[$this->getLastFinishedPass()]['tstamp'];
287    }
288
289    public function hasTestPassedOnce($activeId)
290    {
291        global $DIC; /* @var ILIAS\DI\Container $DIC */
292
293        if (!isset($this->testPassedOnceCache[$activeId])) {
294            $this->testPassedOnceCache[$activeId] = false;
295
296            $res = $DIC->database()->queryF(
297                "SELECT passed_once FROM tst_result_cache WHERE active_fi = %s",
298                array('integer'),
299                array($activeId)
300            );
301
302            while ($row = $DIC->database()->fetchAssoc($res)) {
303                $this->testPassedOnceCache[$activeId] = (bool) $row['passed_once'];
304            }
305        }
306
307        return $this->testPassedOnceCache[$activeId];
308    }
309}
310