Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.90% covered (warning)
86.90%
73 / 84
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SendCalculatedResultService
86.90% covered (warning)
86.90%
73 / 84
28.57% covered (danger)
28.57%
2 / 7
30.89
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
 sendByDeliveryExecutionId
96.00% covered (success)
96.00%
24 / 25
0.00% covered (danger)
0.00%
0 / 1
2
 getResultsStorage
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getScores
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
10.81
 checkIsFullyGraded
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 isSubjectOutcomeVariableGraded
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
7.18
 getScoreTotalTimestamp
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
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 (under the project TAO-PRODUCT);
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoResultServer\models\Import\Service;
24
25use common_exception_Error;
26use oat\oatbox\event\EventManager;
27use oat\oatbox\service\exception\InvalidServiceManagerException;
28use oat\taoDelivery\model\execution\DeliveryExecutionService;
29use oat\taoResultServer\models\classes\implementation\ResultServerService;
30use oat\taoResultServer\models\Events\DeliveryExecutionResultsRecalculated;
31use stdClass;
32use taoResultServer_models_classes_ReadableResultStorage as ReadableResultStorage;
33use taoResultServer_models_classes_Variable as ResultVariable;
34use taoResultServer_models_classes_OutcomeVariable as OutcomeVariable;
35use tao_helpers_Date as DateHelper;
36
37class SendCalculatedResultService
38{
39    private ResultServerService $resultServerService;
40    private EventManager $eventManager;
41    private DeliveryExecutionService $deliveryExecutionService;
42    private DeliveredTestOutcomeDeclarationsService $deliveredTestOutcomeDeclarationsService;
43
44    public function __construct(
45        ResultServerService $resultServerService,
46        EventManager $eventManager,
47        DeliveryExecutionService $deliveryExecutionService,
48        DeliveredTestOutcomeDeclarationsService $qtiTestItemsService
49    ) {
50        $this->resultServerService = $resultServerService;
51        $this->eventManager = $eventManager;
52        $this->deliveryExecutionService = $deliveryExecutionService;
53        $this->deliveredTestOutcomeDeclarationsService = $qtiTestItemsService;
54    }
55
56    /**
57     * @throws common_exception_Error
58     * @throws \common_exception_NotFound
59     * @throws InvalidServiceManagerException
60     */
61    public function sendByDeliveryExecutionId(string $deliveryExecutionId): array
62    {
63        $deliveryExecution = $this->deliveryExecutionService->getDeliveryExecution($deliveryExecutionId);
64        $outcomeVariables = $this->getResultsStorage()->getDeliveryVariables($deliveryExecutionId);
65
66        [$scoreTotal, $scoreTotalMax] = $this->getScores($outcomeVariables);
67
68        $isFullyGraded = $this->checkIsFullyGraded($deliveryExecutionId, $outcomeVariables);
69
70        $microtime = $deliveryExecution->getFinishTime();
71        $scoreTotalMicrotime = $this->getScoreTotalTimestamp($outcomeVariables);
72        if ($scoreTotalMicrotime !== null) {
73            $microtime = $scoreTotalMicrotime;
74        }
75        $timestamp = DateHelper::formatMicrotime($microtime);
76
77        $this->eventManager->trigger(
78            new DeliveryExecutionResultsRecalculated(
79                $deliveryExecution,
80                $scoreTotal,
81                $scoreTotalMax,
82                $isFullyGraded,
83                $timestamp
84            )
85        );
86
87        return [
88            'deliveryExecution' => $deliveryExecution,
89            'scoreTotal' => $scoreTotal,
90            'scoreTotalMax' => $scoreTotalMax,
91            'isFullyGraded' => $isFullyGraded,
92            'timestamp' => $timestamp
93        ];
94    }
95
96    /**
97     * @throws common_exception_Error
98     * @throws InvalidServiceManagerException
99     */
100    private function getResultsStorage(): ReadableResultStorage
101    {
102        $storage = $this->resultServerService->getResultStorage();
103
104        if (!$storage instanceof ReadableResultStorage) {
105            throw new common_exception_Error('Configured result storage is not writable.');
106        }
107
108        return $storage;
109    }
110
111    private function getScores(array $outcomeVariables): array
112    {
113        $scoreTotal = null;
114        $scoreTotalMax = null;
115
116        foreach ($outcomeVariables as $outcomeVariable) {
117            if (!is_array($outcomeVariable)) {
118                continue;
119            }
120
121            /** @var stdClass $variable */
122            $variable = current($outcomeVariable);
123
124            if (!is_object($variable) || !property_exists($variable, 'variable')) {
125                continue;
126            }
127
128            /** @var ResultVariable $variable */
129            $variable = $variable->variable;
130            if (!$variable instanceof ResultVariable) {
131                continue;
132            }
133
134            if ($variable->getIdentifier() === 'SCORE_TOTAL') {
135                $scoreTotal = (float)$variable->getValue();
136
137                continue;
138            }
139
140            if ($variable->getIdentifier() === 'SCORE_TOTAL_MAX') {
141                $scoreTotalMax = (float)$variable->getValue();
142            }
143        }
144        return [$scoreTotal, $scoreTotalMax];
145    }
146
147    private function checkIsFullyGraded(string $deliveryExecutionId, array $outcomeVariables): bool
148    {
149        $testItemsData = $this->deliveredTestOutcomeDeclarationsService
150            ->getDeliveredTestOutcomeDeclarations($deliveryExecutionId);
151
152        $isFullyGraded = true;
153        foreach ($testItemsData as $itemIdentifier => $itemData) {
154            foreach ($itemData['outcomes'] ?? [] as $outcomeDeclaration) {
155                if (!isset($outcomeDeclaration['attributes']['externalScored'])) {
156                    continue;
157                }
158                $isSubjectOutcomeVariableGraded = $this->isSubjectOutcomeVariableGraded(
159                    $outcomeVariables,
160                    $outcomeDeclaration['identifier'],
161                    $itemIdentifier,
162                );
163                if ($isSubjectOutcomeVariableGraded === false) {
164                    $isFullyGraded = false;
165                    break;
166                }
167            }
168        }
169        return $isFullyGraded;
170    }
171
172    private function isSubjectOutcomeVariableGraded(
173        array $outcomeVariables,
174        string $outcomeDeclarationIdentifier,
175        string $itemIdentifier
176    ): bool {
177        foreach ($outcomeVariables as $outcomeVariableArray) {
178            $outcomeVariable = current($outcomeVariableArray);
179            $outcomeItemIdentifier = $outcomeVariable->callIdItem;
180            if ($outcomeItemIdentifier !== null && strpos($outcomeItemIdentifier, $itemIdentifier) === false) {
181                continue;
182            }
183
184            if (!$outcomeVariable->variable instanceof ResultVariable) {
185                continue;
186            }
187            $variable = $outcomeVariable->variable;
188
189            if ($outcomeDeclarationIdentifier !== $variable->getIdentifier()) {
190                continue;
191            }
192            if ($variable->getExternallyGraded()) {
193                return true;
194            }
195        }
196        return false;
197    }
198
199    private function getScoreTotalTimestamp(array $outcomeVariables): ?string
200    {
201        foreach ($outcomeVariables as $outcomeVariableList) {
202            $outcomeVariable = $outcomeVariableList[0]->variable;
203            if ($outcomeVariable instanceof OutcomeVariable && $outcomeVariable->getIdentifier() === 'SCORE_TOTAL') {
204                return $outcomeVariable->getEpoch();
205            }
206        }
207        return null;
208    }
209}