Passed
Pull Request — master (#5629)
by Angel Fernando Quiroz
08:49
created

StatementGetController   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 75
dl 0
loc 169
c 1
b 0
f 0
rs 9.84
wmc 32

7 Methods

Rating   Name   Duplication   Size   Complexity  
A generateMoreIrl() 0 7 2
A buildSingleStatementResponse() 0 13 2
A getStatement() 0 36 5
A buildMultipartResponse() 0 11 3
C validate() 0 23 16
A __construct() 0 6 1
A buildMultiStatementsResponse() 0 15 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the xAPI package.
7
 *
8
 * (c) Christian Flothmann <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace XApi\LrsBundle\Controller;
15
16
use DateTime;
17
use Exception;
18
use Symfony\Component\HttpFoundation\JsonResponse;
19
use Symfony\Component\HttpFoundation\ParameterBag;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
23
use Xabbuh\XApi\Common\Exception\NotFoundException;
24
use Xabbuh\XApi\Model\IRL;
25
use Xabbuh\XApi\Model\Statement;
26
use Xabbuh\XApi\Model\StatementId;
27
use Xabbuh\XApi\Model\StatementResult;
28
use Xabbuh\XApi\Serializer\StatementResultSerializerInterface;
29
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
30
use XApi\LrsBundle\Model\StatementsFilterFactory;
31
use XApi\LrsBundle\Response\AttachmentResponse;
32
use XApi\LrsBundle\Response\MultipartResponse;
33
use XApi\Repository\Api\StatementRepositoryInterface;
34
35
use const FILTER_VALIDATE_BOOLEAN;
36
37
/**
38
 * @author Jérôme Parmentier <[email protected]>
39
 */
40
class StatementGetController
41
{
42
    protected static array $getParameters = [
43
        'statementId' => true,
44
        'voidedStatementId' => true,
45
        'agent' => true,
46
        'verb' => true,
47
        'activity' => true,
48
        'registration' => true,
49
        'related_activities' => true,
50
        'related_agents' => true,
51
        'since' => true,
52
        'until' => true,
53
        'limit' => true,
54
        'format' => true,
55
        'attachments' => true,
56
        'ascending' => true,
57
        'cursor' => true,
58
    ];
59
60
    public function __construct(
61
        protected readonly StatementRepositoryInterface $repository,
62
        protected readonly StatementSerializerInterface $statementSerializer,
63
        protected readonly StatementResultSerializerInterface $statementResultSerializer,
64
        protected readonly StatementsFilterFactory $statementsFilterFactory
65
    ) {}
66
67
    /**
68
     * @return Response
69
     *
70
     * @throws BadRequestHttpException if the query parameters does not comply with xAPI specification
71
     */
72
    public function getStatement(Request $request)
73
    {
74
        $query = new ParameterBag(array_intersect_key($request->query->all(), self::$getParameters));
75
76
        $this->validate($query);
77
78
        $includeAttachments = $query->filter('attachments', false, FILTER_VALIDATE_BOOLEAN);
79
80
        try {
81
            if (($statementId = $query->get('statementId')) !== null) {
82
                $statement = $this->repository->findStatementById(StatementId::fromString($statementId));
83
84
                $response = $this->buildSingleStatementResponse($statement, $includeAttachments);
85
            } elseif (($voidedStatementId = $query->get('voidedStatementId')) !== null) {
86
                $statement = $this->repository->findVoidedStatementById(StatementId::fromString($voidedStatementId));
87
88
                $response = $this->buildSingleStatementResponse($statement, $includeAttachments);
89
            } else {
90
                $statements = $this->repository->findStatementsBy($this->statementsFilterFactory->createFromParameterBag($query));
91
92
                $response = $this->buildMultiStatementsResponse($statements, $query, $includeAttachments);
93
            }
94
        } catch (NotFoundException $e) {
95
            $response = $this->buildMultiStatementsResponse([], $query)
96
                ->setStatusCode(Response::HTTP_NOT_FOUND)
97
                ->setContent('')
98
            ;
99
        } catch (Exception $exception) {
100
            $response = Response::create('', Response::HTTP_BAD_REQUEST);
101
        }
102
103
        $now = new DateTime();
104
        $response->headers->set('X-Experience-API-Consistent-Through', $now->format(DateTime::ATOM));
105
        $response->headers->set('Content-Type', 'application/json');
106
107
        return $response;
108
    }
109
110
    /**
111
     * @param bool $includeAttachments true to include the attachments in the response, false otherwise
112
     *
113
     * @return JsonResponse|MultipartResponse
114
     */
115
    protected function buildSingleStatementResponse(Statement $statement, $includeAttachments = false)
116
    {
117
        $json = $this->statementSerializer->serializeStatement($statement);
118
119
        $response = new Response($json, 200);
120
121
        if ($includeAttachments) {
122
            $response = $this->buildMultipartResponse($response, [$statement]);
123
        }
124
125
        $response->setLastModified($statement->getStored());
126
127
        return $response;
128
    }
129
130
    /**
131
     * @param Statement[] $statements
132
     * @param bool        $includeAttachments true to include the attachments in the response, false otherwise
133
     *
134
     * @return JsonResponse|MultipartResponse
135
     */
136
    protected function buildMultiStatementsResponse(array $statements, ParameterBag $query, $includeAttachments = false)
137
    {
138
        $moreUrlPath = $statements ? $this->generateMoreIrl($query) : null;
139
140
        $json = $this->statementResultSerializer->serializeStatementResult(
141
            new StatementResult($statements, $moreUrlPath)
142
        );
143
144
        $response = new Response($json, 200);
145
146
        if ($includeAttachments) {
147
            $response = $this->buildMultipartResponse($response, $statements);
148
        }
149
150
        return $response;
151
    }
152
153
    /**
154
     * @param Statement[] $statements
155
     *
156
     * @return MultipartResponse
157
     */
158
    protected function buildMultipartResponse(JsonResponse $statementResponse, array $statements)
159
    {
160
        $attachmentsParts = [];
161
162
        foreach ($statements as $statement) {
163
            foreach ((array) $statement->getAttachments() as $attachment) {
164
                $attachmentsParts[] = new AttachmentResponse($attachment);
165
            }
166
        }
167
168
        return new MultipartResponse($statementResponse, $attachmentsParts);
169
    }
170
171
    /**
172
     * Validate the parameters.
173
     *
174
     * @throws BadRequestHttpException if the parameters does not comply with the xAPI specification
175
     */
176
    protected function validate(ParameterBag $query): void
177
    {
178
        $hasStatementId = $query->has('statementId');
179
        $hasVoidedStatementId = $query->has('voidedStatementId');
180
181
        if ($hasStatementId && $hasVoidedStatementId) {
182
            throw new BadRequestHttpException('Request must not have both statementId and voidedStatementId parameters at the same time.');
183
        }
184
185
        $hasAttachments = $query->has('attachments');
186
        $hasFormat = $query->has('format');
187
        $queryCount = $query->count();
188
189
        if (($hasStatementId || $hasVoidedStatementId) && $hasAttachments && $hasFormat && $queryCount > 3) {
190
            throw new BadRequestHttpException('Request must not contain statementId or voidedStatementId parameters, and also any other parameter besides "attachments" or "format".');
191
        }
192
193
        if (($hasStatementId || $hasVoidedStatementId) && ($hasAttachments || $hasFormat) && $queryCount > 2) {
194
            throw new BadRequestHttpException('Request must not contain statementId or voidedStatementId parameters, and also any other parameter besides "attachments" or "format".');
195
        }
196
197
        if (($hasStatementId || $hasVoidedStatementId) && $queryCount > 1) {
198
            throw new BadRequestHttpException('Request must not contain statementId or voidedStatementId parameters, and also any other parameter besides "attachments" or "format".');
199
        }
200
    }
201
202
    protected function generateMoreIrl(ParameterBag $query): IRL
203
    {
204
        $params = $query->all();
205
        $params['cursor'] = empty($params['cursor']) ? 1 : $params['cursor'] + 1;
206
207
        return IRL::fromString(
208
            '/plugin/xapi/lrs.php/statements?'.http_build_query($params)
209
        );
210
    }
211
}
212