Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
89.47% |
153 / 171 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
| ResultImporter | |
89.47% |
153 / 171 |
|
63.64% |
7 / 11 |
34.27 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| importByResultInput | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
2 | |||
| updateTestVariables | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
| updateItemResponseVariables | |
96.15% |
25 / 26 |
|
0.00% |
0 / 1 |
4 | |||
| updateItemOutcomeVariables | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
3 | |||
| getTestScoreVariables | |
73.08% |
19 / 26 |
|
0.00% |
0 / 1 |
7.96 | |||
| getItemVariable | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
5 | |||
| getVariable | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
5.93 | |||
| createCallItemId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getTestUri | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| getResultStorage | |
30.00% |
3 / 10 |
|
0.00% |
0 / 1 |
3.37 | |||
| 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\Service; |
| 24 | |
| 25 | use common_exception_Error; |
| 26 | use core_kernel_persistence_Exception; |
| 27 | use oat\generis\model\data\Ontology; |
| 28 | use oat\taoDeliveryRdf\model\DeliveryAssemblyService; |
| 29 | use oat\taoOutcomeRds\model\AbstractRdsResultStorage; |
| 30 | use oat\taoResultServer\models\classes\ResultServerService; |
| 31 | use oat\taoResultServer\models\Import\Exception\ImportResultException; |
| 32 | use oat\taoResultServer\models\Import\Input\ImportResultInput; |
| 33 | use taoResultServer_models_classes_ResponseVariable; |
| 34 | use taoResultServer_models_classes_Variable; |
| 35 | use Throwable; |
| 36 | |
| 37 | class ResultImporter |
| 38 | { |
| 39 | private Ontology $ontology; |
| 40 | private ResultServerService $resultServerService; |
| 41 | |
| 42 | public function __construct(Ontology $ontology, ResultServerService $resultServerService) |
| 43 | { |
| 44 | $this->ontology = $ontology; |
| 45 | $this->resultServerService = $resultServerService; |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * @param ImportResultInput $input |
| 50 | * @return void |
| 51 | * @throws core_kernel_persistence_Exception|Throwable|common_exception_Error|ImportResultException |
| 52 | */ |
| 53 | public function importByResultInput(ImportResultInput $input): void |
| 54 | { |
| 55 | $resultStorage = $this->getResultStorage(); |
| 56 | $deliveryExecutionUri = $input->getDeliveryExecutionId(); |
| 57 | $testUri = $this->getTestUri($resultStorage, $deliveryExecutionUri); |
| 58 | $testScoreVariables = $this->getTestScoreVariables($resultStorage, $deliveryExecutionUri); |
| 59 | |
| 60 | $resultStorage->getPersistence()->transactional( |
| 61 | function () use ($resultStorage, $input, $testUri, $deliveryExecutionUri, $testScoreVariables): void { |
| 62 | $this->updateItemResponseVariables($resultStorage, $input, $testUri); |
| 63 | |
| 64 | $totalScoreCalculatedByItemOutcomes = $this->updateItemOutcomeVariables( |
| 65 | $resultStorage, |
| 66 | $input, |
| 67 | $testUri, |
| 68 | $testScoreVariables['scoreTotal'] ?? 0 |
| 69 | ); |
| 70 | |
| 71 | if (null === $testScoreVariables) { |
| 72 | return; |
| 73 | } |
| 74 | |
| 75 | $this->updateTestVariables( |
| 76 | $resultStorage, |
| 77 | $testScoreVariables['scoreTotalVariable'], |
| 78 | $testScoreVariables['scoreTotalVariableId'], |
| 79 | $deliveryExecutionUri, |
| 80 | $testUri, |
| 81 | $totalScoreCalculatedByItemOutcomes, |
| 82 | $input->hasOutcomes() |
| 83 | ); |
| 84 | } |
| 85 | ); |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * @throws ImportResultException |
| 90 | */ |
| 91 | private function updateTestVariables( |
| 92 | AbstractRdsResultStorage $resultStorage, |
| 93 | taoResultServer_models_classes_Variable $scoreTotalVariable, |
| 94 | int $scoreTotalVariableId, |
| 95 | string $deliveryExecutionUri, |
| 96 | string $testUri, |
| 97 | float $updatedScoreTotal, |
| 98 | bool $hasOutcomes |
| 99 | ): void { |
| 100 | if ($hasOutcomes) { |
| 101 | $scoreTotalVariable->setEpoch(microtime()); |
| 102 | } |
| 103 | $scoreTotalVariable->setValue($updatedScoreTotal); |
| 104 | |
| 105 | $resultStorage->replaceTestVariables( |
| 106 | $deliveryExecutionUri, |
| 107 | $testUri, |
| 108 | $deliveryExecutionUri, |
| 109 | [ |
| 110 | $scoreTotalVariableId => $scoreTotalVariable |
| 111 | ] |
| 112 | ); |
| 113 | } |
| 114 | |
| 115 | private function updateItemResponseVariables( |
| 116 | AbstractRdsResultStorage $resultStorage, |
| 117 | ImportResultInput $input, |
| 118 | string $testUri |
| 119 | ): void { |
| 120 | $deliveryExecutionUri = $input->getDeliveryExecutionId(); |
| 121 | |
| 122 | foreach ($input->getResponses() as $itemId => $responses) { |
| 123 | $callItemId = $this->createCallItemId($deliveryExecutionUri, $itemId); |
| 124 | $itemVariables = []; |
| 125 | |
| 126 | foreach ($responses as $responseId => $responseValue) { |
| 127 | if (!array_key_exists('correctResponse', $responseValue)) { |
| 128 | continue; |
| 129 | } |
| 130 | |
| 131 | $responseVariable = $this->getItemVariable( |
| 132 | $resultStorage, |
| 133 | $deliveryExecutionUri, |
| 134 | $itemId, |
| 135 | $callItemId, |
| 136 | $responseId |
| 137 | ); |
| 138 | |
| 139 | /** @var taoResultServer_models_classes_ResponseVariable $variable */ |
| 140 | $variable = $responseVariable['variable']; |
| 141 | $variableId = $responseVariable['variableId']; |
| 142 | $itemUri = $responseVariable['itemUri']; |
| 143 | |
| 144 | $variable->setCorrectResponse(boolval($responseValue['correctResponse'])); |
| 145 | |
| 146 | $itemVariables[$variableId] = $variable; |
| 147 | } |
| 148 | |
| 149 | $resultStorage->replaceItemVariables( |
| 150 | $deliveryExecutionUri, |
| 151 | $testUri, |
| 152 | $itemUri, |
| 153 | $callItemId, |
| 154 | $itemVariables |
| 155 | ); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | private function updateItemOutcomeVariables( |
| 160 | AbstractRdsResultStorage $resultStorage, |
| 161 | ImportResultInput $input, |
| 162 | string $testUri, |
| 163 | float $scoreTotal |
| 164 | ): float { |
| 165 | $deliveryExecutionUri = $input->getDeliveryExecutionId(); |
| 166 | |
| 167 | foreach ($input->getOutcomes() as $itemId => $outcomes) { |
| 168 | $itemUri = null; |
| 169 | $updateOutcomeVariables = []; |
| 170 | $callItemId = $this->createCallItemId($deliveryExecutionUri, $itemId); |
| 171 | |
| 172 | foreach ($outcomes as $outcomeId => $outcomeValue) { |
| 173 | $outcomeVariable = $this->getItemVariable( |
| 174 | $resultStorage, |
| 175 | $deliveryExecutionUri, |
| 176 | $itemId, |
| 177 | $callItemId, |
| 178 | $outcomeId |
| 179 | ); |
| 180 | |
| 181 | /** @var taoResultServer_models_classes_Variable $variable */ |
| 182 | $variable = $outcomeVariable['variable']; |
| 183 | $itemUri = $outcomeVariable['itemUri']; |
| 184 | $variableId = $outcomeVariable['variableId']; |
| 185 | |
| 186 | $scoreTotal -= (float)$variable->getValue(); |
| 187 | $scoreTotal += $outcomeValue; |
| 188 | |
| 189 | $variable->setValue($outcomeValue); |
| 190 | $variable->setExternallyGraded(true); |
| 191 | |
| 192 | $updateOutcomeVariables[$variableId] = $variable; |
| 193 | } |
| 194 | |
| 195 | $resultStorage->replaceItemVariables( |
| 196 | $deliveryExecutionUri, |
| 197 | $testUri, |
| 198 | $itemUri, |
| 199 | $callItemId, |
| 200 | $updateOutcomeVariables |
| 201 | ); |
| 202 | } |
| 203 | |
| 204 | return $scoreTotal; |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * @throws ImportResultException |
| 209 | */ |
| 210 | private function getTestScoreVariables( |
| 211 | AbstractRdsResultStorage $resultStorage, |
| 212 | string $deliveryExecutionUri |
| 213 | ): ?array { |
| 214 | foreach ($resultStorage->getDeliveryVariables($deliveryExecutionUri) as $id => $outcomeVariable) { |
| 215 | $variable = $this->getVariable($outcomeVariable); |
| 216 | |
| 217 | if ($variable === null) { |
| 218 | continue; |
| 219 | } |
| 220 | |
| 221 | if ($variable->getIdentifier() === 'SCORE_TOTAL') { |
| 222 | $scoreTotalVariableId = $id; |
| 223 | $scoreTotalVariable = $variable; |
| 224 | $scoreTotal = (float)$variable->getValue(); |
| 225 | |
| 226 | continue; |
| 227 | } |
| 228 | |
| 229 | if ($variable->getIdentifier() === 'SCORE_TOTAL_MAX') { |
| 230 | $scoreTotalMax = (float)$variable->getValue(); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | if (!isset($scoreTotalVariable)) { |
| 235 | return null; |
| 236 | } |
| 237 | |
| 238 | if (!isset($scoreTotal, $scoreTotalVariableId, $scoreTotalMax)) { |
| 239 | throw new ImportResultException( |
| 240 | sprintf( |
| 241 | 'SCORE_TOTAL is null for delivery execution %s', |
| 242 | $deliveryExecutionUri |
| 243 | ) |
| 244 | ); |
| 245 | } |
| 246 | |
| 247 | return [ |
| 248 | 'scoreTotalVariableId' => $scoreTotalVariableId, |
| 249 | 'scoreTotalVariable' => $scoreTotalVariable, |
| 250 | 'scoreTotal' => $scoreTotal, |
| 251 | 'scoreTotalMax' => $scoreTotalMax, |
| 252 | ]; |
| 253 | } |
| 254 | |
| 255 | /** |
| 256 | * @throws ImportResultException |
| 257 | */ |
| 258 | private function getItemVariable( |
| 259 | AbstractRdsResultStorage $resultStorage, |
| 260 | string $deliveryExecutionUri, |
| 261 | string $itemId, |
| 262 | string $callItemId, |
| 263 | string $variableIdentifier |
| 264 | ): array { |
| 265 | $variableVersions = $resultStorage->getVariable($callItemId, $variableIdentifier); |
| 266 | $lastVariable = is_array($variableVersions) ? (array)end($variableVersions) : []; |
| 267 | |
| 268 | if (empty($variableVersions)) { |
| 269 | throw new ImportResultException( |
| 270 | sprintf( |
| 271 | 'Variable %s not found for item %s on delivery execution %s', |
| 272 | $variableIdentifier, |
| 273 | $itemId, |
| 274 | $deliveryExecutionUri |
| 275 | ) |
| 276 | ); |
| 277 | } |
| 278 | |
| 279 | /** @var taoResultServer_models_classes_Variable $variable */ |
| 280 | $variable = $lastVariable['variable'] ?? null; |
| 281 | |
| 282 | if (!$variable instanceof taoResultServer_models_classes_Variable) { |
| 283 | throw new ImportResultException( |
| 284 | sprintf( |
| 285 | 'Variable %s is typeof %s, expected instance of %s, for item %s and execution %s', |
| 286 | $variableIdentifier, |
| 287 | is_object($variable) ? get_class($variable) : gettype($variable), |
| 288 | taoResultServer_models_classes_Variable::class, |
| 289 | $itemId, |
| 290 | $deliveryExecutionUri |
| 291 | ) |
| 292 | ); |
| 293 | } |
| 294 | |
| 295 | return [ |
| 296 | 'itemUri' => $lastVariable['item'] ?? null, |
| 297 | 'variableId' => key($variableVersions), |
| 298 | 'variable' => $variable, |
| 299 | ]; |
| 300 | } |
| 301 | |
| 302 | /** |
| 303 | * @param array|mixed $outcomeVariable |
| 304 | */ |
| 305 | private function getVariable($outcomeVariable): ?taoResultServer_models_classes_Variable |
| 306 | { |
| 307 | if (!is_array($outcomeVariable)) { |
| 308 | return null; |
| 309 | } |
| 310 | |
| 311 | $variable = current($outcomeVariable); |
| 312 | |
| 313 | if (!is_object($variable) || !property_exists($variable, 'variable')) { |
| 314 | return null; |
| 315 | } |
| 316 | |
| 317 | /** @var taoResultServer_models_classes_Variable $variable */ |
| 318 | $variable = $variable->variable; |
| 319 | |
| 320 | if ($variable instanceof taoResultServer_models_classes_Variable) { |
| 321 | return $variable; |
| 322 | } |
| 323 | |
| 324 | return null; |
| 325 | } |
| 326 | |
| 327 | private function createCallItemId(string $deliveryExecutionUri, string $itemId): string |
| 328 | { |
| 329 | return sprintf('%s.%s.0', $deliveryExecutionUri, $itemId); |
| 330 | } |
| 331 | |
| 332 | /** |
| 333 | * @throws core_kernel_persistence_Exception |
| 334 | */ |
| 335 | private function getTestUri(AbstractRdsResultStorage $resultStorage, string $deliveryExecutionUri): string |
| 336 | { |
| 337 | $deliveryId = $resultStorage->getDelivery($deliveryExecutionUri); |
| 338 | |
| 339 | return (string)$this->ontology->getResource($deliveryId) |
| 340 | ->getOnePropertyValue($this->ontology->getProperty(DeliveryAssemblyService::PROPERTY_ORIGIN)); |
| 341 | } |
| 342 | |
| 343 | /** |
| 344 | * @throws ImportResultException |
| 345 | * @throws common_exception_Error |
| 346 | */ |
| 347 | private function getResultStorage(): AbstractRdsResultStorage |
| 348 | { |
| 349 | $resultStorage = $this->resultServerService->getResultStorage(); |
| 350 | |
| 351 | if ($resultStorage instanceof AbstractRdsResultStorage) { |
| 352 | return $resultStorage; |
| 353 | } |
| 354 | |
| 355 | throw new ImportResultException( |
| 356 | sprintf( |
| 357 | 'ResultStorage must be an instance of %s. Instance of %s provided', |
| 358 | AbstractRdsResultStorage::class, |
| 359 | get_class($resultStorage) |
| 360 | ) |
| 361 | ); |
| 362 | } |
| 363 | } |