Passed
Pull Request — master (#6330)
by
unknown
08:09
created

CourseController   F

Complexity

Total Complexity 128

Size/Duplication

Total Lines 1029
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 576
dl 0
loc 1029
rs 2
c 0
b 0
f 0
wmc 128

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D indexJson() 0 122 19
B checkTermsAndConditionJson() 0 54 10
F autoLaunch() 0 172 30
A isUserEnrolledInAnySession() 0 7 1
A findIntroOfCourse() 0 25 2
B addToolIntro() 0 70 6
B getNextCourse() 0 56 9
B getToolIntro() 0 49 6
A checkEnrollments() 0 21 4
A getAutoLaunchLPId() 0 20 2
A welcome() 0 5 1
A searchCourseTemplates() 0 22 2
A getAutoLaunchExerciseId() 0 20 2
A isUserEnrolledInAnyCourse() 0 8 1
A createCourse() 0 44 4
A redirectTool() 0 34 5
C about() 0 145 13
A updateSettings() 0 46 5
A getCategories() 0 29 5

How to fix   Complexity   

Complex Class

Complex classes like CourseController 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 CourseController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Controller;
8
9
use Chamilo\CoreBundle\Entity\Course;
10
use Chamilo\CoreBundle\Entity\CourseRelUser;
11
use Chamilo\CoreBundle\Entity\ExtraField;
12
use Chamilo\CoreBundle\Entity\SequenceResource;
13
use Chamilo\CoreBundle\Entity\Session;
14
use Chamilo\CoreBundle\Entity\SessionRelUser;
15
use Chamilo\CoreBundle\Entity\Tag;
16
use Chamilo\CoreBundle\Entity\Tool;
17
use Chamilo\CoreBundle\Entity\User;
18
use Chamilo\CoreBundle\Framework\Container;
19
use Chamilo\CoreBundle\Repository\AssetRepository;
20
use Chamilo\CoreBundle\Repository\CourseCategoryRepository;
21
use Chamilo\CoreBundle\Repository\ExtraFieldValuesRepository;
22
use Chamilo\CoreBundle\Repository\LanguageRepository;
23
use Chamilo\CoreBundle\Repository\LegalRepository;
24
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
25
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
26
use Chamilo\CoreBundle\Repository\SequenceResourceRepository;
27
use Chamilo\CoreBundle\Repository\TagRepository;
28
use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
29
use Chamilo\CoreBundle\Service\CourseService;
30
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
31
use Chamilo\CoreBundle\ServiceHelper\UserHelper;
32
use Chamilo\CoreBundle\Settings\SettingsManager;
33
use Chamilo\CoreBundle\Tool\ToolChain;
34
use Chamilo\CourseBundle\Controller\ToolBaseController;
35
use Chamilo\CourseBundle\Entity\CCourseDescription;
36
use Chamilo\CourseBundle\Entity\CLink;
37
use Chamilo\CourseBundle\Entity\CShortcut;
38
use Chamilo\CourseBundle\Entity\CTool;
39
use Chamilo\CourseBundle\Entity\CToolIntro;
40
use Chamilo\CourseBundle\Repository\CCourseDescriptionRepository;
41
use Chamilo\CourseBundle\Repository\CLpRepository;
42
use Chamilo\CourseBundle\Repository\CQuizRepository;
43
use Chamilo\CourseBundle\Repository\CShortcutRepository;
44
use Chamilo\CourseBundle\Repository\CToolRepository;
45
use Chamilo\CourseBundle\Settings\SettingsCourseManager;
46
use Chamilo\CourseBundle\Settings\SettingsFormFactory;
47
use CourseManager;
48
use Database;
49
use Display;
50
use Doctrine\ORM\EntityManagerInterface;
51
use Event;
52
use Exception;
53
use Exercise;
54
use ExtraFieldValue;
55
use Fhaculty\Graph\Graph;
56
use Graphp\GraphViz\GraphViz;
57
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
58
use Symfony\Bundle\SecurityBundle\Security;
59
use Symfony\Component\HttpFoundation\JsonResponse;
60
use Symfony\Component\HttpFoundation\RedirectResponse;
61
use Symfony\Component\HttpFoundation\Request;
62
use Symfony\Component\HttpFoundation\Response;
63
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
64
use Symfony\Component\Routing\Attribute\Route;
65
use Symfony\Component\Security\Http\Attribute\IsGranted;
66
use Symfony\Component\Serializer\SerializerInterface;
67
use Symfony\Component\Validator\Exception\ValidatorException;
68
use Symfony\Contracts\Translation\TranslatorInterface;
69
use UserManager;
70
71
/**
72
 * @author Julio Montoya <[email protected]>
73
 */
74
#[Route('/course')]
75
class CourseController extends ToolBaseController
76
{
77
    public function __construct(
78
        private readonly EntityManagerInterface $em,
79
        private readonly SerializerInterface $serializer,
80
        private readonly UserHelper $userHelper,
81
    ) {}
82
83
    #[IsGranted('ROLE_USER')]
84
    #[Route('/{cid}/checkLegal.json', name: 'chamilo_core_course_check_legal_json')]
85
    public function checkTermsAndConditionJson(
86
        Request $request,
87
        LegalRepository $legalTermsRepo,
88
        LanguageRepository $languageRepository,
89
        ExtraFieldValuesRepository $extraFieldValuesRepository,
90
        SettingsManager $settingsManager
91
    ): Response {
92
        $user = $this->userHelper->getCurrent();
93
        $course = $this->getCourse();
94
        $responseData = [
95
            'redirect' => false,
96
            'url' => '#',
97
        ];
98
99
        if ($user->hasRole('ROLE_STUDENT')
100
            && 'true' === $settingsManager->getSetting('registration.allow_terms_conditions')
101
            && 'course' === $settingsManager->getSetting('platform.load_term_conditions_section')
102
        ) {
103
            $termAndConditionStatus = false;
104
            $extraValue = $extraFieldValuesRepository->findLegalAcceptByItemId($user->getId());
105
            if (!empty($extraValue['value'])) {
106
                $result = $extraValue['value'];
107
                $userConditions = explode(':', $result);
108
                $version = $userConditions[0];
109
                $langId = (int) $userConditions[1];
110
                $realVersion = $legalTermsRepo->getLastVersion($langId);
111
                $termAndConditionStatus = ($version >= $realVersion);
112
            }
113
114
            if (false === $termAndConditionStatus) {
115
                $request->getSession()->set('term_and_condition', ['user_id' => $user->getId()]);
116
117
                $redirect = true;
118
119
                if ('true' === $settingsManager->getSetting('course.allow_public_course_with_no_terms_conditions')
120
                    && Course::OPEN_WORLD === $course->getVisibility()
121
                ) {
122
                    $redirect = false;
123
                }
124
125
                if ($redirect && !$this->isGranted('ROLE_ADMIN')) {
126
                    $responseData = [
127
                        'redirect' => true,
128
                        'url' => '/main/auth/inscription.php',
129
                    ];
130
                }
131
            } else {
132
                $request->getSession()->remove('term_and_condition');
133
            }
134
        }
135
136
        return new JsonResponse($responseData);
137
    }
138
139
    #[Route('/{cid}/home.json', name: 'chamilo_core_course_home_json')]
140
    public function indexJson(
141
        Request $request,
142
        CShortcutRepository $shortcutRepository,
143
        EntityManagerInterface $em,
144
        AssetRepository $assetRepository
145
    ): Response {
146
        $requestData = json_decode($request->getContent(), true);
147
        // Sort behaviour
148
        if (!empty($requestData) && isset($requestData['toolItem'])) {
149
            $index = $requestData['index'];
150
            $toolItem = $requestData['toolItem'];
151
            $toolId = (int) $toolItem['iid'];
152
153
            /** @var CTool $cTool */
154
            $cTool = $em->find(CTool::class, $toolId);
155
156
            if ($cTool) {
157
                $cTool->setPosition($index + 1);
158
                $em->persist($cTool);
159
                $em->flush();
160
            }
161
        }
162
163
        $course = $this->getCourse();
164
        $sessionId = $this->getSessionId();
165
        $isInASession = $sessionId > 0;
166
167
        if (null === $course) {
168
            throw $this->createAccessDeniedException();
169
        }
170
171
        if (empty($sessionId)) {
172
            $this->denyAccessUnlessGranted(CourseVoter::VIEW, $course);
173
        }
174
175
        $sessionHandler = $request->getSession();
176
177
        $userId = 0;
178
179
        $user = $this->userHelper->getCurrent();
180
        if (null !== $user) {
181
            $userId = $user->getId();
182
        }
183
184
        $courseCode = $course->getCode();
185
        $courseId = $course->getId();
186
187
        if ($user && $user->hasRole('ROLE_INVITEE')) {
188
            $isSubscribed = CourseManager::is_user_subscribed_in_course(
189
                $userId,
190
                $courseCode,
191
                $isInASession,
192
                $sessionId
193
            );
194
195
            if (!$isSubscribed) {
196
                throw $this->createAccessDeniedException();
197
            }
198
        }
199
200
        $isSpecialCourse = CourseManager::isSpecialCourse($courseId);
201
202
        if ($user && $isSpecialCourse && (isset($_GET['autoreg']) && 1 === (int) $_GET['autoreg'])
203
            && CourseManager::subscribeUser($userId, $courseId, STUDENT)
204
        ) {
205
            $sessionHandler->set('is_allowed_in_course', true);
206
        }
207
208
        $logInfo = [
209
            'tool' => 'course-main',
210
        ];
211
        Event::registerLog($logInfo);
212
213
        // Deleting the objects
214
        $sessionHandler->remove('toolgroup');
215
        $sessionHandler->remove('_gid');
216
        $sessionHandler->remove('oLP');
217
        $sessionHandler->remove('lpobject');
218
219
        api_remove_in_gradebook();
220
        Exercise::cleanSessionVariables();
221
222
        $shortcuts = [];
223
        if (null !== $user) {
224
            $shortcutQuery = $shortcutRepository->getResources($course->getResourceNode());
225
            $shortcuts = $shortcutQuery->getQuery()->getResult();
226
227
            /** @var CShortcut $shortcut */
228
            foreach ($shortcuts as $shortcut) {
229
                $resourceNode = $shortcut->getShortCutNode();
230
                $cLink = $em->getRepository(CLink::class)->findOneBy(['resourceNode' => $resourceNode]);
231
232
                if ($cLink) {
233
                    $shortcut->setCustomImageUrl(
234
                        $cLink->getCustomImage()
235
                            ? $assetRepository->getAssetUrl($cLink->getCustomImage())
236
                            : null
237
                    );
238
                } else {
239
                    $shortcut->setCustomImageUrl(null);
240
                }
241
            }
242
        }
243
        $responseData = [
244
            'shortcuts' => $shortcuts,
245
            'diagram' => '',
246
        ];
247
248
        $json = $this->serializer->serialize(
249
            $responseData,
250
            'json',
251
            [
252
                'groups' => ['course:read', 'ctool:read', 'tool:read', 'cshortcut:read'],
253
            ]
254
        );
255
256
        return new Response(
257
            $json,
258
            Response::HTTP_OK,
259
            [
260
                'Content-type' => 'application/json',
261
            ]
262
        );
263
    }
264
    #[Route('/{courseId}/next-course', name: 'chamilo_course_next_course')]
265
    public function getNextCourse(
266
        int $courseId,
267
        Request $request,
268
        SequenceResourceRepository $repo,
269
        Security $security,
270
        SettingsManager $settingsManager,
271
        EntityManagerInterface $em
272
    ): JsonResponse {
273
        $sessionId = $request->query->getInt('sid');
274
        $useDependents = $request->query->getBoolean('dependents', false);
275
        $user = $security->getUser();
276
        $userId = $user->getId();
277
278
        if ($useDependents) {
279
            $sequences = $repo->getDependents($courseId, SequenceResource::COURSE_TYPE);
280
            $checked = $repo->checkDependentsForUser($sequences, SequenceResource::COURSE_TYPE, $userId, $sessionId);
281
            $isUnlocked = $repo->checkSequenceAreCompleted($checked);
282
            $sequenceResource = $repo->findRequirementForResource($courseId, SequenceResource::COURSE_TYPE);
283
        } else {
284
            $sequences = $repo->getRequirements($courseId, SequenceResource::COURSE_TYPE);
285
286
            $hasValidRequirement = false;
287
            foreach ($sequences as $sequence) {
288
                foreach ($sequence['requirements'] ?? [] as $resource) {
289
                    if ($resource instanceof Course) {
290
                        $hasValidRequirement = true;
291
                        break 2;
292
                    }
293
                }
294
            }
295
296
            if (!$hasValidRequirement) {
297
                return new JsonResponse([]);
298
            }
299
300
            $checked = $repo->checkRequirementsForUser($sequences, SequenceResource::COURSE_TYPE, $userId, $sessionId);
301
            $isUnlocked = $repo->checkSequenceAreCompleted($checked);
302
            $sequenceResource = $repo->findRequirementForResource($courseId, SequenceResource::COURSE_TYPE);
303
        }
304
305
        $graphImage = null;
306
307
        if ($sequenceResource && $sequenceResource->hasGraph()) {
308
            $graph = $sequenceResource->getSequence()->getUnSerializeGraph();
309
            if ($graph !== null) {
310
                $graph->setAttribute('graphviz.node.fontname', 'arial');
311
                $graphviz = new GraphViz();
312
                $graphImage = $graphviz->createImageSrc($graph);
313
            }
314
        }
315
316
        return new JsonResponse([
317
            'sequenceList' => array_values($checked),
318
            'allowSubscription' => $isUnlocked,
319
            'graph' => $graphImage,
320
        ]);
321
    }
322
323
    /**
324
     * Redirects the page to a tool, following the tools settings.
325
     */
326
    #[Route('/{cid}/tool/{toolName}', name: 'chamilo_core_course_redirect_tool')]
327
    public function redirectTool(
328
        Request $request,
329
        string $toolName,
330
        CToolRepository $repo,
331
        ToolChain $toolChain
332
    ): RedirectResponse {
333
        /** @var CTool|null $tool */
334
        $tool = $repo->findOneBy([
335
            'title' => $toolName,
336
        ]);
337
338
        if (null === $tool) {
339
            throw new NotFoundHttpException($this->trans('Tool not found'));
340
        }
341
342
        $tool = $toolChain->getToolFromName($tool->getTool()->getTitle());
343
        $link = $tool->getLink();
344
345
        if (null === $this->getCourse()) {
346
            throw new NotFoundHttpException($this->trans('Course not found'));
347
        }
348
        $optionalParams = '';
349
350
        $optionalParams = $request->query->get('cert') ? '&cert='.$request->query->get('cert') : '';
351
352
        if (strpos($link, 'nodeId')) {
353
            $nodeId = (string) $this->getCourse()->getResourceNode()->getId();
354
            $link = str_replace(':nodeId', $nodeId, $link);
355
        }
356
357
        $url = $link.'?'.$this->getCourseUrlQuery().$optionalParams;
358
359
        return $this->redirect($url);
360
    }
361
362
    /*public function redirectToShortCut(string $toolName, CToolRepository $repo, ToolChain $toolChain): RedirectResponse
363
     * {
364
     * $tool = $repo->findOneBy([
365
     * 'name' => $toolName,
366
     * ]);
367
     * if (null === $tool) {
368
     * throw new NotFoundHttpException($this->trans('Tool not found'));
369
     * }
370
     * $tool = $toolChain->getToolFromName($tool->getTool()->getTitle());
371
     * $link = $tool->getLink();
372
     * if (strpos($link, 'nodeId')) {
373
     * $nodeId = (string) $this->getCourse()->getResourceNode()->getId();
374
     * $link = str_replace(':nodeId', $nodeId, $link);
375
     * }
376
     * $url = $link.'?'.$this->getCourseUrlQuery();
377
     * return $this->redirect($url);
378
     * }*/
379
380
    /**
381
     * Edit configuration with given namespace.
382
     */
383
    #[Route('/{course}/settings/{namespace}', name: 'chamilo_core_course_settings')]
384
    public function updateSettings(
385
        Request $request,
386
        #[MapEntity(expr: 'repository.find(cid)')]
387
        Course $course,
388
        string $namespace,
389
        SettingsCourseManager $manager,
390
        SettingsFormFactory $formFactory
391
    ): Response {
392
        $this->denyAccessUnlessGranted(CourseVoter::VIEW, $course);
393
394
        $schemaAlias = $manager->convertNameSpaceToService($namespace);
395
        $settings = $manager->load($namespace);
396
397
        $form = $formFactory->create($schemaAlias);
398
399
        $form->setData($settings);
400
        $form->handleRequest($request);
401
402
        if ($form->isSubmitted() && $form->isValid()) {
403
            $messageType = 'success';
404
405
            try {
406
                $manager->setCourse($course);
407
                $manager->save($form->getData());
408
                $message = $this->trans('Update');
409
            } catch (ValidatorException $validatorException) {
410
                $message = $this->trans($validatorException->getMessage());
411
                $messageType = 'error';
412
            }
413
            $this->addFlash($messageType, $message);
414
415
            if ($request->headers->has('referer')) {
416
                return $this->redirect($request->headers->get('referer'));
417
            }
418
        }
419
420
        $schemas = $manager->getSchemas();
421
422
        return $this->render(
423
            '@ChamiloCore/Course/settings.html.twig',
424
            [
425
                'course' => $course,
426
                'schemas' => $schemas,
427
                'settings' => $settings,
428
                'form' => $form,
429
            ]
430
        );
431
    }
432
433
    #[Route('/{id}/about', name: 'chamilo_core_course_about')]
434
    public function about(
435
        Course $course,
436
        IllustrationRepository $illustrationRepository,
437
        CCourseDescriptionRepository $courseDescriptionRepository,
438
        EntityManagerInterface $em,
439
        Request $request
440
    ): Response {
441
        $courseId = $course->getId();
442
443
        $user = $this->userHelper->getCurrent();
444
445
        $fieldsRepo = $em->getRepository(ExtraField::class);
446
447
        /** @var TagRepository $tagRepo */
448
        $tagRepo = $em->getRepository(Tag::class);
449
450
        $courseDescriptions = $courseDescriptionRepository->getResourcesByCourse($course)->getQuery()->getResult();
451
452
        $courseValues = new ExtraFieldValue('course');
453
454
        $urlCourse = api_get_path(WEB_PATH).\sprintf('course/%s/about', $courseId);
455
        $courseTeachers = $course->getTeachersSubscriptions();
456
        $teachersData = [];
457
458
        foreach ($courseTeachers as $teacherSubscription) {
459
            $teacher = $teacherSubscription->getUser();
460
            $userData = [
461
                'complete_name' => UserManager::formatUserFullName($teacher),
462
                'image' => $illustrationRepository->getIllustrationUrl($teacher),
463
                'diploma' => $teacher->getDiplomas(),
464
                'openarea' => $teacher->getOpenarea(),
465
            ];
466
467
            $teachersData[] = $userData;
468
        }
469
470
        /** @var ExtraField $tagField */
471
        $tagField = $fieldsRepo->findOneBy([
472
            'itemType' => ExtraField::COURSE_FIELD_TYPE,
473
            'variable' => 'tags',
474
        ]);
475
476
        $courseTags = [];
477
        if (null !== $tagField) {
478
            $courseTags = $tagRepo->getTagsByItem($tagField, $courseId);
479
        }
480
481
        $courseDescription = $courseObjectives = $courseTopics = $courseMethodology = '';
482
        $courseMaterial = $courseResources = $courseAssessment = '';
483
        $courseCustom = [];
484
        foreach ($courseDescriptions as $descriptionTool) {
485
            switch ($descriptionTool->getDescriptionType()) {
486
                case CCourseDescription::TYPE_DESCRIPTION:
487
                    $courseDescription = $descriptionTool->getContent();
488
489
                    break;
490
491
                case CCourseDescription::TYPE_OBJECTIVES:
492
                    $courseObjectives = $descriptionTool;
493
494
                    break;
495
496
                case CCourseDescription::TYPE_TOPICS:
497
                    $courseTopics = $descriptionTool;
498
499
                    break;
500
501
                case CCourseDescription::TYPE_METHODOLOGY:
502
                    $courseMethodology = $descriptionTool;
503
504
                    break;
505
506
                case CCourseDescription::TYPE_COURSE_MATERIAL:
507
                    $courseMaterial = $descriptionTool;
508
509
                    break;
510
511
                case CCourseDescription::TYPE_RESOURCES:
512
                    $courseResources = $descriptionTool;
513
514
                    break;
515
516
                case CCourseDescription::TYPE_ASSESSMENT:
517
                    $courseAssessment = $descriptionTool;
518
519
                    break;
520
521
                case CCourseDescription::TYPE_CUSTOM:
522
                    $courseCustom[] = $descriptionTool;
523
524
                    break;
525
            }
526
        }
527
528
        $topics = [
529
            'objectives' => $courseObjectives,
530
            'topics' => $courseTopics,
531
            'methodology' => $courseMethodology,
532
            'material' => $courseMaterial,
533
            'resources' => $courseResources,
534
            'assessment' => $courseAssessment,
535
            'custom' => array_reverse($courseCustom),
536
        ];
537
538
        $subscriptionUser = false;
539
540
        if ($user) {
541
            $subscriptionUser = CourseManager::is_user_subscribed_in_course($user->getId(), $course->getCode());
542
        }
543
544
        $allowSubscribe = CourseManager::canUserSubscribeToCourse($course->getCode());
545
546
        $image = Container::getIllustrationRepository()->getIllustrationUrl($course, 'course_picture_medium');
547
548
        $params = [
549
            'course' => $course,
550
            'description' => $courseDescription,
551
            'image' => $image,
552
            'syllabus' => $topics,
553
            'tags' => $courseTags,
554
            'teachers' => $teachersData,
555
            'extra_fields' => $courseValues->getAllValuesForAnItem(
556
                $course->getId(),
557
                null,
558
                true
559
            ),
560
            'subscription' => $subscriptionUser,
561
            'url' => '',
562
            'is_premium' => '',
563
            'token' => '',
564
            'base_url' => $request->getSchemeAndHttpHost(),
565
            'allow_subscribe' => $allowSubscribe,
566
        ];
567
568
        $metaInfo = '<meta property="og:url" content="'.$urlCourse.'" />';
569
        $metaInfo .= '<meta property="og:type" content="website" />';
570
        $metaInfo .= '<meta property="og:title" content="'.$course->getTitle().'" />';
571
        $metaInfo .= '<meta property="og:description" content="'.strip_tags($courseDescription).'" />';
572
        $metaInfo .= '<meta property="og:image" content="'.$image.'" />';
573
574
        $htmlHeadXtra[] = $metaInfo;
575
        $htmlHeadXtra[] = api_get_asset('readmore-js/readmore.js');
576
577
        return $this->render('@ChamiloCore/Course/about.html.twig', $params);
578
    }
579
580
    #[Route('/{id}/welcome', name: 'chamilo_core_course_welcome')]
581
    public function welcome(Course $course): Response
582
    {
583
        return $this->render('@ChamiloCore/Course/welcome.html.twig', [
584
            'course' => $course,
585
        ]);
586
    }
587
588
    private function findIntroOfCourse(Course $course): ?CTool
589
    {
590
        $qb = $this->em->createQueryBuilder();
591
592
        $query = $qb->select('ct')
593
            ->from(CTool::class, 'ct')
594
            ->where('ct.course = :c_id')
595
            ->andWhere('ct.title = :title')
596
            ->andWhere(
597
                $qb->expr()->orX(
598
                    $qb->expr()->eq('ct.session', ':session_id'),
599
                    $qb->expr()->isNull('ct.session')
600
                )
601
            )
602
            ->setParameters([
603
                'c_id' => $course->getId(),
604
                'title' => 'course_homepage',
605
                'session_id' => 0,
606
            ])
607
            ->getQuery()
608
        ;
609
610
        $results = $query->getResult();
611
612
        return \count($results) > 0 ? $results[0] : null;
613
    }
614
615
    #[Route('/{id}/getToolIntro', name: 'chamilo_core_course_gettoolintro')]
616
    public function getToolIntro(Request $request, Course $course, EntityManagerInterface $em): Response
617
    {
618
        $sessionId = (int) $request->get('sid');
619
620
        // $session = $this->getSession();
621
        $responseData = [];
622
        $ctoolRepo = $em->getRepository(CTool::class);
623
        $sessionRepo = $em->getRepository(Session::class);
624
        $createInSession = false;
625
626
        $session = null;
627
628
        if (!empty($sessionId)) {
629
            $session = $sessionRepo->find($sessionId);
630
        }
631
632
        $ctool = $this->findIntroOfCourse($course);
633
634
        if ($session) {
635
            $ctoolSession = $ctoolRepo->findOneBy(['title' => 'course_homepage', 'course' => $course, 'session' => $session]);
636
637
            if (!$ctoolSession) {
638
                $createInSession = true;
639
            } else {
640
                $ctool = $ctoolSession;
641
            }
642
        }
643
644
        if ($ctool) {
645
            $ctoolintroRepo = $em->getRepository(CToolIntro::class);
646
647
            /** @var CToolIntro $ctoolintro */
648
            $ctoolintro = $ctoolintroRepo->findOneBy(['courseTool' => $ctool]);
649
            if ($ctoolintro) {
650
                $responseData = [
651
                    'iid' => $ctoolintro->getIid(),
652
                    'introText' => $ctoolintro->getIntroText(),
653
                    'createInSession' => $createInSession,
654
                    'cToolId' => $ctool->getIid(),
655
                ];
656
            }
657
            $responseData['c_tool'] = [
658
                'iid' => $ctool->getIid(),
659
                'title' => $ctool->getTitle(),
660
            ];
661
        }
662
663
        return new JsonResponse($responseData);
664
    }
665
666
    #[Route('/{id}/addToolIntro', name: 'chamilo_core_course_addtoolintro')]
667
    public function addToolIntro(Request $request, Course $course, EntityManagerInterface $em): Response
668
    {
669
        $data = json_decode($request->getContent());
670
        $sessionId = $data->sid ?? ($data->resourceLinkList[0]->sid ?? 0);
671
        $introText = $data->introText ?? null;
672
673
        $session = $sessionId ? $em->getRepository(Session::class)->find($sessionId) : null;
674
        $ctoolRepo = $em->getRepository(CTool::class);
675
        $ctoolintroRepo = $em->getRepository(CToolIntro::class);
676
677
        $ctoolSession = $ctoolRepo->findOneBy([
678
            'title' => 'course_homepage',
679
            'course' => $course,
680
            'session' => $session,
681
        ]);
682
683
        if (!$ctoolSession) {
684
            $toolEntity = $em->getRepository(Tool::class)->findOneBy(['title' => 'course_homepage']);
685
            if ($toolEntity) {
686
                $ctoolSession = (new CTool())
687
                    ->setTool($toolEntity)
688
                    ->setTitle('course_homepage')
689
                    ->setCourse($course)
690
                    ->setPosition(1)
691
                    ->setVisibility(true)
692
                    ->setParent($course)
693
                    ->setCreator($course->getCreator())
694
                    ->setSession($session)
695
                    ->addCourseLink($course)
696
                ;
697
698
                $em->persist($ctoolSession);
699
                $em->flush();
700
            }
701
        }
702
703
        $ctoolIntro = $ctoolintroRepo->findOneBy(['courseTool' => $ctoolSession]);
704
        if (!$ctoolIntro) {
705
            $ctoolIntro = (new CToolIntro())
706
                ->setCourseTool($ctoolSession)
707
                ->setIntroText($introText ?? '')
708
                ->setParent($course)
709
            ;
710
711
            $em->persist($ctoolIntro);
712
            $em->flush();
713
714
            return new JsonResponse([
715
                'status' => 'created',
716
                'cToolId' => $ctoolSession->getIid(),
717
                'introIid' => $ctoolIntro->getIid(),
718
                'introText' => $ctoolIntro->getIntroText(),
719
            ]);
720
        }
721
722
        if (null !== $introText) {
723
            $ctoolIntro->setIntroText($introText);
724
            $em->persist($ctoolIntro);
725
            $em->flush();
726
727
            return new JsonResponse([
728
                'status' => 'updated',
729
                'cToolId' => $ctoolSession->getIid(),
730
                'introIid' => $ctoolIntro->getIid(),
731
                'introText' => $ctoolIntro->getIntroText(),
732
            ]);
733
        }
734
735
        return new JsonResponse(['status' => 'no_action']);
736
    }
737
738
    #[Route('/check-enrollments', name: 'chamilo_core_check_enrollments', methods: ['GET'])]
739
    public function checkEnrollments(EntityManagerInterface $em, SettingsManager $settingsManager): JsonResponse
740
    {
741
        $user = $this->userHelper->getCurrent();
742
743
        if (!$user) {
744
            return new JsonResponse(['error' => 'User not found'], Response::HTTP_UNAUTHORIZED);
745
        }
746
747
        $isEnrolledInCourses = $this->isUserEnrolledInAnyCourse($user, $em);
748
        $isEnrolledInSessions = $this->isUserEnrolledInAnySession($user, $em);
749
750
        if (!$isEnrolledInCourses && !$isEnrolledInSessions) {
751
            $defaultMenuEntry = $settingsManager->getSetting('platform.default_menu_entry_for_course_or_session');
752
            $isEnrolledInCourses = 'my_courses' === $defaultMenuEntry;
753
            $isEnrolledInSessions = 'my_sessions' === $defaultMenuEntry;
754
        }
755
756
        return new JsonResponse([
757
            'isEnrolledInCourses' => $isEnrolledInCourses,
758
            'isEnrolledInSessions' => $isEnrolledInSessions,
759
        ]);
760
    }
761
762
    #[Route('/categories', name: 'chamilo_core_course_form_lists')]
763
    public function getCategories(
764
        SettingsManager $settingsManager,
765
        AccessUrlHelper $accessUrlHelper,
766
        CourseCategoryRepository $courseCategoriesRepo
767
    ): JsonResponse {
768
        $allowBaseCourseCategory = 'true' === $settingsManager->getSetting('course.allow_base_course_category');
769
        $accessUrlId = $accessUrlHelper->getCurrent()->getId();
770
771
        $categories = $courseCategoriesRepo->findAllInAccessUrl(
772
            $accessUrlId,
773
            $allowBaseCourseCategory
774
        );
775
776
        $data = [];
777
        $categoryToAvoid = '';
778
        if (!$this->isGranted('ROLE_ADMIN')) {
779
            $categoryToAvoid = $settingsManager->getSetting('course.course_category_code_to_use_as_model');
780
        }
781
782
        foreach ($categories as $category) {
783
            $categoryCode = $category->getCode();
784
            if (!empty($categoryToAvoid) && $categoryToAvoid == $categoryCode) {
785
                continue;
786
            }
787
            $data[] = ['id' => $category->getId(), 'name' => $category->__toString()];
788
        }
789
790
        return new JsonResponse($data);
791
    }
792
793
    #[Route('/search_templates', name: 'chamilo_core_course_search_templates')]
794
    public function searchCourseTemplates(
795
        Request $request,
796
        AccessUrlHelper $accessUrlHelper,
797
        CourseRepository $courseRepository
798
    ): JsonResponse {
799
        $searchTerm = $request->query->get('search', '');
800
        $accessUrl = $accessUrlHelper->getCurrent();
801
802
        $user = $this->userHelper->getCurrent();
803
804
        $courseList = $courseRepository->getCoursesInfoByUser($user, $accessUrl, 1, $searchTerm);
805
        $results = ['items' => []];
806
        foreach ($courseList as $course) {
807
            $title = $course['title'];
808
            $results['items'][] = [
809
                'id' => $course['id'],
810
                'name' => $title.' ('.$course['code'].') ',
811
            ];
812
        }
813
814
        return new JsonResponse($results);
815
    }
816
817
    #[Route('/create', name: 'chamilo_core_course_create')]
818
    public function createCourse(
819
        Request $request,
820
        TranslatorInterface $translator,
821
        CourseService $courseService
822
    ): JsonResponse {
823
        $courseData = json_decode($request->getContent(), true);
824
825
        $title = $courseData['name'] ?? null;
826
        $wantedCode = $courseData['code'] ?? null;
827
        $courseLanguage = $courseData['language'] ?? null;
828
        $categoryCode = $courseData['category'] ?? null;
829
        $exemplaryContent = $courseData['fillDemoContent'] ?? false;
830
        $template = $courseData['template'] ?? '';
831
832
        $params = [
833
            'title' => $title,
834
            'wanted_code' => $wantedCode,
835
            'course_language' => $courseLanguage,
836
            'exemplary_content' => $exemplaryContent,
837
            'course_template' => $template,
838
        ];
839
840
        if ($categoryCode) {
841
            $params['course_categories'] = $categoryCode;
842
        }
843
844
        try {
845
            $course = $courseService->createCourse($params);
846
            if ($course) {
847
                return new JsonResponse([
848
                    'success' => true,
849
                    'message' => $translator->trans('Course created successfully.'),
850
                    'courseId' => $course->getId(),
851
                ]);
852
            }
853
        } catch (Exception $e) {
854
            return new JsonResponse([
855
                'success' => false,
856
                'message' => $translator->trans($e->getMessage()),
857
            ], Response::HTTP_BAD_REQUEST);
858
        }
859
860
        return new JsonResponse(['success' => false, 'message' => $translator->trans('An error occurred while creating the course.')]);
861
    }
862
863
    #[Route('/{id}/getAutoLaunchExerciseId', name: 'chamilo_core_course_get_auto_launch_exercise_id', methods: ['GET'])]
864
    public function getAutoLaunchExerciseId(
865
        Request $request,
866
        Course $course,
867
        CQuizRepository $quizRepository,
868
        EntityManagerInterface $em
869
    ): JsonResponse {
870
        $data = $request->getContent();
871
        $data = json_decode($data);
872
        $sessionId = $data->sid ?? 0;
873
874
        $sessionRepo = $em->getRepository(Session::class);
875
        $session = null;
876
        if (!empty($sessionId)) {
877
            $session = $sessionRepo->find($sessionId);
878
        }
879
880
        $autoLaunchExerciseId = $quizRepository->findAutoLaunchableQuizByCourseAndSession($course, $session);
881
882
        return new JsonResponse(['exerciseId' => $autoLaunchExerciseId], Response::HTTP_OK);
883
    }
884
885
    #[Route('/{id}/getAutoLaunchLPId', name: 'chamilo_core_course_get_auto_launch_lp_id', methods: ['GET'])]
886
    public function getAutoLaunchLPId(
887
        Request $request,
888
        Course $course,
889
        CLpRepository $lpRepository,
890
        EntityManagerInterface $em
891
    ): JsonResponse {
892
        $data = $request->getContent();
893
        $data = json_decode($data);
894
        $sessionId = $data->sid ?? 0;
895
896
        $sessionRepo = $em->getRepository(Session::class);
897
        $session = null;
898
        if (!empty($sessionId)) {
899
            $session = $sessionRepo->find($sessionId);
900
        }
901
902
        $autoLaunchLPId = $lpRepository->findAutoLaunchableLPByCourseAndSession($course, $session);
903
904
        return new JsonResponse(['lpId' => $autoLaunchLPId], Response::HTTP_OK);
905
    }
906
907
    private function autoLaunch(): void
908
    {
909
        $autoLaunchWarning = '';
910
        $showAutoLaunchLpWarning = false;
911
        $course_id = api_get_course_int_id();
912
        $lpAutoLaunch = api_get_course_setting('enable_lp_auto_launch');
913
        $session_id = api_get_session_id();
914
        $allowAutoLaunchForCourseAdmins =
915
            api_is_platform_admin()
916
            || api_is_allowed_to_edit(true, true)
917
            || api_is_coach();
918
919
        if (!empty($lpAutoLaunch)) {
920
            if (2 === $lpAutoLaunch) {
921
                // LP list
922
                if ($allowAutoLaunchForCourseAdmins) {
923
                    $showAutoLaunchLpWarning = true;
924
                } else {
925
                    $session_key = 'lp_autolaunch_'.$session_id.'_'.$course_id.'_'.api_get_user_id();
926
                    if (!isset($_SESSION[$session_key])) {
927
                        // Redirecting to the LP
928
                        $url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
929
                        $_SESSION[$session_key] = true;
930
                        header(\sprintf('Location: %s', $url));
931
932
                        exit;
933
                    }
934
                }
935
            } else {
936
                $lp_table = Database::get_course_table(TABLE_LP_MAIN);
937
                $condition = '';
938
                if (!empty($session_id)) {
939
                    $condition = api_get_session_condition($session_id);
940
                    $sql = "SELECT id FROM {$lp_table}
941
                            WHERE c_id = {$course_id} AND autolaunch = 1 {$condition}
942
                            LIMIT 1";
943
                    $result = Database::query($sql);
944
                    // If we found nothing in the session we just called the session_id =  0 autolaunch
945
                    if (0 === Database::num_rows($result)) {
946
                        $condition = '';
947
                    }
948
                }
949
950
                $sql = "SELECT iid FROM {$lp_table}
951
                        WHERE c_id = {$course_id} AND autolaunch = 1 {$condition}
952
                        LIMIT 1";
953
                $result = Database::query($sql);
954
                if (Database::num_rows($result) > 0) {
955
                    $lp_data = Database::fetch_array($result);
956
                    if (!empty($lp_data['iid'])) {
957
                        if ($allowAutoLaunchForCourseAdmins) {
958
                            $showAutoLaunchLpWarning = true;
959
                        } else {
960
                            $session_key = 'lp_autolaunch_'.$session_id.'_'.api_get_course_int_id().'_'.api_get_user_id();
961
                            if (!isset($_SESSION[$session_key])) {
962
                                // Redirecting to the LP
963
                                $url = api_get_path(WEB_CODE_PATH).
964
                                    'lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$lp_data['iid'];
965
966
                                $_SESSION[$session_key] = true;
967
                                header(\sprintf('Location: %s', $url));
968
969
                                exit;
970
                            }
971
                        }
972
                    }
973
                }
974
            }
975
        }
976
977
        if ($showAutoLaunchLpWarning) {
978
            $autoLaunchWarning = get_lang(
979
                'The learning path auto-launch setting is ON. When learners enter this course, they will be automatically redirected to the learning path marked as auto-launch.'
980
            );
981
        }
982
983
        $forumAutoLaunch = (int) api_get_course_setting('enable_forum_auto_launch');
984
        if (1 === $forumAutoLaunch) {
985
            if ($allowAutoLaunchForCourseAdmins) {
986
                if (empty($autoLaunchWarning)) {
987
                    $autoLaunchWarning = get_lang(
988
                        "The forum's auto-launch setting is on. Students will be redirected to the forum tool when entering this course."
989
                    );
990
                }
991
            } else {
992
                $url = api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq();
993
                header(\sprintf('Location: %s', $url));
994
995
                exit;
996
            }
997
        }
998
999
        $exerciseAutoLaunch = (int) api_get_course_setting('enable_exercise_auto_launch');
1000
        if (2 === $exerciseAutoLaunch) {
1001
            if ($allowAutoLaunchForCourseAdmins) {
1002
                if (empty($autoLaunchWarning)) {
1003
                    $autoLaunchWarning = get_lang(
1004
                        'TheExerciseAutoLaunchSettingIsONStudentsWillBeRedirectToTheExerciseList'
1005
                    );
1006
                }
1007
            } else {
1008
                // Redirecting to the document
1009
                $url = api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq();
1010
                header(\sprintf('Location: %s', $url));
1011
1012
                exit;
1013
            }
1014
        } elseif (1 === $exerciseAutoLaunch) {
1015
            if ($allowAutoLaunchForCourseAdmins) {
1016
                if (empty($autoLaunchWarning)) {
1017
                    $autoLaunchWarning = get_lang(
1018
                        'TheExerciseAutoLaunchSettingIsONStudentsWillBeRedirectToAnSpecificExercise'
1019
                    );
1020
                }
1021
            } else {
1022
                // Redirecting to an exercise
1023
                $table = Database::get_course_table(TABLE_QUIZ_TEST);
1024
                $condition = '';
1025
                if (!empty($session_id)) {
1026
                    $condition = api_get_session_condition($session_id);
1027
                    $sql = "SELECT iid FROM {$table}
1028
                            WHERE c_id = {$course_id} AND autolaunch = 1 {$condition}
1029
                            LIMIT 1";
1030
                    $result = Database::query($sql);
1031
                    // If we found nothing in the session we just called the session_id = 0 autolaunch
1032
                    if (0 === Database::num_rows($result)) {
1033
                        $condition = '';
1034
                    }
1035
                }
1036
1037
                $sql = "SELECT iid FROM {$table}
1038
                        WHERE c_id = {$course_id} AND autolaunch = 1 {$condition}
1039
                        LIMIT 1";
1040
                $result = Database::query($sql);
1041
                if (Database::num_rows($result) > 0) {
1042
                    $row = Database::fetch_array($result);
1043
                    $exerciseId = $row['iid'];
1044
                    $url = api_get_path(WEB_CODE_PATH).
1045
                        'exercise/overview.php?exerciseId='.$exerciseId.'&'.api_get_cidreq();
1046
                    header(\sprintf('Location: %s', $url));
1047
1048
                    exit;
1049
                }
1050
            }
1051
        }
1052
1053
        $documentAutoLaunch = (int) api_get_course_setting('enable_document_auto_launch');
1054
        if (1 === $documentAutoLaunch) {
1055
            if ($allowAutoLaunchForCourseAdmins) {
1056
                if (empty($autoLaunchWarning)) {
1057
                    $autoLaunchWarning = get_lang(
1058
                        'The document auto-launch feature configuration is enabled. Learners will be automatically redirected to document tool.'
1059
                    );
1060
                }
1061
            } else {
1062
                // Redirecting to the document
1063
                $url = api_get_path(WEB_CODE_PATH).'document/document.php?'.api_get_cidreq();
1064
                header("Location: $url");
1065
1066
                exit;
1067
            }
1068
        }
1069
1070
        /*  SWITCH TO A DIFFERENT HOMEPAGE VIEW
1071
         the setting homepage_view is adjustable through
1072
         the platform administration section */
1073
        if (!empty($autoLaunchWarning)) {
1074
            $this->addFlash(
1075
                'warning',
1076
                Display::return_message(
1077
                    $autoLaunchWarning,
1078
                    'warning'
1079
                )
1080
            );
1081
        }
1082
    }
1083
1084
    // Implement the real logic to check course enrollment
1085
    private function isUserEnrolledInAnyCourse(User $user, EntityManagerInterface $em): bool
1086
    {
1087
        $enrollmentCount = $em
1088
            ->getRepository(CourseRelUser::class)
1089
            ->count(['user' => $user])
1090
        ;
1091
1092
        return $enrollmentCount > 0;
1093
    }
1094
1095
    // Implement the real logic to check session enrollment
1096
    private function isUserEnrolledInAnySession(User $user, EntityManagerInterface $em): bool
1097
    {
1098
        $enrollmentCount = $em->getRepository(SessionRelUser::class)
1099
            ->count(['user' => $user])
1100
        ;
1101
1102
        return $enrollmentCount > 0;
1103
    }
1104
}
1105