Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
59.66% |
71 / 119 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
QtiResultXmlFactory | |
59.66% |
71 / 119 |
|
33.33% |
1 / 3 |
49.94 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
createByImportResult | |
72.94% |
62 / 85 |
|
0.00% |
0 / 1 |
19.46 | |||
getCurrentOutcomeVariable | |
21.88% |
7 / 32 |
|
0.00% |
0 / 1 |
16.92 |
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) 2023 (original work) Open Assessment Technologies SA; |
19 | */ |
20 | |
21 | declare(strict_types=1); |
22 | |
23 | namespace oat\taoResultServer\models\Import\Factory; |
24 | |
25 | use common_exception_Error; |
26 | use core_kernel_persistence_Exception; |
27 | use oat\dtms\DateTime; |
28 | use oat\generis\model\data\Ontology; |
29 | use oat\taoDeliveryRdf\model\DeliveryAssemblyService; |
30 | use oat\taoResultServer\models\classes\ResultManagement; |
31 | use oat\taoResultServer\models\classes\ResultServerService; |
32 | use oat\taoResultServer\models\Import\Exception\ImportResultException; |
33 | use oat\taoResultServer\models\Import\Input\ImportResultInput; |
34 | use stdClass; |
35 | use taoResultServer_models_classes_ReadableResultStorage; |
36 | use taoResultServer_models_classes_ResponseVariable; |
37 | use taoResultServer_models_classes_Variable; |
38 | |
39 | class QtiResultXmlFactory |
40 | { |
41 | private Ontology $ontology; |
42 | private ResultServerService $resultServerService; |
43 | |
44 | public function __construct(Ontology $ontology, ResultServerService $resultServerService) |
45 | { |
46 | $this->ontology = $ontology; |
47 | $this->resultServerService = $resultServerService; |
48 | } |
49 | |
50 | /** |
51 | * @throws ImportResultException |
52 | * @throws common_exception_Error |
53 | * @throws core_kernel_persistence_Exception |
54 | */ |
55 | public function createByImportResult(ImportResultInput $input): string |
56 | { |
57 | $resultStorage = $this->resultServerService->getResultStorage(); |
58 | |
59 | if (!$resultStorage instanceof ResultManagement) { |
60 | throw new ImportResultException( |
61 | sprintf( |
62 | 'ResultsStorage must implement %s. Instance of %s provided', |
63 | ResultManagement::class, |
64 | get_class($resultStorage) |
65 | ) |
66 | ); |
67 | } |
68 | |
69 | $itemResults = []; |
70 | $timestamp = (new DateTime())->format(DATE_RFC3339_EXTENDED); |
71 | $deliveryExecutionId = $input->getDeliveryExecutionId(); |
72 | $outcomeVariables = $resultStorage->getDeliveryVariables($deliveryExecutionId); |
73 | |
74 | /** @var taoResultServer_models_classes_ResponseVariable $scoreTotalVariable */ |
75 | $scoreTotalVariable = null; |
76 | $scoreTotal = null; |
77 | $scoreTotalMax = null; |
78 | $updatedScoreTotal = null; |
79 | |
80 | foreach ($outcomeVariables as $id => $outcomeVariable) { |
81 | if (!is_array($outcomeVariable)) { |
82 | continue; |
83 | } |
84 | |
85 | /** @var stdClass $variable */ |
86 | $variable = current($outcomeVariable); |
87 | |
88 | if (!is_object($variable) || !property_exists($variable, 'variable')) { |
89 | continue; |
90 | } |
91 | |
92 | /** @var taoResultServer_models_classes_Variable $variable */ |
93 | $variable = $variable->variable; |
94 | |
95 | if (!$variable instanceof taoResultServer_models_classes_Variable) { |
96 | continue; |
97 | } |
98 | |
99 | if ($variable->getIdentifier() === 'SCORE_TOTAL') { |
100 | $scoreTotal = (float)$variable->getValue(); |
101 | |
102 | $updatedScoreTotal = $scoreTotal; |
103 | $scoreTotalVariable = $variable; |
104 | |
105 | continue; |
106 | } |
107 | |
108 | if ($variable->getIdentifier() === 'SCORE_TOTAL_MAX') { |
109 | $scoreTotalMax = (float)$variable->getValue(); |
110 | } |
111 | } |
112 | |
113 | if ($scoreTotal === null) { |
114 | throw new ImportResultException( |
115 | sprintf( |
116 | 'SCORE_TOTAL is null for delivery execution %s', |
117 | $deliveryExecutionId |
118 | ) |
119 | ); |
120 | } |
121 | |
122 | foreach ($input->getOutcomes() as $itemId => $outcomes) { |
123 | $callItemId = sprintf('%s.%s.0', $deliveryExecutionId, $itemId); |
124 | |
125 | foreach ($outcomes as $outcomeId => $outcomeValue) { |
126 | $variable = $this->getCurrentOutcomeVariable( |
127 | $resultStorage, |
128 | $callItemId, |
129 | $outcomeId, |
130 | $itemId, |
131 | $deliveryExecutionId |
132 | ); |
133 | |
134 | $updatedScoreTotal -= (float)$variable->getValue(); |
135 | $updatedScoreTotal += $outcomeValue; |
136 | |
137 | if ($updatedScoreTotal > $scoreTotalMax) { |
138 | throw new ImportResultException( |
139 | sprintf( |
140 | 'SCORE_TOTAL_MAX cannot be higher than %s, %s provided', |
141 | $scoreTotalMax, |
142 | $updatedScoreTotal |
143 | ) |
144 | ); |
145 | } |
146 | |
147 | $itemResults[] = sprintf( |
148 | '<itemResult identifier="%s" datestamp="%s" sessionStatus="final"> |
149 | <outcomeVariable identifier="%s" cardinality="%s" baseType="%s" %s %s> |
150 | <value>%s</value> |
151 | </outcomeVariable> |
152 | </itemResult>', |
153 | $itemId, |
154 | $timestamp, |
155 | $outcomeId, |
156 | $variable->getCardinality(), |
157 | $variable->getBaseType(), |
158 | $variable->getNormalMaximum() ? sprintf('normalMaximum="%s"', $variable->getNormalMaximum()) : '', |
159 | $variable->getNormalMinimum() ? sprintf('normalMinimum="%s"', $variable->getNormalMinimum()) : '', |
160 | $outcomeValue |
161 | ); |
162 | } |
163 | } |
164 | |
165 | $deliveryId = $resultStorage->getDelivery($deliveryExecutionId); |
166 | $testUri = $this->ontology->getResource($deliveryId) |
167 | ->getOnePropertyValue($this->ontology->getProperty(DeliveryAssemblyService::PROPERTY_ORIGIN)); |
168 | |
169 | // phpcs:disable |
170 | return sprintf( |
171 | '<?xml version="1.0" encoding="UTF-8"?> |
172 | <assessmentResult |
173 | xmlns="http://www.imsglobal.org/xsd/imsqti_result_v2p1" |
174 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
175 | xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_result_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_result_v2p1.xsd"> |
176 | <context/> |
177 | <testResult identifier="%s" datestamp="%s"> |
178 | <outcomeVariable identifier="SCORE_TOTAL" cardinality="%s" baseType="%s"> |
179 | <value>%s</value> |
180 | </outcomeVariable> |
181 | </testResult> |
182 | %s |
183 | </assessmentResult>', |
184 | $testUri, |
185 | $timestamp, |
186 | $scoreTotalVariable->getCardinality(), |
187 | $scoreTotalVariable->getBaseType(), |
188 | $updatedScoreTotal, |
189 | implode('', $itemResults) |
190 | ); |
191 | // phpcs:enable |
192 | } |
193 | |
194 | |
195 | private function getCurrentOutcomeVariable( |
196 | taoResultServer_models_classes_ReadableResultStorage $resultStorage, |
197 | string $callItemId, |
198 | string $outcomeId, |
199 | string $itemId, |
200 | string $deliveryExecutionId |
201 | ): taoResultServer_models_classes_Variable { |
202 | $outcomeVariableVersions = $resultStorage->getVariable($callItemId, $outcomeId); |
203 | |
204 | if (!is_array($outcomeVariableVersions) || empty($outcomeVariableVersions)) { |
205 | throw new ImportResultException( |
206 | sprintf( |
207 | 'Outcome variable %s not found for item %s on delivery execution %s', |
208 | $outcomeId, |
209 | $itemId, |
210 | $deliveryExecutionId |
211 | ) |
212 | ); |
213 | } |
214 | |
215 | $lastOutcomeVariable = (array)end($outcomeVariableVersions); |
216 | |
217 | if (empty($lastOutcomeVariable)) { |
218 | throw new ImportResultException( |
219 | sprintf( |
220 | 'There is no outcome variable %s for %s', |
221 | $outcomeId, |
222 | $callItemId |
223 | ) |
224 | ); |
225 | } |
226 | |
227 | /** @var taoResultServer_models_classes_Variable $variable */ |
228 | $variable = $lastOutcomeVariable['variable'] ?? null; |
229 | |
230 | if (!$variable instanceof taoResultServer_models_classes_Variable) { |
231 | throw new ImportResultException( |
232 | sprintf( |
233 | 'Outcome variable %s is typeof %s, expected instance of %s, for item %s and execution %s', |
234 | $outcomeId, |
235 | get_class($variable), |
236 | taoResultServer_models_classes_Variable::class, |
237 | $itemId, |
238 | $deliveryExecutionId |
239 | ) |
240 | ); |
241 | } |
242 | |
243 | return $variable; |
244 | } |
245 | } |