Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.95% covered (warning)
80.95%
34 / 42
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
QtiItemResponseValidator
80.95% covered (warning)
80.95%
34 / 42
66.67% covered (warning)
66.67%
2 / 3
40.53
0.00% covered (danger)
0.00%
0 / 1
 validate
75.00% covered (warning)
75.00%
24 / 32
0.00% covered (danger)
0.00%
0 / 1
46.02
 getResponseValidation
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getAllowSkip
100.00% covered (success)
100.00%
5 / 5
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) 2024-2025 (original work) Open Assessment Technologies SA;
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoQtiTest\model\Infrastructure;
24
25use oat\taoQtiTest\models\runner\QtiRunnerEmptyResponsesException;
26use qtism\runtime\common\State;
27use qtism\runtime\tests\AssessmentItemSessionException;
28use qtism\runtime\tests\AssessmentTestSession;
29
30class QtiItemResponseValidator
31{
32    /**
33     * @param AssessmentTestSession $testSession
34     * @param State $responses
35     * @throws AssessmentItemSessionException
36     */
37    public function validate(AssessmentTestSession $testSession, State $responses): void
38    {
39        $itemSession       = $testSession->getCurrentAssessmentItemSession();
40        $item              = $itemSession->getAssessmentItem();
41        $outcomeDecls      = $item->getOutcomeDeclarations();
42        $responseDecls     = $item->getResponseDeclarations();
43        $itemConstraints   = $item->getResponseValidityConstraints();
44
45        $allowSkip         = $this->getAllowSkip($testSession);
46        $nullOnly          = $responses->containsNullOnly();
47        $validateRequired  = $this->getResponseValidation($testSession);
48
49        $hasConstraints    = $itemConstraints->count() > 0;
50        $hasOutcomes       = $outcomeDecls->count() > 0;
51        $hasResponses      = $responseDecls->count() > 0;
52        $noDeclOrConst     = !$hasConstraints && !$hasOutcomes && !$hasResponses;
53
54        // 1) Skip allowed & nothing answered & no forced validation
55        if ($allowSkip && $nullOnly && ! $validateRequired) {
56            return;
57        }
58
59        // 2) Skip allowed & nothing answered & validation *on* & no declarations/constraints
60        if (!$allowSkip && $nullOnly && !$validateRequired && !$noDeclOrConst) {
61            throw new QtiRunnerEmptyResponsesException();
62        }
63
64        // 3) Skip *not* allowed & nothing answered & validation *on* & no declarations/constraints
65        if (!$allowSkip && $nullOnly && $validateRequired && $noDeclOrConst) {
66            return;
67        }
68
69        // 4) Skip *not* allowed & nothing answered & validation *on* => error
70        if (!$allowSkip && $nullOnly && $validateRequired) {
71            throw new QtiRunnerEmptyResponsesException();
72        }
73
74        // 5) If skip allowed, nothing answered, validation *on* and item has constraints/outcomes/responses
75        if ($allowSkip && $nullOnly && $validateRequired && $hasConstraints) {
76            throw new QtiRunnerEmptyResponsesException();
77        }
78
79        // 6) If validation is on, always run the constraint check
80        if ($validateRequired) {
81            $itemSession->checkResponseValidityConstraints($responses);
82            return;
83        }
84
85        if (!$validateRequired && $allowSkip && $hasConstraints && $hasOutcomes && $hasResponses) {
86            return;
87        }
88
89        // 7) Force-enable validation when off but item *does* have constraints/outcomes/responses
90        if (!$validateRequired && $hasConstraints && $hasOutcomes && $hasResponses) {
91            $itemSession
92            ->getItemSessionControl()
93            ->setValidateResponses(true);
94
95            $itemSession->checkResponseValidityConstraints($responses);
96        }
97    }
98
99    private function getResponseValidation(AssessmentTestSession $testSession): bool
100    {
101        return $testSession->getRoute()
102            ->current()
103            ->getItemSessionControl()
104            ->getItemSessionControl()
105            ->mustValidateResponses();
106    }
107
108    private function getAllowSkip(AssessmentTestSession $testSession): bool
109    {
110        return $testSession->getRoute()
111            ->current()
112            ->getItemSessionControl()
113            ->getItemSessionControl()
114            ->doesAllowSkipping();
115    }
116}