Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
LtiAgsListener
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 6
420
0.00% covered (danger)
0.00%
0 / 1
 onDeliveryExecutionStart
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 onDeliveryExecutionResultsRecalculated
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 onDeliveryExecutionFinish
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
110
 queueSendAgsScoreTaskWithScores
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 getAgsMaxRetries
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLtiContextRepository
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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-2023 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 *
20 */
21
22declare(strict_types=1);
23
24namespace oat\ltiDeliveryProvider\model\events;
25
26use DateTime;
27use DateTimeInterface;
28use OAT\Library\Lti1p3Ags\Model\Score\ScoreInterface;
29use OAT\Library\Lti1p3Core\Message\Payload\Claim\AgsClaim;
30use oat\ltiDeliveryProvider\model\execution\LtiContextRepositoryInterface;
31use oat\ltiDeliveryProvider\model\tasks\SendAgsScoreTask;
32use oat\oatbox\service\ConfigurableService;
33use oat\tao\model\taskQueue\QueueDispatcherInterface;
34use oat\taoDelivery\models\classes\execution\event\DeliveryExecutionCreated;
35use oat\taoLti\models\classes\LtiLaunchData;
36use oat\taoLti\models\classes\user\Lti1p3User;
37use oat\taoQtiTest\models\event\DeliveryExecutionFinish;
38use oat\taoResultServer\models\Events\DeliveryExecutionResultsRecalculated;
39use tao_helpers_Date as DateHelper;
40use taoResultServer_models_classes_OutcomeVariable as OutcomeVariable;
41
42class LtiAgsListener extends ConfigurableService
43{
44    public const OPTION_AGS_MAX_RETRY = 'ags_max_retries';
45
46    public function onDeliveryExecutionStart(DeliveryExecutionCreated $event): void
47    {
48        $user = $event->getUser();
49        $deliveryExecution = $event->getDeliveryExecution();
50
51        if ($user instanceof Lti1p3User && $user->getLaunchData()->hasVariable(LtiLaunchData::AGS_CLAIMS)) {
52
53            /** @var AgsClaim $agsClaim */
54            $agsClaim = $user->getLaunchData()->getVariable(LtiLaunchData::AGS_CLAIMS);
55
56            /** @var QueueDispatcherInterface $taskQueue */
57            $taskQueue = $this->getServiceLocator()->get(QueueDispatcherInterface::SERVICE_ID);
58            $taskQueue->createTask(new SendAgsScoreTask(), [
59                'registrationId' => $user->getRegistrationId(),
60                'deliveryExecutionId' => $deliveryExecution->getIdentifier(),
61                'agsClaim' => $agsClaim->normalize(),
62                'data' => [
63                    'userId' => $user->getIdentifier(),
64                    'activityProgress' => ScoreInterface::ACTIVITY_PROGRESS_STATUS_STARTED,
65                    'timestamp' => (new DateTime('now'))->format(DateTimeInterface::RFC3339_EXTENDED),
66                ]
67            ], 'AGS score send on test launch');
68        }
69    }
70
71    public function onDeliveryExecutionResultsRecalculated(DeliveryExecutionResultsRecalculated $event): void
72    {
73        $deliveryExecution = $event->getDeliveryExecution();
74        $gradingStatus = ScoreInterface::GRADING_PROGRESS_STATUS_PENDING_MANUAL;
75        if ($event->isFullyGraded()) {
76            $gradingStatus = ScoreInterface::GRADING_PROGRESS_STATUS_FULLY_GRADED;
77        }
78
79        if ($launchData = $this->getLtiContextRepository()->findByDeliveryExecution($deliveryExecution)) {
80            $this->queueSendAgsScoreTaskWithScores(
81                'AGS scores send on result recalculation',
82                $launchData,
83                $deliveryExecution->getIdentifier(),
84                $event->getTotalScore(),
85                $event->getTotalMaxScore(),
86                $gradingStatus,
87                $event->getTimestamp()
88            );
89        }
90    }
91
92    public function onDeliveryExecutionFinish(DeliveryExecutionFinish $event): void
93    {
94        $deliveryId = $event->getDeliveryExecution()->getIdentifier();
95        $launchData = $this->getLtiContextRepository()->findByDeliveryExecutionId($deliveryId);
96        if (!$launchData) {
97            return;
98        }
99        $scoreTotal = null;
100        $scoreTotalMax = null;
101        $scoreTotalMicrotime = null;
102        foreach ($event->getVariables() as $variable) {
103            $variable = array_pop($variable)->variable;
104
105            if ($variable instanceof OutcomeVariable) {
106                if ($variable->getIdentifier() === 'SCORE_TOTAL') {
107                    $scoreTotal = (float)$variable->getValue();
108                    $scoreTotalMicrotime = $variable->getEpoch();
109                }
110
111                if ($variable->getIdentifier() === 'SCORE_TOTAL_MAX') {
112                    $scoreTotalMax = (float)$variable->getValue();
113                }
114
115                if ($scoreTotal !== null && $scoreTotalMax !== null) {
116                    break;
117                }
118            }
119        }
120
121        if (!$scoreTotalMicrotime) {
122            $scoreTotalMicrotime = $event->getDeliveryExecution()->getFinishTime();
123        }
124
125        $this->queueSendAgsScoreTaskWithScores(
126            'AGS score send on test finish',
127            $launchData,
128            $deliveryId,
129            $scoreTotal,
130            $scoreTotalMax,
131            $event->getIsManualScored()
132                ? ScoreInterface::GRADING_PROGRESS_STATUS_PENDING_MANUAL
133                : ScoreInterface::GRADING_PROGRESS_STATUS_FULLY_GRADED,
134            DateHelper::formatMicrotime($scoreTotalMicrotime)
135        );
136    }
137
138    private function queueSendAgsScoreTaskWithScores(
139        string $taskLabel,
140        LtiLaunchData $ltiLaunchData,
141        string $deliveryExecutionId,
142        $scoreTotal,
143        $scoreTotalMax,
144        string $gradingStatus,
145        ?string $timestamp = null
146    ): void {
147
148        if (!$ltiLaunchData->hasVariable(LtiLaunchData::AGS_CLAIMS)) {
149            return;
150        }
151
152        $agsClaim = $ltiLaunchData->getVariable(LtiLaunchData::AGS_CLAIMS);
153        $registrationId = $ltiLaunchData->getVariable(LtiLaunchData::TOOL_CONSUMER_INSTANCE_ID);
154        $userId = $ltiLaunchData->getUserID();
155        $taskBody = [
156            'retryMax' => $this->getAgsMaxRetries(),
157            'registrationId' => $registrationId,
158            'deliveryExecutionId' => $deliveryExecutionId,
159            'agsClaim' => $agsClaim->normalize(),
160            'data' => [
161                'userId' => $userId,
162                'activityProgress' => ScoreInterface::ACTIVITY_PROGRESS_STATUS_COMPLETED,
163                'gradingProgress' => $gradingStatus,
164                'scoreGiven' => $scoreTotal,
165                'scoreMaximum' => $scoreTotalMax,
166                'timestamp' => $timestamp ?? (new DateTime('now'))->format(DateTimeInterface::RFC3339_EXTENDED),
167            ]
168        ];
169
170        /** @var QueueDispatcherInterface $taskQueue */
171        $taskQueue = $this->getServiceLocator()->get(QueueDispatcherInterface::SERVICE_ID);
172        $taskQueue->createTask(new SendAgsScoreTask(), $taskBody, $taskLabel);
173    }
174
175    private function getAgsMaxRetries(): int
176    {
177        return $this->getOption(self::OPTION_AGS_MAX_RETRY, 5);
178    }
179
180    private function getLtiContextRepository(): LtiContextRepositoryInterface
181    {
182        return $this->getServiceManager()->getContainer()->get(LtiContextRepositoryInterface::class);
183    }
184}