Passed
Push — master ( 3b0be6...d0ad56 )
by
unknown
10:47 queued 02:30
created

SessionAdminController   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 218
c 2
b 0
f 0
dl 0
loc 376
rs 9.52
wmc 36

9 Methods

Rating   Name   Duplication   Size   Complexity  
B listCompleted() 0 77 9
A listCourses() 0 26 1
A normalizePath() 0 10 6
A listIncomplete() 0 44 2
A getCourseForSessionAdmin() 0 34 2
A __construct() 0 8 1
B searchUsers() 0 76 8
B extendSessionByWeek() 0 42 6
A listRestartables() 0 35 1
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller\Admin;
8
9
use Chamilo\CoreBundle\Controller\BaseController;
10
use Chamilo\CoreBundle\Entity\AccessUrlRelUser;
11
use Chamilo\CoreBundle\Entity\Course;
12
use Chamilo\CoreBundle\Entity\ExtraField;
13
use Chamilo\CoreBundle\Entity\GradebookCertificate;
14
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
15
use Chamilo\CoreBundle\Entity\SessionRelUser;
16
use Chamilo\CoreBundle\Helpers\AccessUrlHelper;
17
use Chamilo\CoreBundle\Helpers\UserHelper;
18
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
19
use Chamilo\CoreBundle\Repository\ExtraFieldValuesRepository;
20
use Chamilo\CoreBundle\Repository\GradebookCertificateRepository;
21
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
22
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
23
use Chamilo\CoreBundle\Repository\Node\UserRepository;
24
use Chamilo\CoreBundle\Repository\SessionRelCourseRelUserRepository;
25
use Chamilo\CoreBundle\Settings\SettingsManager;
26
use Chamilo\CourseBundle\Repository\CCourseDescriptionRepository;
27
use DateTime;
28
use Doctrine\ORM\EntityManagerInterface;
29
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
30
use Symfony\Component\HttpFoundation\JsonResponse;
31
use Symfony\Component\HttpFoundation\Request;
32
use Symfony\Component\HttpFoundation\Response;
33
use Symfony\Component\Routing\Annotation\Route;
34
35
use function pathinfo;
36
37
use const PATHINFO_FILENAME;
38
39
#[Security("is_granted('ROLE_ADMIN') or is_granted('ROLE_SESSION_MANAGER')")]
40
#[Route('/admin/sessionadmin')]
41
class SessionAdminController extends BaseController
42
{
43
    public function __construct(
44
        private readonly CourseRepository $courseRepository,
45
        private readonly AccessUrlHelper $accessUrlHelper,
46
        private readonly SettingsManager $settingsManager,
47
        private readonly UserHelper $userHelper,
48
        private readonly CCourseDescriptionRepository $courseDescriptionRepository,
49
        private readonly IllustrationRepository $illustrationRepository
50
    ) {}
51
52
    #[Route('/courses', name: 'chamilo_core_admin_sessionadmin_courses', methods: ['GET'])]
53
    public function listCourses(): JsonResponse
54
    {
55
        $url = $this->accessUrlHelper->getCurrent();
56
        $courses = $this->courseRepository->getCoursesByAccessUrl($url);
57
58
        $filterForCards = '';
59
60
        $data = array_map(function (Course $course) use ($filterForCards) {
61
            $illustrationUrl = $this->illustrationRepository->getIllustrationUrl(
62
                $course,
63
                $filterForCards,
64
                512
65
            );
66
67
            return [
68
                'id' => $course->getId(),
69
                'title' => $course->getTitle(),
70
                'code' => $course->getCode(),
71
                'description' => $course->getDescription(),
72
                'visibility' => $course->getVisibility(),
73
                'illustrationUrl' => $this->normalizePath($illustrationUrl),
74
            ];
75
        }, $courses);
76
77
        return $this->json($data);
78
    }
79
80
    private function normalizePath(?string $path): string
81
    {
82
        if (!\is_string($path) || $path === '') {
83
            return '/img/session_default.svg';
84
        }
85
        $p = trim($path);
86
        if (\str_starts_with($p, 'http://') || \str_starts_with($p, 'https://') || \str_starts_with($p, '/')) {
87
            return $p;
88
        }
89
        return '/'.ltrim($p, '/');
90
    }
91
92
    #[Route('/courses/completed', name: 'chamilo_core_admin_sessionadmin_courses_completed', methods: ['GET'])]
93
    public function listCompleted(
94
        Request $request,
95
        GradebookCertificateRepository $repo,
96
        AccessUrlHelper $accessUrlHelper,
97
    ): JsonResponse {
98
        // Extract and validate pagination parameters from the query
99
        $offset = max(0, (int) $request->query->get('offset', 0));
100
        $limit = max(1, (int) $request->query->get('limit', 50));
101
102
        // Determine current access URL context
103
        $url = $accessUrlHelper->getCurrent();
104
105
        // Retrieve certificates with associated session and course context
106
        $certs = $repo->findCertificatesWithContext($url->getId(), $offset, $limit);
107
108
        $user = $this->userHelper->getCurrent();
109
110
        $allowPublic = 'true' === $this->settingsManager->getSetting('course.allow_public_certificates', true);
111
        $allowSessionAdmin = 'true' === $this->settingsManager->getSetting('certificate.session_admin_can_download_all_certificates', true);
112
        $isSessionAdmin = $user && $user->isSessionAdmin();
113
114
        // Transform the certificate entities into a frontend-friendly structure
115
        $mapCertificate = function (GradebookCertificate $gc) use (
116
            $allowPublic,
117
            $allowSessionAdmin,
118
            $isSessionAdmin,
119
            $user
120
        ) {
121
            $sessionRel = $gc->getCategory()->getCourse()->getSessions()[0] ?? null;
122
            $session = $sessionRel?->getSession();
123
            $path = $gc->getPathCertificate();
124
125
            $hash = null;
126
            $downloadUrl = null;
127
            $isDownloadAllowed = false;
128
129
            if (!empty($path)) {
130
                $hash = pathinfo($path, PATHINFO_FILENAME);
131
                $downloadUrl = '/certificates/'.$hash.'.pdf';
0 ignored issues
show
Bug introduced by
Are you sure $hash of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
                $downloadUrl = '/certificates/'./** @scrutinizer ignore-type */ $hash.'.pdf';
Loading history...
132
133
                $isPlatformAdmin = $user && $user->isAdmin();
134
                $isPublic = $allowPublic && $gc->getPublish();
135
136
                $isDownloadAllowed = $isPlatformAdmin
137
                    || $isPublic
138
                    || ($isSessionAdmin && $allowSessionAdmin);
139
            }
140
141
            return [
142
                'id' => $gc->getId(),
143
                'issuedAt' => $gc->getCreatedAt()->format('Y-m-d H:i:s'),
144
                'user' => [
145
                    'id' => $gc->getUser()->getId(),
146
                    'name' => $gc->getUser()->getFullName(),
147
                ],
148
                'course' => [
149
                    'id' => $gc->getCategory()->getCourse()->getId(),
150
                    'title' => $gc->getCategory()->getCourse()->getTitle(),
151
                ],
152
                'session' => $session ? [
153
                    'id' => $session->getId(),
154
                    'title' => $session->getTitle(),
155
                ] : null,
156
                'downloadUrl' => $downloadUrl,
157
                'isDownloadAllowed' => $isDownloadAllowed,
158
            ];
159
        };
160
161
        $items = array_map($mapCertificate, $certs);
162
163
        // Return JSON response with pagination metadata and certificate list
164
        return $this->json([
165
            'items' => $items,
166
            'offset' => $offset,
167
            'limit' => $limit,
168
            'count' => \count($items),
169
        ]);
170
    }
171
172
    #[Route('/courses/incomplete', name: 'chamilo_core_admin_sessionadmin_courses_incomplete', methods: ['GET'])]
173
    public function listIncomplete(
174
        GradebookCertificateRepository $repo,
175
        AccessUrlHelper $accessUrlHelper
176
    ): JsonResponse {
177
        $url = $accessUrlHelper->getCurrent();
178
        $results = $repo->findIncompleteCertificates($url->getId());
179
180
        $items = array_map(function (SessionRelUser $sru) {
181
            $user = $sru->getUser();
182
            $session = $sru->getSession();
183
184
            $courses = $session->getCourses();
185
            $courseItems = [];
186
187
            foreach ($courses as $src) {
188
                $course = $src->getCourse();
189
190
                $courseItems[] = [
191
                    'user' => [
192
                        'id' => $user->getId(),
193
                        'name' => $user->getFullName(),
194
                    ],
195
                    'course' => [
196
                        'id' => $course->getId(),
197
                        'title' => $course->getTitle(),
198
                    ],
199
                    'session' => [
200
                        'id' => $session->getId(),
201
                        'title' => $session->getTitle(),
202
                        'startDate' => $session->getAccessStartDate()?->format('Y-m-d'),
203
                        'endDate' => $session->getAccessEndDate()?->format('Y-m-d'),
204
                    ],
205
                ];
206
            }
207
208
            return $courseItems;
209
        }, $results);
210
211
        $flatItems = array_merge(...$items);
212
213
        return $this->json([
214
            'items' => $flatItems,
215
            'count' => \count($flatItems),
216
        ]);
217
    }
218
219
    #[Route('/courses/restartable', name: 'chamilo_core_admin_sessionadmin_courses_restartable', methods: ['GET'])]
220
    public function listRestartables(
221
        Request $request,
222
        GradebookCertificateRepository $repo,
223
        AccessUrlHelper $accessUrlHelper
224
    ): JsonResponse {
225
        $offset = max(0, (int) $request->query->get('offset', 0));
226
        $limit = max(1, (int) $request->query->get('limit', 10));
227
228
        $urlId = $accessUrlHelper->getCurrent()->getId();
229
230
        /** @var SessionRelCourseRelUser[] $rows */
231
        $rows = $repo->findRestartableSessions($urlId, $offset, $limit);
232
233
        $items = array_map(static function (SessionRelCourseRelUser $srcu) {
234
            $session = $srcu->getSession();
235
            $course = $srcu->getCourse();
236
            $user = $srcu->getUser();
237
238
            return [
239
                'user' => ['id' => $user->getId(), 'name' => $user->getFullName()],
240
                'course' => ['id' => $course->getId(), 'title' => $course->getTitle()],
241
                'session' => [
242
                    'id' => $session->getId(),
243
                    'title' => $session->getTitle(),
244
                    'endDate' => $session->getAccessEndDate()?->format('Y-m-d'),
245
                ],
246
            ];
247
        }, $rows);
248
249
        return $this->json([
250
            'items' => $items,
251
            'offset' => $offset,
252
            'limit' => $limit,
253
            'count' => \count($items),
254
        ]);
255
    }
256
257
    #[Route('/courses/extend_week', name: 'chamilo_core_admin_sessionadmin_session_extend_one_week', methods: ['POST'])]
258
    public function extendSessionByWeek(
259
        Request $request,
260
        SessionRelCourseRelUserRepository $sessionRelCourseRelUserRepository,
261
        EntityManagerInterface $entityManager
262
    ): JsonResponse {
263
        $data = json_decode($request->getContent(), true);
264
        $sessionId = (int) ($data['sessionId'] ?? 0);
265
        $userId = (int) ($data['userId'] ?? 0);
266
        $courseId = (int) ($data['courseId'] ?? 0);
267
268
        if (!$sessionId || !$userId || !$courseId) {
269
            return $this->json(['error' => 'Missing data'], Response::HTTP_BAD_REQUEST);
270
        }
271
272
        $rel = $sessionRelCourseRelUserRepository->findOneBy([
273
            'session' => $sessionId,
274
            'user' => $userId,
275
            'course' => $courseId,
276
        ]);
277
278
        if (!$rel) {
279
            return $this->json(['error' => 'Relation not found'], Response::HTTP_NOT_FOUND);
280
        }
281
282
        $session = $rel->getSession();
283
284
        $now = new DateTime('now');
285
        $currentEndDate = $session->getAccessEndDate();
286
287
        $baseDate = $now > $currentEndDate ? $now : $currentEndDate;
288
289
        $newEndDate = clone $baseDate;
290
        $newEndDate->modify('+1 week');
291
292
        $session->setAccessEndDate($newEndDate);
293
        $entityManager->flush();
294
295
        return $this->json([
296
            'success' => true,
297
            'message' => 'Session extended by one week.',
298
            'newEndDate' => $newEndDate->format('Y-m-d'),
299
        ]);
300
    }
301
302
    #[Route('/courses/{id}', name: 'chamilo_core_admin_sessionadmin_course_view', methods: ['GET'])]
303
    public function getCourseForSessionAdmin(int $id): JsonResponse
304
    {
305
        /** @var Course $course */
306
        $course = $this->courseRepository->find($id);
307
308
        if (!$course) {
0 ignored issues
show
introduced by
$course is of type Chamilo\CoreBundle\Entity\Course, thus it always evaluated to true.
Loading history...
309
            return $this->json(['error' => 'Course not found'], Response::HTTP_NOT_FOUND);
310
        }
311
312
        $qb = $this->courseDescriptionRepository->getResourcesByCourse($course);
313
        $items = $qb->getQuery()->getResult();
314
315
        $descriptions = array_map(static function ($d) {
316
            return [
317
                'title' => $d->getTitle(),
318
                'content' => $d->getContent(),
319
            ];
320
        }, $items);
321
322
        $filterForCards = '';
323
        $illustrationUrl = $this->illustrationRepository->getIllustrationUrl(
324
            $course,
325
            $filterForCards,
326
            512
327
        );
328
329
        return $this->json([
330
            'id' => $course->getId(),
331
            'title' => $course->getTitle(),
332
            'code' => $course->getCode(),
333
            'description' => '',
334
            'descriptions' => $descriptions,
335
            'illustrationUrl' => $this->normalizePath($illustrationUrl),
336
        ]);
337
    }
338
339
    #[Route('/users', name: 'chamilo_core_admin_sessionadmin_search_users', methods: ['GET'])]
340
    public function searchUsers(
341
        Request $request,
342
        UserRepository $userRepo,
343
        ExtraFieldRepository $extraFieldRepo,
344
        ExtraFieldValuesRepository $extraFieldValuesRepo,
345
        AccessUrlHelper $accessUrlHelper,
346
        SettingsManager $settingsManager,
347
    ): JsonResponse {
348
        $lastname = $request->query->get('lastname');
349
        $firstname = $request->query->get('firstname');
350
        $extraFilters = $request->query->all('extraFilters');
351
352
        $configuredExtraFieldVariable = $settingsManager->getSetting(
353
            'platform.session_admin_user_subscription_search_extra_field_to_search',
354
            true
355
        );
356
357
        $allUrlsAllowed = 'true' === $settingsManager->getSetting('platform.session_admin_access_to_all_users_on_all_urls', true);
358
359
        $filters = [];
360
        if ($lastname) {
361
            $filters['lastname'] = $lastname;
362
        }
363
        if ($firstname) {
364
            $filters['firstname'] = $firstname;
365
        }
366
        if ($configuredExtraFieldVariable && !empty($extraFilters[$configuredExtraFieldVariable])) {
367
            $filters['extraFilters'] = [
368
                $configuredExtraFieldVariable => $extraFilters[$configuredExtraFieldVariable],
369
            ];
370
        }
371
372
        $currentUrl = $accessUrlHelper->getCurrent();
373
        $accessUrl = $allUrlsAllowed ? null : $currentUrl;
374
375
        $users = $userRepo->findUsersForSessionAdmin(
376
            $filters['lastname'] ?? null,
377
            $filters['firstname'] ?? null,
378
            $filters['extraFilters'] ?? [],
379
            $accessUrl
380
        );
381
382
        $data = [];
383
        foreach ($users as $user) {
384
            $extra = [];
385
            $extraValue = $extraFieldValuesRepo->getValueByVariableAndItem(
386
                $configuredExtraFieldVariable,
387
                $user->getId(),
388
                ExtraField::USER_FIELD_TYPE
389
            );
390
391
            $extra[$configuredExtraFieldVariable] = $extraValue?->getFieldValue();
392
393
            if (!$allUrlsAllowed) {
394
                $localAccess = true;
395
            } else {
396
                $localAccess = $user->getPortals()->exists(
397
                    fn ($key, AccessUrlRelUser $rel) => $rel->getUrl() === $currentUrl
398
                );
399
            }
400
401
            $data[] = [
402
                'id' => $user->getId(),
403
                'lastname' => $user->getLastname(),
404
                'firstname' => $user->getFirstname(),
405
                'fullname' => trim($user->getFirstname().' '.$user->getLastname()),
406
                'email' => $user->getEmail(),
407
                'isActive' => $user->getIsActive(),
408
                'hasLocalAccess' => $localAccess,
409
                'extra' => $extra,
410
            ];
411
        }
412
413
        return $this->json([
414
            'items' => $data,
415
        ]);
416
    }
417
}
418