Passed
Push — master ( 09a914...d665b7 )
by Angel Fernando Quiroz
10:25
created

AccessUrlController   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 114
c 0
b 0
f 0
dl 0
loc 226
rs 9.1199
wmc 41

6 Methods

Rating   Name   Duplication   Size   Complexity  
B authSourcesAssign() 0 41 8
C importUsers() 0 70 14
A authSourcesList() 0 23 3
A __construct() 0 4 1
C removeUsers() 0 66 14
A formatReport() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like AccessUrlController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AccessUrlController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller;
8
9
use ApiPlatform\Api\IriConverterInterface;
10
use Chamilo\CoreBundle\Entity\AccessUrl;
11
use Chamilo\CoreBundle\Entity\User;
12
use Chamilo\CoreBundle\Helpers\AuthenticationConfigHelper;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Exception;
15
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
16
use Symfony\Component\HttpFoundation\JsonResponse;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\Routing\Attribute\Route;
20
use Symfony\Component\Security\Http\Attribute\IsGranted;
21
use Symfony\Contracts\Translation\TranslatorInterface;
22
23
#[Route('/access-url')]
24
class AccessUrlController extends AbstractController
25
{
26
    public function __construct(
27
        private readonly TranslatorInterface $translator,
28
        private readonly EntityManagerInterface $em,
29
    ) {}
30
31
    #[IsGranted('ROLE_ADMIN')]
32
    #[Route('/users/import', name: 'chamilo_core_access_url_users_import', methods: ['GET', 'POST'])]
33
    public function importUsers(Request $request): Response
34
    {
35
        $report = [];
36
37
        if ($request->isMethod('POST') && $request->files->has('csv_file')) {
38
            $file = $request->files->get('csv_file')->getPathname();
39
            $handle = fopen($file, 'r');
40
            $lineNumber = 0;
41
42
            while (($data = fgetcsv($handle, 1000, ',')) !== false) {
43
                $lineNumber++;
44
45
                if (1 === $lineNumber && 'username' === strtolower(trim($data[0]))) {
46
                    continue; // Skip header
47
                }
48
49
                if (\count($data) < 2) {
50
                    $report[] = $this->formatReport('alert-circle', 'Line %s: invalid format. Two columns expected.', [$lineNumber]);
51
52
                    continue;
53
                }
54
55
                [$username, $url] = array_map('trim', $data);
56
57
                if (!$username || !$url) {
58
                    $report[] = $this->formatReport('alert-circle', 'Line %s: missing username or URL.', [$lineNumber]);
59
60
                    continue;
61
                }
62
63
                // Normalize URL
64
                if (!str_starts_with($url, 'http')) {
65
                    $url = 'https://'.$url;
66
                }
67
                if (!str_ends_with($url, '/')) {
68
                    $url .= '/';
69
                }
70
71
                $user = $this->em->getRepository(User::class)->findOneBy(['username' => $username]);
72
                if (!$user) {
73
                    $report[] = $this->formatReport('close-circle', "Line %s: user '%s' not found.", [$lineNumber, $username]);
74
75
                    continue;
76
                }
77
78
                $accessUrl = $this->em->getRepository(AccessUrl::class)->findOneBy(['url' => $url]);
79
                if (!$accessUrl) {
80
                    $report[] = $this->formatReport('close-circle', "Line %s: URL '%s' not found.", [$lineNumber, $url]);
81
82
                    continue;
83
                }
84
85
                if ($accessUrl->hasUser($user)) {
86
                    $report[] = $this->formatReport('information-outline', "Line %s: user '%s' is already assigned to '%s'.", [$lineNumber, $username, $url]);
87
                } else {
88
                    $accessUrl->addUser($user);
89
                    $this->em->persist($accessUrl);
90
                    $report[] = $this->formatReport('check-circle', "Line %s: user '%s' successfully assigned to '%s'.", [$lineNumber, $username, $url]);
91
                }
92
            }
93
94
            fclose($handle);
95
            $this->em->flush();
96
        }
97
98
        return $this->render('@ChamiloCore/AccessUrl/import_users.html.twig', [
99
            'report' => $report,
100
            'title' => $this->translator->trans('Assign users to URLs from CSV'),
101
        ]);
102
    }
103
104
    #[IsGranted('ROLE_ADMIN')]
105
    #[Route('/users/remove', name: 'chamilo_core_access_url_users_remove', methods: ['GET', 'POST'])]
106
    public function removeUsers(Request $request): Response
107
    {
108
        $report = [];
109
110
        if ($request->isMethod('POST') && $request->files->has('csv_file')) {
111
            $file = $request->files->get('csv_file')->getPathname();
112
            $handle = fopen($file, 'r');
113
            $lineNumber = 0;
114
115
            while (($data = fgetcsv($handle, 1000, ',')) !== false) {
116
                $lineNumber++;
117
118
                if (1 === $lineNumber && 'username' === strtolower(trim($data[0]))) {
119
                    continue; // Skip header
120
                }
121
122
                [$username, $url] = array_map('trim', $data);
123
124
                if (!$username || !$url) {
125
                    $report[] = $this->formatReport('alert-circle', 'Line %s: empty fields.', [$lineNumber]);
126
127
                    continue;
128
                }
129
130
                if (!str_starts_with($url, 'http')) {
131
                    $url = 'https://'.$url;
132
                }
133
                if (!str_ends_with($url, '/')) {
134
                    $url .= '/';
135
                }
136
137
                $user = $this->em->getRepository(User::class)->findOneBy(['username' => $username]);
138
                if (!$user) {
139
                    $report[] = $this->formatReport('close-circle', "Line %s: user '%s' not found.", [$lineNumber, $username]);
140
141
                    continue;
142
                }
143
144
                $accessUrl = $this->em->getRepository(AccessUrl::class)->findOneBy(['url' => $url]);
145
                if (!$accessUrl) {
146
                    $report[] = $this->formatReport('close-circle', "Line %s: URL '%s' not found.", [$lineNumber, $url]);
147
148
                    continue;
149
                }
150
151
                foreach ($accessUrl->getUsers() as $rel) {
152
                    if ($rel->getUser()->getId() === $user->getId()) {
153
                        $this->em->remove($rel);
154
                        $report[] = $this->formatReport('account-remove-outline', "Line %s: user '%s' removed from '%s'.", [$lineNumber, $username, $url]);
155
156
                        continue 2;
157
                    }
158
                }
159
160
                $report[] = $this->formatReport('alert-circle', 'Line %s: no relation found between user and URL.', [$lineNumber]);
161
            }
162
163
            fclose($handle);
164
            $this->em->flush();
165
        }
166
167
        return $this->render('@ChamiloCore/AccessUrl/remove_users.html.twig', [
168
            'report' => $report,
169
            'title' => $this->translator->trans('Remove users from URLs with a CSV file'),
170
        ]);
171
    }
172
173
    private function formatReport(string $icon, string $message, array $params): string
174
    {
175
        $text = vsprintf($this->translator->trans($message), $params);
176
177
        return \sprintf('<i class="mdi mdi-%s text-base me-1"></i> %s', $icon, $text);
178
    }
179
180
    #[Route('/auth-sources/list', methods: ['GET'])]
181
    #[IsGranted('ROLE_ADMIN')]
182
    public function authSourcesList(
183
        Request $request,
184
        AuthenticationConfigHelper $authConfigHelper,
185
        IriConverterInterface $iriConverter,
186
    ): JsonResponse {
187
        $accessUrlIri = $request->query->get('access_url', '');
188
189
        if (!$accessUrlIri) {
190
            throw $this->createNotFoundException('Access URL not found');
191
        }
192
193
        try {
194
            /** @var AccessUrl $accessUrl */
195
            $accessUrl = $iriConverter->getResourceFromIri($accessUrlIri);
196
        } catch (Exception) {
197
            throw $this->createNotFoundException('Access URL not found');
198
        }
199
200
        $authSources = $authConfigHelper->getAuthSourceAuthentications($accessUrl);
201
202
        return new JsonResponse($authSources);
203
    }
204
205
    /**
206
     * @throws Exception
207
     */
208
    #[Route('/auth-sources/assign', methods: ['POST'])]
209
    #[IsGranted('ROLE_ADMIN')]
210
    public function authSourcesAssign(
211
        Request $request,
212
        AuthenticationConfigHelper $authConfigHelper,
213
        IriConverterInterface $iriConverter,
214
        EntityManagerInterface $entityManager,
215
    ): Response {
216
        $data = json_decode($request->getContent(), true);
217
218
        if (empty($data['users']) || empty($data['access_url'] || empty($data['auth_source']))) {
219
            throw new Exception('Missing required parameters');
220
        }
221
222
        try {
223
            /** @var AccessUrl $accessUrl */
224
            $accessUrl = $iriConverter->getResourceFromIri($data['access_url']);
225
        } catch (Exception) {
226
            throw $this->createNotFoundException('Access URL not found');
227
        }
228
229
        $authSources = $authConfigHelper->getAuthSourceAuthentications($accessUrl);
230
231
        if (!in_array($data['auth_source'], $authSources)) {
232
            throw new Exception('User authentication method not allowed');
233
        }
234
235
        foreach ($data['users'] as $userIri) {
236
            try {
237
                /** @var User $user */
238
                $user = $iriConverter->getResourceFromIri($userIri);
239
            } catch (Exception) {
240
                continue;
241
            }
242
243
            $user->addAuthSourceByAuthentication($data['auth_source'], $accessUrl);
244
        }
245
246
        $entityManager->flush();
247
248
        return new Response(null, Response::HTTP_NO_CONTENT);
249
    }
250
}
251