Passed
Pull Request — master (#7302)
by Angel Fernando Quiroz
18:19 queued 07:15
created

UserItemController::patchUser()   C

Complexity

Conditions 12
Paths 11

Size

Total Lines 49
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 26
c 1
b 0
f 0
nc 11
nop 2
dl 0
loc 49
rs 6.9666

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