Passed
Pull Request — master (#7302)
by Angel Fernando Quiroz
09:53
created

UserItemController::applyPatchWithPath()   C

Complexity

Conditions 14
Paths 14

Size

Total Lines 74
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 37
c 1
b 0
f 0
nc 14
nop 3
dl 0
loc 74
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Controller\Scim;
8
9
use Chamilo\CoreBundle\Entity\User;
10
use Chamilo\CoreBundle\Exception\ScimException;
11
use Chamilo\CoreBundle\Repository\Node\UserRepository;
12
use Chamilo\CoreBundle\Serializer\Denormalizer\Scim\UserDenormalizer;
13
use Chamilo\CoreBundle\Serializer\Normalizer\Scim\UserNormalizer;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Symfony\Component\HttpFoundation\JsonResponse;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpFoundation\Response;
18
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
19
use Symfony\Component\Routing\Attribute\Route;
20
use Symfony\Component\Serializer\SerializerInterface;
21
use UserManager;
22
23
#[Route(
24
    '/scim/v2/Users/{uuid}',
25
    name: 'scim_user',
26
    methods: ['GET', 'PUT', 'PATCH', 'DELETE']
27
)]
28
class UserItemController extends AbstractScimController
29
{
30
    public function __invoke(
31
        string $uuid,
32
        Request $request,
33
        EntityManagerInterface $entityManager,
34
        UserRepository $userRepo,
35
        SerializerInterface $serializer,
36
    ): JsonResponse {
37
        $this->authenticateRequest($request);
38
39
        $user = $userRepo->findOneBy(['uuid' => $uuid]);
40
41
        if (!$user || $user->isSoftDeleted()) {
42
            throw $this->createNotFoundException($this->translator->trans('User not found.'));
43
        }
44
45
        return match ($request->getMethod()) {
46
            'GET' => $this->findUser(
47
                $user,
48
                $serializer,
49
            ),
50
            'PUT' => $this->replaceUser(
51
                $user,
52
                $request,
53
                $serializer,
54
                $entityManager,
55
            ),
56
            'PATCH' => $this->patchUser(
57
                $user,
58
                $request,
59
                $entityManager,
60
                $serializer,
61
            ),
62
            'DELETE' => $this->deleteUser($user),
63
            default => throw new MethodNotAllowedHttpException(['GET', 'PUT', 'PATCH', 'DELETE']),
64
        };
65
    }
66
67
    private function findUser(
68
        User $user,
69
        SerializerInterface $serializer,
70
    ): JsonResponse {
71
        $normalized = $serializer->normalize($user, UserNormalizer::FORMAT);
72
73
        return new JsonResponse(
74
            $normalized,
75
            Response::HTTP_OK,
76
            ['Content-Type' => self::SCIM_CONTENT_TYPE]
77
        );
78
    }
79
80
    private function replaceUser(
81
        User $user,
82
        Request $request,
83
        SerializerInterface $serializer,
84
        EntityManagerInterface $entityManager,
85
    ): JsonResponse {
86
        $data = $this->getAndValidateJson($request);
87
88
        $serializer->denormalize($data, User::class, UserDenormalizer::FORMAT, ['object_to_populate' => $user]);
89
90
        $entityManager->flush();
91
92
        return $this->findUser($user, $serializer);
93
    }
94
95
    private function patchUser(
96
        User $user,
97
        Request $request,
98
        EntityManagerInterface $entityManager,
99
        SerializerInterface $serializer,
100
    ): JsonResponse {
101
        $data = $this->getAndValidateJson($request);
102
103
        if (!isset($data['schemas']) || !\in_array('urn:ietf:params:scim:api:messages:2.0:PatchOp', $data['schemas'])) {
104
            throw new ScimException($this->translator->trans('Invalid schemas for PATCH operation.'));
105
        }
106
107
        if (!isset($data['Operations']) || !\is_array($data['Operations'])) {
108
            throw new ScimException($this->translator->trans('Missing required "Operations" array.'));
109
        }
110
111
        foreach ($data['Operations'] as $operation) {
112
            $op = strtolower($operation['op'] ?? '');
113
            $path = $operation['path'] ?? null;
114
            $value = $operation['value'] ?? null;
115
116
            if (!\in_array($op, ['add', 'replace', 'remove'])) {
117
                throw new ScimException(\sprintf($this->translator->trans("The operation '%s' is not supported."), $op));
118
            }
119
120
            if ($path) {
121
                if ('remove' === $op) {
122
                    $value = '';
123
                }
124
125
                $this->applyPatchWithPath($user, $path, (string) $value);
126
127
                if ('externalId' === $path) {
128
                    $this->scimHelper->saveExternalId($value, $user);
129
                }
130
            } else {
131
                if (!\is_array($value)) {
132
                    throw new ScimException($this->translator->trans('Required value for operation without path'));
133
                }
134
135
                $this->applyBulkReplace($user, $value);
136
137
                if (isset($value['externalId'])) {
138
                    $this->scimHelper->saveExternalId($value['externalId'], $user);
139
                }
140
            }
141
        }
142
143
        $entityManager->flush();
144
145
        return $this->findUser($user, $serializer);
146
    }
147
148
    private function applyPatchWithPath(User $user, string $path, string $value): void
149
    {
150
        $lowerPath = strtolower($path);
151
152
        if ('userName' === $lowerPath) {
153
            $user->setUsername($value);
154
155
            return;
156
        }
157
158
        if ('active' === $lowerPath) {
159
            $user->setActive((int) $value);
160
161
            return;
162
        }
163
164
        if ('locale' === $lowerPath) {
165
            $user->setLocale($value);
166
167
            return;
168
        }
169
170
        if ('timezone' === $lowerPath) {
171
            $user->setTimezone($value);
172
173
            return;
174
        }
175
176
        // emails[type eq "work"].value
177
        if (preg_match('/^emails\[type eq "([^"]+)"]\.value$/i', $path, $matches)) {
178
            $type = $matches[1];
179
180
            if ('work' === strtolower($type)) {
181
                $user->setEmail($value);
182
            }
183
184
            return;
185
        }
186
187
        // phoneNumbers[type eq "work"].value
188
        if (preg_match('/^phoneNumbers\[type eq "([^"]+)"]\.value$/i', $path, $matches)) {
189
            $type = $matches[1];
190
191
            if ('work' === strtolower($type)) {
192
                $user->setPhone($value);
193
            }
194
195
            return;
196
        }
197
198
        // addresses[type eq "work"].formatted
199
        if (preg_match('/^addresses\[type eq "([^"]+)"]\.formatted$/i', $path, $matches)) {
200
            $type = $matches[1];
201
202
            if ('work' === strtolower($type)) {
203
                $user->setAddress($value);
204
            }
205
206
            return;
207
        }
208
209
        if (str_starts_with($lowerPath, 'name.')) {
210
            $subPath = substr($path, 5); // exclude "name."
211
212
            switch (strtolower($subPath)) {
213
                case 'givenname':
214
                    $user->setFirstname($value);
215
216
                    break;
217
218
                case 'familyname':
219
                    $user->setLastname($value);
220
221
                    break;
222
            }
223
        }
224
    }
225
226
    private function applyBulkReplace(User $user, array $value): void
227
    {
228
        /*if (isset($value['externalId'])) {
229
            $user->setExternalId($value['externalId']);
230
        }*/
231
232
        if (isset($value['userName'])) {
233
            $user->setUsername($value['userName']);
234
        }
235
236
        // name.givenName, name.familyName
237
        if (isset($value['name.givenName'])) {
238
            $user->setFirstname($value['name.givenName']);
239
        }
240
241
        if (isset($value['name.familyName'])) {
242
            $user->setLastname($value['name.familyName']);
243
        }
244
245
        if ($email = UserDenormalizer::getPrimaryValue($value, 'emails')) {
246
            $user->setEmail($email);
247
        }
248
249
        if ($phone = UserDenormalizer::getPrimaryValue($value, 'phoneNumbers')) {
250
            $user->setPhone($phone);
251
        }
252
253
        if ($address = UserDenormalizer::getPrimaryValue($value, 'addresses', 'formatted')) {
254
            $user->setAddress($address);
255
        }
256
257
        if (isset($value['active'])) {
258
            $user->setActive((int) $value['active']);
259
        }
260
261
        if (isset($value['locale'])) {
262
            $user->setLocale($value['locale']);
263
        }
264
265
        if (isset($value['timezone'])) {
266
            $user->setTimezone($value['timezone']);
267
        }
268
    }
269
270
    private function deleteUser(User $user): JsonResponse
271
    {
272
        UserManager::delete_user($user->getId());
273
274
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
275
    }
276
}
277