Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.89% covered (warning)
68.89%
31 / 45
55.56% covered (warning)
55.56%
5 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimerAdjustmentService
68.89% covered (warning)
68.89%
31 / 45
55.56% covered (warning)
55.56%
5 / 9
38.93
0.00% covered (danger)
0.00%
0 / 1
 increase
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 decrease
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getAdjustedMaxTime
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 getAdjustment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAdjustmentByType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 findMaximumPossibleDecrease
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
4.13
 putAdjustmentToTheMap
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 getTimer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2020 (original work) Open Assessment Technologies SA ;
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoQtiTest\models\runner\time;
24
25use oat\oatbox\service\ConfigurableService;
26use oat\taoQtiTest\models\runner\session\TestSession;
27use oat\taoQtiTest\models\runner\StorageManager;
28use oat\taoTests\models\runner\time\InvalidStorageException;
29use qtism\common\datatypes\QtiDuration;
30use qtism\data\QtiIdentifiable;
31
32class TimerAdjustmentService extends ConfigurableService implements TimerAdjustmentServiceInterface
33{
34    protected const INCREASE = 'increase';
35    protected const DECREASE = 'decrease';
36
37    /** @var TestSession */
38    private $testSession;
39
40    /**
41     * {@inheritDoc}
42     * @throws InvalidStorageException
43     */
44    public function increase(
45        TestSession $testSession,
46        int $seconds,
47        string $type = self::TYPE_TIME_ADJUSTMENT,
48        QtiIdentifiable $source = null
49    ): bool {
50        $this->testSession = $testSession;
51
52        return $this->register(self::INCREASE, $seconds, $type, $source);
53    }
54
55    /**
56     * {@inheritDoc}
57     * @throws InvalidStorageException
58     */
59    public function decrease(
60        TestSession $testSession,
61        int $seconds,
62        string $type = self::TYPE_TIME_ADJUSTMENT,
63        QtiIdentifiable $source = null
64    ): bool {
65        $this->testSession = $testSession;
66
67        $seconds = $this->findMaximumPossibleDecrease($seconds);
68        if ($seconds === 0) {
69            return false;
70        }
71
72        return $this->register(self::DECREASE, $seconds, $type, $source);
73    }
74
75    /**
76     * {@inheritDoc}
77     */
78    public function getAdjustedMaxTime(QtiIdentifiable $source, QtiTimer $timer): ?QtiDuration
79    {
80        $timeLimits = $source->getTimeLimits();
81        if (!$timeLimits || ($maxTime = $timeLimits->getMaxTime()) === null) {
82            return null;
83        }
84
85        $maximumTime = clone $maxTime;
86        if ($timer !== null) {
87            $adjustmentSeconds = $this->getAdjustment($source, $timer);
88            if ($adjustmentSeconds > 0) {
89                $maximumTime->add(new QtiDuration('PT' . $adjustmentSeconds . 'S'));
90            } else {
91                $maximumTime->sub(new QtiDuration('PT' . abs($adjustmentSeconds) . 'S'));
92            }
93        }
94        return $maximumTime;
95    }
96
97    /**
98     * Get adjusted seconds
99     * @param QtiIdentifiable $source
100     * @param QtiTimer $qtiTimer
101     * @return int
102     */
103    public function getAdjustment(QtiIdentifiable $source, QtiTimer $qtiTimer): int
104    {
105        return $qtiTimer->getAdjustmentMap()->get($source->getIdentifier());
106    }
107
108    /**
109     * @inheritDoc
110     */
111    public function getAdjustmentByType(QtiIdentifiable $source, QtiTimer $timer, ?string $adjustmentType = null): int
112    {
113        return $timer->getAdjustmentMap()->getByType($source->getIdentifier(), $adjustmentType);
114    }
115
116    /**
117     * @throws InvalidStorageException
118     */
119    private function register(string $action, int $seconds, string $type, QtiIdentifiable $source = null): bool
120    {
121        if ($source) {
122            $this->putAdjustmentToTheMap($source, $type, $action, $seconds);
123        } else {
124            $this->putAdjustmentToTheMap($this->testSession->getCurrentAssessmentItemRef(), $type, $action, $seconds);
125            $this->putAdjustmentToTheMap($this->testSession->getCurrentAssessmentSection(), $type, $action, $seconds);
126            $this->putAdjustmentToTheMap($this->testSession->getCurrentTestPart(), $type, $action, $seconds);
127            $this->putAdjustmentToTheMap($this->testSession->getAssessmentTest(), $type, $action, $seconds);
128        }
129        $this->getTimer()->save();
130        $this->getServiceLocator()->get(StorageManager::SERVICE_ID)->persist();
131
132        return true;
133    }
134
135    private function findMaximumPossibleDecrease(int $requestedSeconds): int
136    {
137        $minRemaining = PHP_INT_MAX;
138        foreach ($this->testSession->getTimeConstraints() as $tc) {
139            $maximumRemainingTime = $tc->getMaximumRemainingTime();
140            if ($maximumRemainingTime === false) {
141                continue;
142            }
143            $maximumRemainingTime = $maximumRemainingTime->getSeconds(true);
144            $minRemaining = min($minRemaining, $maximumRemainingTime);
145        }
146
147        if ($minRemaining < $requestedSeconds) {
148            return $minRemaining;
149        }
150
151        return $requestedSeconds;
152    }
153
154    private function putAdjustmentToTheMap(QtiIdentifiable $element, string $type, string $action, int $seconds)
155    {
156        if ($element === null || !$element->getTimeLimits() || !$element->getTimeLimits()->hasMaxTime()) {
157            return;
158        }
159
160        if ($action === self::INCREASE) {
161            $this->getTimer()->getAdjustmentMap()->increase($element->getIdentifier(), $type, $seconds);
162        } elseif ($action === self::DECREASE) {
163            $this->getTimer()->getAdjustmentMap()->decrease($element->getIdentifier(), $type, $seconds);
164        }
165    }
166
167    private function getTimer(): QtiTimer
168    {
169        return $this->testSession->getTimer();
170    }
171}