Passed
Pull Request — master (#6351)
by
unknown
08:05
created

CourseService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 12
dl 0
loc 14
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Service;
8
9
use Agenda;
10
use AnnouncementManager;
11
use Answer;
12
use AppPlugin;
13
use Chamilo\CoreBundle\Entity\Course;
14
use Chamilo\CoreBundle\Entity\CourseRelUser;
15
use Chamilo\CoreBundle\Entity\GradebookCategory;
16
use Chamilo\CoreBundle\Entity\GradebookLink;
17
use Chamilo\CoreBundle\Entity\User;
18
use Chamilo\CoreBundle\Repository\CourseCategoryRepository;
19
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
20
use Chamilo\CoreBundle\Repository\Node\UserRepository;
21
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
22
use Chamilo\CoreBundle\Settings\SettingsManager;
23
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
24
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
25
use Chamilo\CourseBundle\Entity\CCourseSetting;
26
use Chamilo\CourseBundle\Entity\CForum;
27
use Chamilo\CourseBundle\Entity\CGroupCategory;
28
use DateTime;
29
use DateTimeZone;
30
use Doctrine\ORM\EntityManager;
31
use DocumentManager;
32
use Exception;
33
use Exercise;
34
use InvalidArgumentException;
35
use Link;
36
use LogicException;
37
use MultipleAnswer;
38
use Symfony\Bundle\SecurityBundle\Security;
39
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
40
use Symfony\Component\Finder\Finder;
41
use Symfony\Component\Finder\SplFileInfo;
42
use Symfony\Component\HttpFoundation\RequestStack;
43
use Symfony\Component\Mailer\MailerInterface;
44
use Symfony\Component\Mime\Email;
45
use Symfony\Contracts\Translation\TranslatorInterface;
46
use Throwable;
47
48
class CourseService
49
{
50
    public const MAX_COURSE_LENGTH_CODE = 40;
51
52
    public function __construct(
53
        private readonly EntityManager $entityManager,
54
        private readonly CourseRepository $courseRepository,
55
        private readonly Security $security,
56
        private readonly CourseCategoryRepository $courseCategoryRepository,
57
        private readonly UserRepository $userRepository,
58
        private readonly SettingsManager $settingsManager,
59
        private readonly TranslatorInterface $translator,
60
        private readonly MailerInterface $mailer,
61
        private readonly EventLoggerService $eventLoggerService,
62
        private readonly ParameterBagInterface $parameterBag,
63
        private readonly RequestStack $requestStack,
64
        private readonly AccessUrlHelper $accessUrlHelper
65
    ) {}
66
67
    public function createCourse(array $params): ?Course
68
    {
69
        if (empty($params['title'])) {
70
            throw new InvalidArgumentException('The course title cannot be empty.');
71
        }
72
73
        if (empty($params['wanted_code'])) {
74
            $params['wanted_code'] = $this->generateCourseCode($params['title']);
75
        }
76
77
        if ($this->courseRepository->courseCodeExists($params['wanted_code'])) {
78
            throw new Exception('The course code already exists: '.$params['wanted_code']);
79
        }
80
81
        $keys = $this->defineCourseKeys($params['wanted_code']);
82
83
        $params = array_merge($params, $keys);
84
        $course = $this->registerCourse($params);
85
        if ($course) {
86
            $this->handlePostCourseCreation($course, $params);
87
        }
88
89
        return $course;
90
    }
91
92
    public function registerCourse(array $rawParams): ?Course
93
    {
94
        try {
95
            /** @var User|null $currentUser */
96
            $currentUser = $this->security->getUser();
97
98
            // Fallback admin user if running from CLI
99
            if (!$currentUser instanceof User) {
100
                $currentUser = $this->getFallbackAdminUser();
101
            }
102
103
            $params = $this->prepareAndValidateCourseData($rawParams);
104
            $accessUrl = $this->accessUrlHelper->getCurrent();
105
            $course = new Course();
106
            $course
107
                ->setTitle($params['title'])
108
                ->setCode($params['code'])
109
                ->setVisualCode($params['visualCode'])
110
                ->setCourseLanguage($params['courseLanguage'])
111
                ->setDescription($this->translator->trans('Course Description'))
112
                ->setVisibility((int) $params['visibility'])
113
                ->setShowScore(1)
114
                ->setDiskQuota((int) $params['diskQuota'])
115
                ->setExpirationDate($params['expirationDate'])
116
                ->setDepartmentName($params['departmentName'] ?? '')
117
                ->setDepartmentUrl($params['departmentUrl'])
118
                ->setSubscribe($params['subscribe'])
119
                ->setSticky($params['sticky'] ?? false)
120
                ->setVideoUrl($params['videoUrl'] ?? '')
121
                ->setUnsubscribe($params['unsubscribe'])
122
                ->setCreator($currentUser)
123
            ;
124
            $course->addAccessUrl($accessUrl);
125
126
            if (!empty($params['categories'])) {
127
                foreach ($params['categories'] as $categoryId) {
128
                    $category = $this->courseCategoryRepository->find($categoryId);
129
                    if ($category) {
130
                        $course->addCategory($category);
131
                    }
132
                }
133
            }
134
135
            $addTeacher = $params['add_user_as_teacher'] ?? true;
136
            $user = $currentUser ?? $this->getFallbackAdminUser();
137
            if (!empty($params['user_id'])) {
138
                $user = $this->userRepository->find((int) $params['user_id']);
139
            }
140
            if ($addTeacher) {
141
                $courseRelTutor = (new CourseRelUser())
142
                    ->setCourse($course)
143
                    ->setUser($user)
144
                    ->setStatus(1)
145
                    ->setTutor(true)
146
                    ->setRelationType(0)
147
                    ->setUserCourseCat(0)
148
                ;
149
                $course->addSubscription($courseRelTutor);
150
            }
151
152
            if (!empty($params['teachers'])) {
153
                foreach ($params['teachers'] as $teacherId) {
154
                    $teacher = $this->userRepository->find($teacherId);
155
                    if ($teacher) {
156
                        $courseRelTeacher = (new CourseRelUser())
157
                            ->setCourse($course)
158
                            ->setUser($teacher)
159
                            ->setStatus(1)
160
                            ->setTutor(false)
161
                            ->setRelationType(0)
162
                            ->setUserCourseCat(0)
163
                        ;
164
                        $course->addSubscription($courseRelTeacher);
165
                    }
166
                }
167
            }
168
169
            $this->courseRepository->create($course);
170
171
            if (!empty($rawParams['exemplary_content'])) {
172
                $this->fillCourse($course, $params);
173
            }
174
175
            if (isset($rawParams['course_template'])) {
176
                $this->useTemplateAsBasisIfRequired(
177
                    $course->getCode(),
178
                    (int) $rawParams['course_template']
179
                );
180
            }
181
182
            return $course;
183
        } catch (Exception $e) {
184
            throw $e;
185
        }
186
    }
187
188
    public function sendEmailToAdmin(Course $course): void
189
    {
190
        $siteName = $this->getDefaultSetting('platform.site_name');
191
        $recipientEmail = $this->getDefaultSetting('admin.administrator_email');
192
        $recipientName = $this->getDefaultSetting('admin.administrator_name').' '.$this->getDefaultSetting('admin.administrator_surname');
193
        $institutionName = $this->getDefaultSetting('platform.institution');
194
        $courseName = $course->getTitle();
195
196
        $subject = $this->translator->trans('New Course Created in')."$siteName - $institutionName";
197
198
        $greeting = $this->translator->trans('email.greeting');
199
        $intro = $this->translator->trans('email.course_created_intro');
200
        $courseNameLabel = $this->translator->trans('email.course_name');
201
202
        $message = "$greeting $recipientName,\n\n";
203
        $message .= "$intro $siteName - $institutionName.\n";
204
        $message .= "$courseNameLabel $courseName\n";
205
        $message .= $this->translator->trans('Course name: ').$course->getTitle()."\n";
206
207
        foreach ($course->getCategories() as $category) {
208
            $message .= $this->translator->trans('Category: ').$category->getCode()."\n";
209
        }
210
211
        $message .= $this->translator->trans('Coach: ').$course->getTutorName()."\n";
212
        $message .= $this->translator->trans('Language: ').$course->getCourseLanguage();
213
214
        $email = (new Email())
215
            ->from($recipientEmail)
216
            ->to($recipientEmail)
217
            ->subject($subject)
218
            ->text($message)
219
        ;
220
221
        $this->mailer->send($email);
222
    }
223
224
    public function defineCourseKeys(
225
        string $wantedCode,
226
        string $prefixForAll = '',
227
        string $prefixForPath = '',
228
        bool $addUniquePrefix = false,
229
        bool $useCodeIndependentKeys = true
230
    ): array {
231
        $wantedCode = $this->generateCourseCode($wantedCode);
232
        $keysCourseCode = $useCodeIndependentKeys ? $wantedCode : '';
233
234
        $uniquePrefix = $addUniquePrefix ? substr(md5(uniqid((string) rand(), true)), 0, 10) : '';
235
236
        $keys = [];
237
        $finalSuffix = ['CourseId' => '', 'CourseDir' => ''];
238
        $limitNumTry = 100;
239
        $tryCount = 0;
240
241
        $keysAreUnique = false;
242
243
        while (!$keysAreUnique && $tryCount < $limitNumTry) {
244
            $keysCourseId = $prefixForAll.$uniquePrefix.$keysCourseCode.$finalSuffix['CourseId'];
245
            $keysCourseRepository = $prefixForPath.$uniquePrefix.$wantedCode.$finalSuffix['CourseDir'];
246
247
            if ($this->courseRepository->courseCodeExists($keysCourseId)) {
248
                $finalSuffix['CourseId'] = substr(md5(uniqid((string) rand(), true)), 0, 4);
249
                $tryCount++;
250
            } else {
251
                $keysAreUnique = true;
252
            }
253
        }
254
255
        if ($keysAreUnique) {
256
            $keys = [
257
                'code' => $keysCourseCode,
258
                'visual_code' => $keysCourseId,
259
                'directory' => $keysCourseRepository,
260
            ];
261
        }
262
263
        return $keys;
264
    }
265
266
    public function fillCourse(Course $course, array $params): void
267
    {
268
        $entityManager = $this->entityManager;
269
270
        $this->insertCourseSettings($course);
271
272
        $this->createGroupCategory($course);
273
274
        $gradebook = $this->createRootGradebook($course);
275
276
        if ('true' === $this->settingsManager->getSetting('course.example_material_course_creation')) {
277
            $this->insertExampleContent($course, $gradebook);
278
        }
279
280
        $this->installCoursePlugins($course->getId());
281
282
        $entityManager->flush();
283
    }
284
285
    private function insertCourseSettings(Course $course): void
286
    {
287
        $defaultEmailExerciseAlert = 0;
288
        if ('true' === $this->settingsManager->getSetting('exercise.email_alert_manager_on_new_quiz')) {
289
            $defaultEmailExerciseAlert = 1;
290
        }
291
292
        $settings = [
293
            'email_alert_manager_on_new_doc' => ['title' => '', 'default' => 0, 'category' => 'work'],
294
            'email_alert_on_new_doc_dropbox' => ['default' => 0, 'category' => 'dropbox'],
295
            'allow_user_edit_agenda' => ['default' => 0, 'category' => 'agenda'],
296
            'allow_user_edit_announcement' => ['default' => 0, 'category' => 'announcement'],
297
            'email_alert_manager_on_new_quiz' => ['default' => $defaultEmailExerciseAlert, 'category' => 'quiz'],
298
            'allow_user_image_forum' => ['default' => 1, 'category' => 'forum'],
299
            'course_theme' => ['default' => '', 'category' => 'theme'],
300
            'allow_learning_path_theme' => ['default' => 1, 'category' => 'theme'],
301
            'allow_open_chat_window' => ['default' => 1, 'category' => 'chat'],
302
            'email_alert_to_teacher_on_new_user_in_course' => ['default' => 0, 'category' => 'registration'],
303
            'allow_user_view_user_list' => ['default' => 1, 'category' => 'user'],
304
            'display_info_advance_inside_homecourse' => ['default' => 1, 'category' => 'thematic_advance'],
305
            'email_alert_students_on_new_homework' => ['default' => 0, 'category' => 'work'],
306
            'enable_lp_auto_launch' => ['default' => 0, 'category' => 'learning_path'],
307
            'enable_exercise_auto_launch' => ['default' => 0, 'category' => 'exercise'],
308
            'enable_document_auto_launch' => ['default' => 0, 'category' => 'document'],
309
            'pdf_export_watermark_text' => ['default' => '', 'category' => 'learning_path'],
310
            'allow_public_certificates' => [
311
                'default' => 'true' === $this->settingsManager->getSetting('course.allow_public_certificates') ? 1 : '',
312
                'category' => 'certificates',
313
            ],
314
            'documents_default_visibility' => ['default' => 'visible', 'category' => 'document'],
315
            'show_course_in_user_language' => ['default' => 2, 'category' => null],
316
            'email_to_teachers_on_new_work_feedback' => ['default' => 1, 'category' => null],
317
        ];
318
319
        foreach ($settings as $variable => $setting) {
320
            $courseSetting = new CCourseSetting();
321
            $courseSetting->setCId($course->getId());
322
            $courseSetting->setVariable($variable);
323
            $courseSetting->setTitle($setting['title'] ?? '');
324
            $courseSetting->setValue((string) $setting['default']);
325
            $courseSetting->setCategory($setting['category'] ?? '');
326
327
            $this->entityManager->persist($courseSetting);
328
        }
329
330
        $this->entityManager->flush();
331
    }
332
333
    private function createGroupCategory(Course $course): void
334
    {
335
        $groupCategory = new CGroupCategory();
336
        $groupCategory
337
            ->setTitle($this->translator->trans('Default groups'))
338
            ->setParent($course)
339
            ->addCourseLink($course)
340
        ;
341
342
        $this->entityManager->persist($groupCategory);
343
        $this->entityManager->flush();
344
    }
345
346
    private function createRootGradebook(Course $course): GradebookCategory
347
    {
348
        /** @var User $currentUser */
349
        $currentUser = $this->security->getUser();
350
        if (!$currentUser instanceof User) {
351
            $currentUser = $this->getFallbackAdminUser();
352
        }
353
354
        if (!$currentUser) {
355
            throw new LogicException('There is no user currently authenticated..');
356
        }
357
358
        $gradebookCategory = new GradebookCategory();
359
        $gradebookCategory
360
            ->setTitle($course->getCode())
361
            ->setLocked(0)
362
            ->setGenerateCertificates(false)
363
            ->setDescription('')
364
            ->setCourse($course)
365
            ->setWeight(100)
366
            ->setVisible(false)
367
            ->setCertifMinScore(75)
368
            ->setUser($currentUser)
369
        ;
370
371
        $this->entityManager->persist($gradebookCategory);
372
        $this->entityManager->flush();
373
374
        return $gradebookCategory;
375
    }
376
377
    private function insertExampleContent(Course $course, GradebookCategory $gradebook): void
378
    {
379
        /** @var User $currentUser */
380
        $currentUser = $this->security->getUser();
381
        if (!$currentUser instanceof User) {
382
            $currentUser = $this->getFallbackAdminUser();
383
        }
384
385
        $files = [
386
            ['path' => '/audio', 'title' => $this->translator->trans('Audio'), 'filetype' => 'folder', 'size' => 0],
387
            ['path' => '/images', 'title' => $this->translator->trans('Images'), 'filetype' => 'folder', 'size' => 0],
388
            ['path' => '/images/gallery', 'title' => $this->translator->trans('Gallery'), 'filetype' => 'folder', 'size' => 0],
389
            ['path' => '/video', 'title' => $this->translator->trans('Video'), 'filetype' => 'folder', 'size' => 0],
390
        ];
391
        $paths = [];
392
        $courseInfo = ['real_id' => $course->getId(), 'code' => $course->getCode()];
393
        foreach ($files as $file) {
394
            $doc = DocumentManager::addDocument(
395
                $courseInfo,
396
                $file['path'],
397
                $file['filetype'],
398
                $file['size'],
399
                $file['title'],
400
                null,
401
                0,
402
                null,
403
                0,
404
                0,
405
                0,
406
                false
407
            );
408
            $paths[$file['path']] = $doc->getIid();
409
        }
410
411
        $projectDir = $this->parameterBag->get('kernel.project_dir');
412
        $defaultPath = $projectDir.'/public/img/document';
413
414
        $request = $this->requestStack->getCurrentRequest();
415
        $baseUrl = $request->getSchemeAndHttpHost().$request->getBasePath();
416
417
        $finder = new Finder();
418
        $finder->in($defaultPath);
419
420
        /** @var SplFileInfo $file */
421
        foreach ($finder as $file) {
422
            $parentName = \dirname(str_replace($defaultPath, '', $file->getRealPath()));
423
            if ('/' === $parentName || '/certificates' === $parentName) {
424
                continue;
425
            }
426
427
            $title = $file->getFilename();
428
            $parentId = $paths[$parentName];
429
430
            if ($file->isDir()) {
431
                $realPath = str_replace($defaultPath, '', $file->getRealPath());
432
                $document = DocumentManager::addDocument(
433
                    $courseInfo,
434
                    $realPath,
435
                    'folder',
436
                    null,
437
                    $title,
438
                    '',
439
                    null,
440
                    null,
441
                    null,
442
                    null,
443
                    null,
444
                    false,
445
                    null,
446
                    $parentId,
447
                    $file->getRealPath()
448
                );
449
                $paths[$realPath] = $document->getIid();
450
            } else {
451
                $realPath = str_replace($defaultPath, '', $file->getRealPath());
452
                $document = DocumentManager::addDocument(
453
                    $courseInfo,
454
                    $realPath,
455
                    'file',
456
                    $file->getSize(),
457
                    $title,
458
                    '',
459
                    null,
460
                    null,
461
                    null,
462
                    null,
463
                    null,
464
                    false,
465
                    null,
466
                    $parentId,
467
                    $file->getRealPath()
468
                );
469
            }
470
        }
471
472
        $now = new DateTime('now', new DateTimeZone('UTC'));
473
        $formattedNow = $now->format('Y-m-d H:i:s');
474
475
        $agenda = new Agenda('course');
476
        $agenda->set_course($courseInfo);
477
        $agenda->addEvent(
478
            $formattedNow,
479
            $formattedNow,
480
            0,
481
            $this->translator->trans('Course creation'),
482
            $this->translator->trans('This course was created at this time'),
483
            ['everyone' => 'everyone']
484
        );
485
486
        /*  Links tool */
487
        $link = new Link();
488
        $link->setCourse($courseInfo);
489
        $links = [
490
            [
491
                'c_id' => $course->getId(),
492
                'url' => 'http://www.google.com',
493
                'title' => 'Quick and powerful search engine',
494
                'description' => $this->translator->trans('Quick and powerful search engine'),
495
                'category_id' => 0,
496
                'on_homepage' => 0,
497
                'target' => '_self',
498
                'session_id' => 0,
499
            ],
500
            [
501
                'c_id' => $course->getId(),
502
                'url' => 'http://www.wikipedia.org',
503
                'title' => 'Free online encyclopedia',
504
                'description' => $this->translator->trans('Free online encyclopedia'),
505
                'category_id' => 0,
506
                'on_homepage' => 0,
507
                'target' => '_self',
508
                'session_id' => 0,
509
            ],
510
        ];
511
512
        foreach ($links as $params) {
513
            $link->save($params, false, false);
514
        }
515
516
        /* Announcement tool */
517
        AnnouncementManager::add_announcement(
518
            $courseInfo,
519
            0,
520
            $this->translator->trans('This is an announcement example'),
521
            $this->translator->trans('This is an announcement example. Only trainers are allowed to publish announcements.'),
522
            ['everyone' => 'everyone'],
523
            null,
524
            null,
525
            $formattedNow
526
        );
527
528
        /*  Exercise tool */
529
        $exercise = new Exercise($course->getId());
530
        $exercise->exercise = $this->translator->trans('Sample test');
531
        $html = '<table width="100%" border="0" cellpadding="0" cellspacing="0">
532
                        <tr>
533
                        <td width="220" valign="top" align="left">
534
                            <img src="'.$baseUrl.'/public/img/document/images/mr_chamilo/doubts.png">
535
                        </td>
536
                        <td valign="top" align="left">'.$this->translator->trans('Irony').'</td></tr>
537
                    </table>';
538
        $exercise->type = 1;
539
        $exercise->setRandom(0);
540
        $exercise->active = 1;
541
        $exercise->results_disabled = 0;
542
        $exercise->description = $html;
543
        $exercise->save();
544
545
        $question = new MultipleAnswer();
546
        $question->course = $courseInfo;
547
        $question->question = $this->translator->trans('Socratic irony is...');
548
        $question->description = $this->translator->trans('(more than one answer can be true)');
549
        $question->weighting = 10;
550
        $question->position = 1;
551
        $question->course = $courseInfo;
552
        $question->save($exercise);
553
        $questionId = $question->id;
554
555
        $answer = new Answer($questionId, $courseInfo['real_id']);
556
        $answer->createAnswer($this->translator->trans('Ridiculise one\'s interlocutor in order to have him concede he is wrong.'), 0, $this->translator->trans('No. Socratic irony is not a matter of psychology, it concerns argumentation.'), -5, 1);
557
        $answer->createAnswer($this->translator->trans('Admit one\'s own errors to invite one\'s interlocutor to do the same.'), 0, $this->translator->trans('No. Socratic irony is not a seduction strategy or a method based on the example.'), -5, 2);
558
        $answer->createAnswer($this->translator->trans('Compell one\'s interlocutor, by a series of questions and sub-questions, to admit he doesn\'t know what he claims to know.'), 1, $this->translator->trans('Indeed'), 5, 3);
559
        $answer->createAnswer($this->translator->trans('Use the Principle of Non Contradiction to force one\'s interlocutor into a dead end.'), 1, $this->translator->trans('This answer is not false. It is true that the revelation of the interlocutor\'s ignorance means showing the contradictory conclusions where lead his premisses.'), 5, 4);
560
        $answer->save();
561
562
        // Forums.
563
        $params = [
564
            'forum_category_title' => $this->translator->trans('Example Forum Category'),
565
            'forum_category_comment' => '',
566
        ];
567
568
        $forumCategoryId = saveForumCategory($params, $courseInfo, false);
569
570
        $params = [
571
            'forum_category' => $forumCategoryId,
572
            'forum_title' => $this->translator->trans('Example Forum'),
573
            'forum_comment' => '',
574
            'default_view_type_group' => ['default_view_type' => 'flat'],
575
        ];
576
577
        $forumId = store_forum($params, $courseInfo, true);
578
        $repo = $this->entityManager->getRepository(CForum::class);
579
        $forumEntity = $repo->find($forumId);
580
581
        $params = [
582
            'post_title' => $this->translator->trans('Example Thread'),
583
            'forum_id' => $forumId,
584
            'post_text' => $this->translator->trans('Example ThreadContent'),
585
            'calification_notebook_title' => '',
586
            'numeric_calification' => '',
587
            'weight_calification' => '',
588
            'forum_category' => $forumCategoryId,
589
            'thread_peer_qualify' => 0,
590
        ];
591
592
        saveThread($forumEntity, $params, $courseInfo, false);
593
594
        $this->createExampleGradebookContent($course, $gradebook, $exercise->id);
595
    }
596
597
    private function createExampleGradebookContent(Course $course, GradebookCategory $parentCategory, int $refId): void
598
    {
599
        $manager = $this->entityManager;
600
601
        /* Gradebook tool */
602
        $courseCode = $course->getCode();
603
604
        $childGradebookCategory = new GradebookCategory();
605
        $childGradebookCategory->setTitle($courseCode);
606
        $childGradebookCategory->setLocked(0);
607
        $childGradebookCategory->setGenerateCertificates(false);
608
        $childGradebookCategory->setDescription('');
609
        $childGradebookCategory->setCourse($course);
610
        $childGradebookCategory->setWeight(100);
611
        $childGradebookCategory->setVisible(true);
612
        $childGradebookCategory->setCertifMinScore(75);
613
        $childGradebookCategory->setParent($parentCategory);
614
        $childGradebookCategory->setUser(api_get_user_entity());
615
616
        $manager->persist($childGradebookCategory);
617
        $manager->flush();
618
619
        $gradebookLink = new GradebookLink();
620
621
        $gradebookLink->setType(1);
622
        $gradebookLink->setRefId($refId);
623
        $gradebookLink->setUser(api_get_user_entity());
624
        $gradebookLink->setCourse($course);
625
        $gradebookLink->setCategory($childGradebookCategory);
626
        $gradebookLink->setCreatedAt(new DateTime());
627
        $gradebookLink->setWeight(100);
628
        $gradebookLink->setVisible(1);
629
        $gradebookLink->setLocked(0);
630
631
        $manager->persist($gradebookLink);
632
        $manager->flush();
633
    }
634
635
    private function installCoursePlugins(int $courseId): void
636
    {
637
        $app_plugin = new AppPlugin();
638
        $app_plugin->install_course_plugins($courseId);
639
    }
640
641
    public function useTemplateAsBasisIfRequired($courseCode, $courseTemplate): void
642
    {
643
        $templateSetting = $this->settingsManager->getSetting('course.course_creation_use_template');
644
        $teacherCanSelectCourseTemplate = 'true' === $this->settingsManager->getSetting('course.teacher_can_select_course_template');
645
        $courseTemplate = isset($courseTemplate) ? (int) $courseTemplate : 0;
646
647
        $useTemplate = false;
648
        if ($teacherCanSelectCourseTemplate && $courseTemplate > 0) {
649
            $useTemplate = true;
650
            $originCourse = $this->courseRepository->findCourseAsArray((int) $courseTemplate);
651
        } elseif (!empty($templateSetting)) {
652
            $useTemplate = true;
653
            $originCourse = $this->courseRepository->findCourseAsArray((int) $templateSetting);
654
        }
655
656
        if ($useTemplate && !empty($originCourse)) {
657
            try {
658
                $originCourse['official_code'] = $originCourse['code'];
659
                $cb = new CourseBuilder(null, $originCourse);
660
                $course = $cb->build(null, $originCourse['code']);
661
                $cr = new CourseRestorer($course);
662
                $cr->set_file_option();
663
                $cr->restore($courseCode);
664
            } catch (Exception $e) {
665
                error_log('Error during course template application: '.$e->getMessage());
666
            } catch (Throwable $t) {
667
                error_log('Unexpected error during course template application: '.$t->getMessage());
668
            }
669
        }
670
    }
671
672
    private function prepareAndValidateCourseData(array $params): array
673
    {
674
        $title = str_replace('&amp;', '&', $params['title'] ?? '');
675
        $code = $params['code'] ?? '';
676
        $visualCode = $params['visual_code'] ?? '';
677
        $directory = $params['directory'] ?? '';
678
        $tutorName = $params['tutor_name'] ?? null;
679
        $courseLanguage = !empty($params['course_language']) ? $params['course_language'] : $this->getDefaultSetting('language.platform_language');
680
        $departmentName = $params['department_name'] ?? null;
681
        $departmentUrl = $this->fixDepartmentUrl($params['department_url'] ?? '');
682
        $diskQuota = $params['disk_quota'] ?? $this->getDefaultSetting('document.default_document_quotum');
683
        $visibility = $params['visibility'] ?? $this->getDefaultSetting('course.courses_default_creation_visibility', Course::OPEN_PLATFORM);
684
        $subscribe = $params['subscribe'] ?? (Course::OPEN_PLATFORM == $visibility);
685
        $unsubscribe = $params['unsubscribe'] ?? false;
686
        $expirationDate = $params['expiration_date'] ?? $this->getFutureExpirationDate();
687
        $teachers = $params['teachers'] ?? [];
688
        $categories = $params['course_categories'] ?? [];
689
        $notifyAdmins = $this->getDefaultSetting('course.send_email_to_admin_when_create_course');
690
691
        $errors = [];
692
        if (empty($code)) {
693
            $errors[] = 'courseSysCode is missing';
694
        }
695
        if (empty($visualCode)) {
696
            $errors[] = 'courseScreenCode is missing';
697
        }
698
        if (empty($directory)) {
699
            $errors[] = 'courseRepository is missing';
700
        }
701
        if (empty($title)) {
702
            $errors[] = 'title is missing';
703
        }
704
        if ($visibility < 0 || $visibility > 4) {
705
            $errors[] = 'visibility is invalid';
706
        }
707
708
        if (!empty($errors)) {
709
            throw new Exception(implode(', ', $errors));
710
        }
711
712
        return [
713
            'title' => $title,
714
            'code' => $code,
715
            'visualCode' => $visualCode,
716
            'directory' => $directory,
717
            'tutorName' => $tutorName,
718
            'courseLanguage' => $courseLanguage,
719
            'departmentName' => $departmentName,
720
            'departmentUrl' => $departmentUrl,
721
            'diskQuota' => $diskQuota,
722
            'visibility' => $visibility,
723
            'subscribe' => $subscribe,
724
            'unsubscribe' => $unsubscribe,
725
            'expirationDate' => new DateTime($expirationDate),
726
            'teachers' => $teachers,
727
            'categories' => $categories,
728
            'notifyAdmins' => $notifyAdmins,
729
        ];
730
    }
731
732
    private function getDefaultSetting(string $name, $default = null)
733
    {
734
        $settingValue = $this->settingsManager->getSetting($name);
735
736
        return null !== $settingValue ? $settingValue : $default;
737
    }
738
739
    private function fixDepartmentUrl(string $url): string
740
    {
741
        if (!empty($url) && !str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
742
            return 'http://'.$url;
743
        }
744
745
        return 'http://' === $url ? '' : $url;
746
    }
747
748
    private function getFutureExpirationDate(): string
749
    {
750
        return (new DateTime())->modify('+1 year')->format('Y-m-d H:i:s');
751
    }
752
753
    private function generateCourseCode(string $title): string
754
    {
755
        $cleanTitle = preg_replace('/[^A-Z0-9]/', '', strtoupper($this->replaceDangerousChar($title)));
756
757
        return substr($cleanTitle, 0, self::MAX_COURSE_LENGTH_CODE);
758
    }
759
760
    private function replaceDangerousChar(string $text): string
761
    {
762
        $encoding = mb_detect_encoding($text, mb_detect_order(), true);
763
        if ('UTF-8' !== $encoding) {
764
            $text = mb_convert_encoding($text, 'UTF-8', $encoding);
765
        }
766
767
        $text = str_replace(' ', '-', $text);
768
        $text = preg_replace('/[^-\w]+/', '', $text);
769
770
        return preg_replace('/\.+$/', '', $text);
771
    }
772
773
    private function handlePostCourseCreation(Course $course, array $params): void
774
    {
775
        if ($params['notifyAdmins'] ?? false) {
776
            $this->sendEmailToAdmin($course);
777
        }
778
779
        $currentUser = $this->security->getUser();
780
        if (!$currentUser instanceof User) {
781
            $currentUser = $this->getFallbackAdminUser();
782
        }
783
784
        $this->eventLoggerService->addEvent(
785
            'course_created',
786
            'course_id',
787
            $course->getId(),
788
            null,
789
            $currentUser->getId(),
790
            $course->getId()
791
        );
792
    }
793
794
    private function getFallbackAdminUser(): User
795
    {
796
        $qb = $this->entityManager->createQueryBuilder();
797
798
        $qb->select('u')
799
            ->from(User::class, 'u')
800
            ->where('u.roles LIKE :role')
801
            ->setParameter('role', '%ROLE_ADMIN%')
802
            ->setMaxResults(1);
803
804
        $user = $qb->getQuery()->getOneOrNullResult();
805
806
        if (!$user instanceof User) {
807
            throw new \RuntimeException('No admin user found for fallback.');
808
        }
809
810
        return $user;
811
    }
812
}
813