Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.69% covered (warning)
82.69%
43 / 52
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
QtiItemResponseRepository
82.69% covered (warning)
82.69%
43 / 52
16.67% covered (danger)
16.67%
1 / 6
22.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 save
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 endItemTimer
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 saveItemState
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 saveItemResponses
88.00% covered (warning)
88.00%
22 / 25
0.00% covered (danger)
0.00%
0 / 1
9.14
 blockEmptyResponse
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
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) 2021 (original work) Open Assessment Technologies SA;
19 *
20 * @author Ricardo Quintanilha <ricardo.quintanilha@taotesting.com>
21 */
22
23declare(strict_types=1);
24
25namespace oat\taoQtiTest\model\Infrastructure;
26
27use oat\tao\model\featureFlag\FeatureFlagChecker;
28use oat\taoQtiTest\model\Domain\Model\ItemResponse;
29use oat\taoQtiTest\model\Domain\Model\ItemResponseRepositoryInterface;
30use oat\taoQtiTest\model\Infrastructure\Validation\ExtraQtiInteractionResponseValidator;
31use oat\taoQtiTest\models\classes\runner\QtiRunnerInvalidResponsesException;
32use oat\taoQtiTest\models\runner\QtiRunnerEmptyResponsesException;
33use oat\taoQtiTest\models\runner\QtiRunnerItemResponseException;
34use oat\taoQtiTest\models\runner\QtiRunnerService;
35use oat\taoQtiTest\models\runner\RunnerServiceContext;
36use qtism\runtime\tests\AssessmentItemSessionException;
37use taoQtiTest_helpers_TestRunnerUtils as TestRunnerUtils;
38
39class QtiItemResponseRepository implements ItemResponseRepositoryInterface
40{
41    /** @var QtiRunnerService */
42    private $runnerService;
43    private FeatureFlagChecker $featureFlagChecker;
44    private QtiItemResponseValidator $itemResponseValidator;
45    private ExtraQtiInteractionResponseValidator $extraQtiInteractionResponseValidator;
46
47    public function __construct(
48        QtiRunnerService $runnerService,
49        FeatureFlagChecker $featureFlagChecker,
50        QtiItemResponseValidator $itemResponseValidator,
51        ExtraQtiInteractionResponseValidator $extraQtiInteractionResponseValidator
52    ) {
53        $this->runnerService = $runnerService;
54        $this->featureFlagChecker = $featureFlagChecker;
55        $this->itemResponseValidator = $itemResponseValidator;
56        $this->extraQtiInteractionResponseValidator = $extraQtiInteractionResponseValidator;
57    }
58
59    public function save(ItemResponse $itemResponse, RunnerServiceContext $serviceContext): void
60    {
61        if ($this->runnerService->isTerminated($serviceContext)) {
62            return;
63        }
64
65        $this->endItemTimer($itemResponse, $serviceContext);
66        $this->saveItemState($itemResponse, $serviceContext);
67        $this->saveItemResponses($itemResponse, $serviceContext);
68    }
69
70    private function endItemTimer(ItemResponse $itemResponse, RunnerServiceContext $serviceContext): void
71    {
72        if ($itemResponse->getDuration() === null) {
73            return;
74        }
75
76        $this->runnerService->endTimer(
77            $serviceContext,
78            $itemResponse->getDuration(),
79            $itemResponse->getTimestamp()
80        );
81    }
82
83    private function saveItemState(ItemResponse $itemResponse, RunnerServiceContext $serviceContext): void
84    {
85        if (
86            empty($itemResponse->getItemIdentifier())
87            || $itemResponse->getState() === null
88        ) {
89            return;
90        }
91
92        $this->runnerService->setItemState(
93            $serviceContext,
94            $itemResponse->getItemIdentifier(),
95            $itemResponse->getState()
96        );
97    }
98
99    private function saveItemResponses(ItemResponse $itemResponse, RunnerServiceContext $serviceContext): void
100    {
101        if (
102            empty($itemResponse->getItemIdentifier())
103            || $itemResponse->getResponse() === null
104        ) {
105            return;
106        }
107
108        $itemDefinition = $this->runnerService->getItemHref($serviceContext, $itemResponse->getItemIdentifier());
109
110        if (empty($itemDefinition)) {
111            return;
112        }
113
114        if (
115            $serviceContext->getCurrentAssessmentItemRef() === false
116            || $serviceContext->getCurrentAssessmentItemRef()->getIdentifier() !== $itemResponse->getItemIdentifier()
117        ) {
118            throw new QtiRunnerItemResponseException(__('Item response identifier does not match current item'));
119        }
120
121        $responses = $this->runnerService->parsesItemResponse(
122            $serviceContext,
123            $itemDefinition,
124            $itemResponse->getResponse()
125        );
126
127        if ($this->featureFlagChecker->isEnabled('FEATURE_FLAG_RESPONSE_VALIDATOR')) {
128            try {
129                $this->itemResponseValidator->validate($serviceContext->getTestSession(), $responses);
130                $this->extraQtiInteractionResponseValidator->validate(
131                    $this->runnerService->getItemData($serviceContext, $itemDefinition),
132                    $responses
133                );
134            } catch (AssessmentItemSessionException | QtiRunnerInvalidResponsesException $e) {
135                throw new QtiRunnerInvalidResponsesException($e->getMessage());
136            }
137        }
138
139        if ($this->blockEmptyResponse($serviceContext, $responses)) {
140            throw new QtiRunnerEmptyResponsesException();
141        }
142
143        $this->runnerService->storeItemResponse($serviceContext, $itemDefinition, $responses);
144    }
145
146    /**
147     * @param RunnerServiceContext $serviceContext
148     * @param mixed $responses
149     * @return bool
150     * @throws \common_Exception
151     * @throws \common_ext_ExtensionException
152     */
153    protected function blockEmptyResponse(RunnerServiceContext $serviceContext, mixed $responses): bool
154    {
155        return $this->runnerService->getTestConfig()->getConfigValue('enableAllowSkipping')
156            && !TestRunnerUtils::doesAllowSkipping($serviceContext->getTestSession())
157            && $this->runnerService->emptyResponse($serviceContext, $responses);
158    }
159}