Passed
Pull Request — master (#6396)
by Angel Fernando Quiroz
13:45 queued 05:26
created

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