Passed
Pull Request — master (#6673)
by
unknown
08:47
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 RuntimeException;
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 CourseHelper
49
{
50
    public const MAX_COURSE_LENGTH_CODE = 40;
51
    private bool $debug = false;
52
53
    public function __construct(
54
        private readonly EntityManager $entityManager,
55
        private readonly CourseRepository $courseRepository,
56
        private readonly Security $security,
57
        private readonly CourseCategoryRepository $courseCategoryRepository,
58
        private readonly UserRepository $userRepository,
59
        private readonly SettingsManager $settingsManager,
60
        private readonly TranslatorInterface $translator,
61
        private readonly MailerInterface $mailer,
62
        private readonly EventLoggerHelper $eventLoggerHelper,
63
        private readonly ParameterBagInterface $parameterBag,
64
        private readonly RequestStack $requestStack,
65
        private readonly AccessUrlHelper $accessUrlHelper
66
    ) {}
67
68
    public function createCourse(array $params): ?Course
69
    {
70
        $this->debugLog('createCourse:start', [
71
            'title' => $params['title'] ?? null,
72
            'wanted_code' => $params['wanted_code'] ?? null,
73
            'exemplary_content' => !empty($params['exemplary_content']),
74
        ]);
75
76
        if (empty($params['title'])) {
77
            throw new InvalidArgumentException('The course title cannot be empty.');
78
        }
79
80
        if (empty($params['wanted_code'])) {
81
            $params['wanted_code'] = $this->generateCourseCode($params['title']);
82
            $this->debugLog('createCourse:generatedWantedCode', ['wanted_code' => $params['wanted_code']]);
83
        }
84
85
        if ($this->courseRepository->courseCodeExists($params['wanted_code'])) {
86
            $this->debugLog('createCourse:duplicateCode', ['wanted_code' => $params['wanted_code']]);
87
            throw new Exception('The course code already exists: '.$params['wanted_code']);
88
        }
89
90
        $keys = $this->defineCourseKeys($params['wanted_code']);
91
        $this->debugLog('createCourse:keys', $keys);
92
93
        $params = array_merge($params, $keys);
94
        $course = $this->registerCourse($params);
95
        if ($course) {
96
            $this->debugLog('createCourse:registered', ['courseId' => $course->getId()]);
97
            $this->handlePostCourseCreation($course, $params);
98
            $this->debugLog('createCourse:done', ['courseId' => $course->getId()]);
99
        }
100
101
        return $course;
102
    }
103
104
    public function registerCourse(array $rawParams): ?Course
105
    {
106
        $this->debugLog('registerCourse:start', [
107
            'exemplary_content_raw' => !empty($rawParams['exemplary_content']),
108
            'has_template' => isset($rawParams['course_template']) && $rawParams['course_template'] !== '',
109
        ]);
110
111
        try {
112
            /** @var User|null $currentUser */
113
            $currentUser = $this->security->getUser();
114
115
            // Fallback admin user if running from CLI
116
            if (!$currentUser instanceof User) {
117
                $currentUser = $this->getFallbackAdminUser();
118
                $this->debugLog('registerCourse:fallbackAdmin', ['adminId' => $currentUser->getId()]);
119
            }
120
121
            $params = $this->prepareAndValidateCourseData($rawParams);
122
            $params['_exemplary_content_request'] = !empty($rawParams['exemplary_content']);
123
124
            $accessUrl = $this->accessUrlHelper->getCurrent();
125
            $course = new Course();
126
            $course
127
                ->setTitle($params['title'])
128
                ->setCode($params['code'])
129
                ->setVisualCode($params['visualCode'])
130
                ->setCourseLanguage($params['courseLanguage'])
131
                ->setDescription($this->translator->trans('Course Description'))
132
                ->setVisibility((int) $params['visibility'])
133
                ->setShowScore(1)
134
                ->setDiskQuota((int) $params['diskQuota'])
135
                ->setExpirationDate($params['expirationDate'])
136
                ->setDepartmentName($params['departmentName'] ?? '')
137
                ->setDepartmentUrl($params['departmentUrl'])
138
                ->setSubscribe($params['subscribe'])
139
                ->setSticky($params['sticky'] ?? false)
140
                ->setVideoUrl($params['videoUrl'] ?? '')
141
                ->setUnsubscribe($params['unsubscribe'])
142
                ->setCreator($currentUser)
143
            ;
144
            $course->addAccessUrl($accessUrl);
145
146
            if (!empty($params['categories'])) {
147
                $this->debugLog('registerCourse:categoriesAttach', ['count' => count($params['categories'])]);
148
                foreach ($params['categories'] as $categoryId) {
149
                    $category = $this->courseCategoryRepository->find($categoryId);
150
                    if ($category) {
151
                        $course->addCategory($category);
152
                    }
153
                }
154
            }
155
156
            $addTeacher = $params['add_user_as_teacher'] ?? true;
157
            $user = $currentUser ?? $this->getFallbackAdminUser();
158
            if (!empty($params['user_id'])) {
159
                $user = $this->userRepository->find((int) $params['user_id']);
160
                $this->debugLog('registerCourse:explicitUser', ['userId' => $user?->getId()]);
161
            }
162
            if ($addTeacher) {
163
                $courseRelTutor = (new CourseRelUser())
164
                    ->setCourse($course)
165
                    ->setUser($user)
166
                    ->setStatus(1)
167
                    ->setTutor(true)
168
                    ->setRelationType(0)
169
                    ->setUserCourseCat(0)
170
                ;
171
                $course->addSubscription($courseRelTutor);
172
            }
173
174
            if (!empty($params['teachers'])) {
175
                $this->debugLog('registerCourse:additionalTeachers', ['count' => count($params['teachers'])]);
176
                foreach ($params['teachers'] as $teacherId) {
177
                    $teacher = $this->userRepository->find($teacherId);
178
                    if ($teacher) {
179
                        $courseRelTeacher = (new CourseRelUser())
180
                            ->setCourse($course)
181
                            ->setUser($teacher)
182
                            ->setStatus(1)
183
                            ->setTutor(false)
184
                            ->setRelationType(0)
185
                            ->setUserCourseCat(0)
186
                        ;
187
                        $course->addSubscription($courseRelTeacher);
188
                    }
189
                }
190
            }
191
192
            $this->courseRepository->create($course);
193
            $this->debugLog('registerCourse:persisted', ['courseId' => $course->getId()]);
194
195
            if (!empty($rawParams['exemplary_content'])) {
196
                $this->debugLog('registerCourse:willFillCourse', [
197
                    'request_demo' => true,
198
                ]);
199
                $this->fillCourse($course, $params);
200
            } else {
201
                $this->debugLog('registerCourse:skipFillCourse', [
202
                    'request_demo' => false,
203
                ]);
204
            }
205
206
            if (isset($rawParams['course_template'])) {
207
                $this->debugLog('registerCourse:maybeTemplate', ['template' => (int) $rawParams['course_template']]);
208
                $this->useTemplateAsBasisIfRequired(
209
                    $course->getCode(),
210
                    (int) $rawParams['course_template']
211
                );
212
            }
213
214
            $this->debugLog('registerCourse:done', ['courseId' => $course->getId()]);
215
            return $course;
216
        } catch (Exception $e) {
217
            $this->debugLog('registerCourse:exception', ['msg' => $e->getMessage()]);
218
            throw $e;
219
        }
220
    }
221
222
    public function sendEmailToAdmin(Course $course): void
223
    {
224
        $siteName = $this->getDefaultSetting('platform.site_name');
225
        $recipientEmail = $this->getDefaultSetting('admin.administrator_email');
226
        $recipientName = $this->getDefaultSetting('admin.administrator_name').' '.$this->getDefaultSetting('admin.administrator_surname');
227
        $institutionName = $this->getDefaultSetting('platform.institution');
228
        $courseName = $course->getTitle();
229
230
        $subject = $this->translator->trans('New course created on %s')." $siteName - $institutionName";
231
232
        $greeting = $this->translator->trans('Dear %s,');
233
        $intro = $this->translator->trans('This message is to inform you that a new course has been created on %s');
234
        $courseNameLabel = $this->translator->trans('Course name');
235
236
        $message = \sprintf($greeting, $recipientName)."\n\n";
237
        $message .= "$intro $siteName - $institutionName.\n";
238
        $message .= "$courseNameLabel $courseName\n";
239
        $message .= $this->translator->trans('Course name').': '.$course->getTitle()."\n";
240
241
        foreach ($course->getCategories() as $category) {
242
            $message .= $this->translator->trans('Category').': '.$category->getCode()."\n";
243
        }
244
245
        $message .= $this->translator->trans('Coach').': '.$course->getTutorName()."\n";
246
        $message .= $this->translator->trans('Language').': '.$course->getCourseLanguage();
247
248
        $email = (new Email())
249
            ->from($recipientEmail)
250
            ->to($recipientEmail)
251
            ->subject($subject)
252
            ->text($message)
253
        ;
254
255
        $this->mailer->send($email);
256
    }
257
258
    public function defineCourseKeys(
259
        string $wantedCode,
260
        string $prefixForAll = '',
261
        string $prefixForPath = '',
262
        bool $addUniquePrefix = false,
263
        bool $useCodeIndependentKeys = true
264
    ): array {
265
        $wantedCode = $this->generateCourseCode($wantedCode);
266
        $keysCourseCode = $useCodeIndependentKeys ? $wantedCode : '';
267
268
        $uniquePrefix = $addUniquePrefix ? substr(md5(uniqid((string) rand(), true)), 0, 10) : '';
269
270
        $keys = [];
271
        $finalSuffix = ['CourseId' => '', 'CourseDir' => ''];
272
        $limitNumTry = 100;
273
        $tryCount = 0;
274
275
        $keysAreUnique = false;
276
277
        while (!$keysAreUnique && $tryCount < $limitNumTry) {
278
            $keysCourseId = $prefixForAll.$uniquePrefix.$keysCourseCode.$finalSuffix['CourseId'];
279
            $keysCourseRepository = $prefixForPath.$uniquePrefix.$wantedCode.$finalSuffix['CourseDir'];
280
281
            if ($this->courseRepository->courseCodeExists($keysCourseId)) {
282
                $finalSuffix['CourseId'] = substr(md5(uniqid((string) rand(), true)), 0, 4);
283
                $tryCount++;
284
            } else {
285
                $keysAreUnique = true;
286
            }
287
        }
288
289
        if ($keysAreUnique) {
290
            $keys = [
291
                'code' => $keysCourseCode,
292
                'visual_code' => $keysCourseId,
293
                'directory' => $keysCourseRepository,
294
            ];
295
        }
296
297
        return $keys;
298
    }
299
300
    private function fillCourse(Course $course, array $params): void
301
    {
302
        $this->debugLog('fillCourse:start', [
303
            'courseId' => $course->getId(),
304
            'exemplary_request' => !empty($params['_exemplary_content_request']),
305
        ]);
306
307
        $this->insertCourseSettings($course);
308
        $this->debugLog('fillCourse:insertCourseSettings:ok');
309
310
        $this->createGroupCategory($course);
311
        $this->debugLog('fillCourse:createGroupCategory:ok');
312
313
        $gradebook = $this->createRootGradebook($course);
314
        $this->debugLog('fillCourse:createRootGradebook:ok', ['gradebookId' => $gradebook->getId()]);
315
316
        $setting = $this->settingsManager->getSetting('course.example_material_course_creation');
317
        $this->debugLog('fillCourse:setting.example_material_course_creation', ['value' => $setting]);
318
319
        if ('true' === $setting) {
320
            $this->debugLog('fillCourse:insertExampleContent:willRun');
321
            $this->insertExampleContent($course, $gradebook);
322
            $this->debugLog('fillCourse:insertExampleContent:done');
323
        } else {
324
            $this->debugLog('fillCourse:insertExampleContent:skippedBySetting');
325
        }
326
327
        $this->entityManager->flush();
328
        $this->debugLog('fillCourse:end');
329
    }
330
331
332
333
    private function insertCourseSettings(Course $course): void
334
    {
335
        $defaultEmailExerciseAlert = 0;
336
        if ('true' === $this->settingsManager->getSetting('exercise.email_alert_manager_on_new_quiz')) {
337
            $defaultEmailExerciseAlert = 1;
338
        }
339
340
        $settings = [
341
            'email_alert_manager_on_new_doc' => ['title' => '', 'default' => 0, 'category' => 'work'],
342
            'email_alert_on_new_doc_dropbox' => ['default' => 0, 'category' => 'dropbox'],
343
            'allow_user_edit_agenda' => ['default' => 0, 'category' => 'agenda'],
344
            'allow_user_edit_announcement' => ['default' => 0, 'category' => 'announcement'],
345
            'email_alert_manager_on_new_quiz' => ['default' => $defaultEmailExerciseAlert, 'category' => 'quiz'],
346
            'allow_user_image_forum' => ['default' => 1, 'category' => 'forum'],
347
            'course_theme' => ['default' => '', 'category' => 'theme'],
348
            'allow_learning_path_theme' => ['default' => 1, 'category' => 'theme'],
349
            'allow_open_chat_window' => ['default' => 1, 'category' => 'chat'],
350
            'email_alert_to_teacher_on_new_user_in_course' => ['default' => 0, 'category' => 'registration'],
351
            'allow_user_view_user_list' => ['default' => 1, 'category' => 'user'],
352
            'display_info_advance_inside_homecourse' => ['default' => 1, 'category' => 'thematic_advance'],
353
            'email_alert_students_on_new_homework' => ['default' => 0, 'category' => 'work'],
354
            'enable_lp_auto_launch' => ['default' => 0, 'category' => 'learning_path'],
355
            'enable_exercise_auto_launch' => ['default' => 0, 'category' => 'exercise'],
356
            'enable_document_auto_launch' => ['default' => 0, 'category' => 'document'],
357
            'pdf_export_watermark_text' => ['default' => '', 'category' => 'learning_path'],
358
            'allow_public_certificates' => [
359
                'default' => 'true' === $this->settingsManager->getSetting('course.allow_public_certificates') ? 1 : '',
360
                'category' => 'certificates',
361
            ],
362
            'documents_default_visibility' => ['default' => 'visible', 'category' => 'document'],
363
            'show_course_in_user_language' => ['default' => 2, 'category' => null],
364
            'email_to_teachers_on_new_work_feedback' => ['default' => 1, 'category' => null],
365
        ];
366
367
        foreach ($settings as $variable => $setting) {
368
            $courseSetting = new CCourseSetting();
369
            $courseSetting->setCId($course->getId());
370
            $courseSetting->setVariable($variable);
371
            $courseSetting->setTitle($setting['title'] ?? '');
372
            $courseSetting->setValue((string) $setting['default']);
373
            $courseSetting->setCategory($setting['category'] ?? '');
374
375
            $this->entityManager->persist($courseSetting);
376
        }
377
378
        $this->entityManager->flush();
379
    }
380
381
    private function createGroupCategory(Course $course): void
382
    {
383
        $groupCategory = new CGroupCategory();
384
        $groupCategory
385
            ->setTitle($this->translator->trans('Default groups'))
386
            ->setParent($course)
387
            ->addCourseLink($course)
388
        ;
389
390
        $this->entityManager->persist($groupCategory);
391
        $this->entityManager->flush();
392
    }
393
394
    private function createRootGradebook(Course $course): GradebookCategory
395
    {
396
        /** @var User $currentUser */
397
        $currentUser = $this->security->getUser();
398
        if (!$currentUser instanceof User) {
399
            $currentUser = $this->getFallbackAdminUser();
400
        }
401
402
        if (!$currentUser) {
403
            throw new LogicException('There is no user currently authenticated..');
404
        }
405
406
        $gradebookCategory = new GradebookCategory();
407
        $gradebookCategory
408
            ->setTitle($course->getCode())
409
            ->setLocked(0)
410
            ->setGenerateCertificates(false)
411
            ->setDescription('')
412
            ->setCourse($course)
413
            ->setWeight(100)
414
            ->setVisible(false)
415
            ->setCertifMinScore(75)
416
            ->setUser($currentUser)
417
        ;
418
419
        $this->entityManager->persist($gradebookCategory);
420
        $this->entityManager->flush();
421
422
        return $gradebookCategory;
423
    }
424
425
    private function insertExampleContent(Course $course, GradebookCategory $gradebook): void
426
    {
427
        /** @var User $currentUser */
428
        $currentUser = $this->security->getUser();
429
        if (!$currentUser instanceof User) {
430
            $currentUser = $this->getFallbackAdminUser();
431
        }
432
433
        $docsCreated = 0;
434
        $foldersCreated = 0;
435
        $filesCreated = 0;
436
        $linksCreated = 0;
437
        $announcementId = null;
438
        $agendaAdded = false;
439
        $exerciseId = null;
440
        $forumCategoryId = null;
441
        $forumId = null;
442
        $threadId = null;
443
444
        $this->debugLog('insertExampleContent:begin', ['courseId' => $course->getId()]);
445
446
        $files = [
447
            ['path' => '/audio',          'title' => $this->translator->trans('Audio'),    'filetype' => 'folder', 'size' => 0],
448
            ['path' => '/images',         'title' => $this->translator->trans('Images'),   'filetype' => 'folder', 'size' => 0],
449
            ['path' => '/images/gallery', 'title' => $this->translator->trans('Gallery'),  'filetype' => 'folder', 'size' => 0],
450
            ['path' => '/video',          'title' => $this->translator->trans('Video'),    'filetype' => 'folder', 'size' => 0],
451
        ];
452
        $paths = [];
453
        $courseInfo = ['real_id' => $course->getId(), 'code' => $course->getCode()];
454
        foreach ($files as $file) {
455
            try {
456
                $doc = DocumentManager::addDocument(
457
                    $courseInfo,
458
                    $file['path'],
459
                    $file['filetype'],
460
                    $file['size'],
461
                    $file['title'],
462
                    null,
463
                    0,
464
                    null,
465
                    0,
466
                    0,
467
                    0,
468
                    false
469
                );
470
                $iid = method_exists($doc, 'getIid') ? $doc->getIid() : null;
471
                $paths[$file['path']] = $iid;
472
                $foldersCreated++;
473
                $docsCreated++;
474
                $this->debugLog('insertExampleContent:document:root', [
475
                    'path' => $file['path'],
476
                    'title' => $file['title'],
477
                    'iid' => $iid,
478
                ]);
479
            } catch (\Throwable $e) {
480
                $this->debugLog('insertExampleContent:document:root:error', [
481
                    'path' => $file['path'],
482
                    'msg' => $e->getMessage(),
483
                ]);
484
            }
485
        }
486
487
        $projectDir = $this->parameterBag->get('kernel.project_dir');
488
        $defaultPath = $projectDir.'/public/img/document';
489
490
        $request = $this->requestStack->getCurrentRequest();
491
        $baseUrl = $request->getSchemeAndHttpHost().$request->getBasePath();
492
493
        $finder = new Finder();
494
        if (is_dir($defaultPath)) {
495
            $finder->in($defaultPath);
496
            $countFound = 0;
497
498
            /** @var SplFileInfo $file */
499
            foreach ($finder as $file) {
500
                $countFound++;
501
                $parentName = \dirname(str_replace($defaultPath, '', $file->getRealPath()));
502
                if ('/' === $parentName || '/certificates' === $parentName) {
503
                    continue;
504
                }
505
506
                $title = $file->getFilename();
507
                $parentId = $paths[$parentName] ?? null;
508
509
                if ($file->isDir()) {
510
                    try {
511
                        $realPath = str_replace($defaultPath, '', $file->getRealPath());
512
                        $document = DocumentManager::addDocument(
513
                            $courseInfo,
514
                            $realPath,
515
                            'folder',
516
                            null,
517
                            $title,
518
                            '',
519
                            null,
520
                            null,
521
                            null,
522
                            null,
523
                            null,
524
                            false,
525
                            null,
526
                            $parentId,
527
                            $file->getRealPath()
528
                        );
529
                        $iid = method_exists($document, 'getIid') ? $document->getIid() : null;
530
                        $paths[$realPath] = $iid;
531
                        $foldersCreated++;
532
                        $docsCreated++;
533
                        $this->debugLog('insertExampleContent:document:folder', [
534
                            'path' => $realPath,
535
                            'title' => $title,
536
                            'iid' => $iid,
537
                            'parentId' => $parentId,
538
                        ]);
539
                    } catch (\Throwable $e) {
540
                        $this->debugLog('insertExampleContent:document:folder:error', [
541
                            'title' => $title,
542
                            'msg' => $e->getMessage(),
543
                        ]);
544
                    }
545
                } else {
546
                    try {
547
                        $realPath = str_replace($defaultPath, '', $file->getRealPath());
548
                        $document = DocumentManager::addDocument(
549
                            $courseInfo,
550
                            $realPath,
551
                            'file',
552
                            $file->getSize(),
553
                            $title,
554
                            '',
555
                            null,
556
                            null,
557
                            null,
558
                            null,
559
                            null,
560
                            false,
561
                            null,
562
                            $parentId,
563
                            $file->getRealPath()
564
                        );
565
                        $iid = method_exists($document, 'getIid') ? $document->getIid() : null;
566
                        $filesCreated++;
567
                        $docsCreated++;
568
                        $this->debugLog('insertExampleContent:document:file', [
569
                            'path' => $realPath,
570
                            'title' => $title,
571
                            'iid' => $iid,
572
                            'parentId' => $parentId,
573
                            'size' => $file->getSize(),
574
                        ]);
575
                    } catch (\Throwable $e) {
576
                        $this->debugLog('insertExampleContent:document:file:error', [
577
                            'title' => $title,
578
                            'msg' => $e->getMessage(),
579
                        ]);
580
                    }
581
                }
582
            }
583
584
            $this->debugLog('insertExampleContent:document:scan', [
585
                'basePath' => $defaultPath,
586
                'found' => $countFound,
587
                'foldersCreated' => $foldersCreated,
588
                'filesCreated' => $filesCreated,
589
                'totalDocsCreated' => $docsCreated,
590
            ]);
591
        } else {
592
            $this->debugLog('insertExampleContent:document:basePathMissing', ['basePath' => $defaultPath]);
593
        }
594
595
        try {
596
            $now = new DateTime('now', new DateTimeZone('UTC'));
597
            $formattedNow = $now->format('Y-m-d H:i:s');
598
599
            $agenda = new Agenda('course');
600
            $agenda->set_course($courseInfo);
601
            $agenda->addEvent(
602
                $formattedNow,
603
                $formattedNow,
604
                0,
605
                $this->translator->trans('Course creation'),
606
                $this->translator->trans('This course was created at this time'),
607
                ['everyone' => 'everyone']
608
            );
609
            $agendaAdded = true;
610
            $this->debugLog('insertExampleContent:agenda:ok');
611
        } catch (\Throwable $e) {
612
            $this->debugLog('insertExampleContent:agenda:error', ['msg' => $e->getMessage()]);
613
        }
614
615
        try {
616
            $link = new \Link();
617
            $link->setCourse($courseInfo);
618
            $links = [
619
                [
620
                    'c_id' => $course->getId(),
621
                    'url' => 'http://www.google.com',
622
                    'title' => 'Quick and powerful search engine',
623
                    'description' => $this->translator->trans('Quick and powerful search engine'),
624
                    'category_id' => 0,
625
                    'on_homepage' => 0,
626
                    'target' => '_self',
627
                    'session_id' => 0,
628
                ],
629
                [
630
                    'c_id' => $course->getId(),
631
                    'url' => 'http://www.wikipedia.org',
632
                    'title' => 'Free online encyclopedia',
633
                    'description' => $this->translator->trans('Free online encyclopedia'),
634
                    'category_id' => 0,
635
                    'on_homepage' => 0,
636
                    'target' => '_self',
637
                    'session_id' => 0,
638
                ],
639
            ];
640
641
            foreach ($links as $params) {
642
                $link->save($params, false, false);
643
                $linksCreated++;
644
            }
645
            $this->debugLog('insertExampleContent:links:ok', ['linksCreated' => $linksCreated]);
646
        } catch (\Throwable $e) {
647
            $this->debugLog('insertExampleContent:links:error', ['msg' => $e->getMessage()]);
648
        }
649
650
        try {
651
            $announcementId = \AnnouncementManager::add_announcement(
652
                $courseInfo,
653
                0,
654
                $this->translator->trans('This is an announcement example'),
655
                $this->translator->trans('This is an announcement example. Only trainers are allowed to publish announcements.'),
656
                ['everyone' => 'everyone'],
657
                null,
658
                null,
659
                (new DateTime('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s')
660
            );
661
            $this->debugLog('insertExampleContent:announcement:ok', ['announcementId' => $announcementId]);
662
        } catch (\Throwable $e) {
663
            $this->debugLog('insertExampleContent:announcement:error', ['msg' => $e->getMessage()]);
664
        }
665
666
        try {
667
            $exercise = new \Exercise($course->getId());
668
            $exercise->exercise = $this->translator->trans('Sample test');
669
            $html = '<table width="100%" border="0" cellpadding="0" cellspacing="0">
670
                        <tr>
671
                        <td width="220" valign="top" align="left">
672
                            <img src="'.$baseUrl.'/public/img/document/images/mr_chamilo/doubts.png">
673
                        </td>
674
                        <td valign="top" align="left">'.$this->translator->trans('Irony').'</td></tr>
675
                    </table>';
676
            $exercise->type = 1;
677
            $exercise->setRandom(0);
678
            $exercise->active = 1;
679
            $exercise->results_disabled = 0;
680
            $exercise->description = $html;
681
            $exercise->save();
682
            $exerciseId = $exercise->id;
683
            $this->debugLog('insertExampleContent:exercise:created', ['exerciseId' => $exerciseId]);
684
685
            $question = new \MultipleAnswer();
686
            $question->course = $courseInfo;
687
            $question->question = $this->translator->trans('Socratic irony is...');
688
            $question->description = $this->translator->trans('(more than one answer can be true)');
689
            $question->weighting = 10;
690
            $question->position = 1;
691
            $question->course = $courseInfo;
692
            $question->save($exercise);
693
            $questionId = $question->id;
694
695
            $answer = new \Answer($questionId, $courseInfo['real_id']);
696
            $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);
697
            $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);
698
            $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. Socratic irony is an interrogative method. The Greek "eirotao" means "ask questions"'), 5, 3);
699
            $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);
700
            $answer->save();
701
            $this->debugLog('insertExampleContent:exercise:questionAnswers:ok', ['questionId' => $questionId]);
702
703
            // Gradebook link
704
            $manager = $this->entityManager;
705
            $childGradebookCategory = new GradebookCategory();
706
            $childGradebookCategory->setTitle($course->getCode());
707
            $childGradebookCategory->setLocked(0);
708
            $childGradebookCategory->setGenerateCertificates(false);
709
            $childGradebookCategory->setDescription('');
710
            $childGradebookCategory->setCourse($course);
711
            $childGradebookCategory->setWeight(100);
712
            $childGradebookCategory->setVisible(true);
713
            $childGradebookCategory->setCertifMinScore(75);
714
            $childGradebookCategory->setParent($gradebook);
715
            $childGradebookCategory->setUser(api_get_user_entity());
716
717
            $manager->persist($childGradebookCategory);
718
            $manager->flush();
719
720
            $gradebookLink = new GradebookLink();
721
            $gradebookLink->setType(1);
722
            $gradebookLink->setRefId($exerciseId);
723
            $gradebookLink->setUserScoreList([]);
724
            $gradebookLink->setCourse($course);
725
            $gradebookLink->setCategory($childGradebookCategory);
726
            $gradebookLink->setCreatedAt(new DateTime());
727
            $gradebookLink->setWeight(100);
728
            $gradebookLink->setVisible(1);
729
            $gradebookLink->setLocked(0);
730
731
            $manager->persist($gradebookLink);
732
            $manager->flush();
733
            $this->debugLog('insertExampleContent:gradebookLink:ok', [
734
                'childCategoryId' => $childGradebookCategory->getId(),
735
                'refId' => $exerciseId,
736
            ]);
737
        } catch (\Throwable $e) {
738
            $this->debugLog('insertExampleContent:exercise:error', ['msg' => $e->getMessage()]);
739
        }
740
741
        try {
742
            $params = [
743
                'forum_category_title' => $this->translator->trans('Example Forum Category'),
744
                'forum_category_comment' => '',
745
            ];
746
            $forumCategoryId = \saveForumCategory($params, $courseInfo, false);
747
            $this->debugLog('insertExampleContent:forum:category', ['forumCategoryId' => $forumCategoryId]);
748
749
            $params = [
750
                'forum_category' => $forumCategoryId,
751
                'forum_title' => $this->translator->trans('Example Forum'),
752
                'forum_comment' => '',
753
                'default_view_type_group' => ['default_view_type' => 'flat'],
754
            ];
755
            $forumId = \store_forum($params, $courseInfo, true);
756
            $this->debugLog('insertExampleContent:forum:forum', ['forumId' => $forumId]);
757
758
            $repo = $this->entityManager->getRepository(CForum::class);
759
            $forumEntity = $repo->find($forumId);
760
761
            $params = [
762
                'post_title' => $this->translator->trans('Example Thread'),
763
                'forum_id' => $forumId,
764
                'post_text' => $this->translator->trans('Example content'),
765
                'calification_notebook_title' => '',
766
                'numeric_calification' => '',
767
                'weight_calification' => '',
768
                'forum_category' => $forumCategoryId,
769
                'thread_peer_qualify' => 0,
770
            ];
771
            $threadId = \saveThread($forumEntity, $params, $courseInfo, false);
772
            $this->debugLog('insertExampleContent:forum:thread', ['threadId' => $threadId]);
773
        } catch (\Throwable $e) {
774
            $this->debugLog('insertExampleContent:forum:error', ['msg' => $e->getMessage()]);
775
        }
776
777
        $this->debugLog('insertExampleContent:end', [
778
            'courseId' => $course->getId(),
779
            'docsCreated' => $docsCreated,
780
            'foldersCreated' => $foldersCreated,
781
            'filesCreated' => $filesCreated,
782
            'linksCreated' => $linksCreated,
783
            'agendaAdded' => $agendaAdded,
784
            'announcementId' => $announcementId,
785
            'exerciseId' => $exerciseId,
786
            'forumCategoryId' => $forumCategoryId,
787
            'forumId' => $forumId,
788
            'threadId' => $threadId,
789
        ]);
790
    }
791
792
    private function createExampleGradebookContent(Course $course, GradebookCategory $parentCategory, int $refId): void
793
    {
794
        $manager = $this->entityManager;
795
796
        /* Gradebook tool */
797
        $courseCode = $course->getCode();
798
799
        $childGradebookCategory = new GradebookCategory();
800
        $childGradebookCategory->setTitle($courseCode);
801
        $childGradebookCategory->setLocked(0);
802
        $childGradebookCategory->setGenerateCertificates(false);
803
        $childGradebookCategory->setDescription('');
804
        $childGradebookCategory->setCourse($course);
805
        $childGradebookCategory->setWeight(100);
806
        $childGradebookCategory->setVisible(true);
807
        $childGradebookCategory->setCertifMinScore(75);
808
        $childGradebookCategory->setParent($parentCategory);
809
        $childGradebookCategory->setUser(api_get_user_entity());
810
811
        $manager->persist($childGradebookCategory);
812
        $manager->flush();
813
814
        $gradebookLink = new GradebookLink();
815
816
        $gradebookLink->setType(1);
817
        $gradebookLink->setRefId($refId);
818
        $gradebookLink->setUserScoreList([]);
819
        $gradebookLink->setCourse($course);
820
        $gradebookLink->setCategory($childGradebookCategory);
821
        $gradebookLink->setCreatedAt(new DateTime());
822
        $gradebookLink->setWeight(100);
823
        $gradebookLink->setVisible(1);
824
        $gradebookLink->setLocked(0);
825
826
        $manager->persist($gradebookLink);
827
        $manager->flush();
828
    }
829
830
    private function installCoursePlugins(int $courseId): void
831
    {
832
        $app_plugin = new AppPlugin();
833
        $app_plugin->install_course_plugins($courseId);
834
    }
835
836
    public function useTemplateAsBasisIfRequired($courseCode, $courseTemplate): void
837
    {
838
        $templateSetting = $this->settingsManager->getSetting('course.course_creation_use_template');
839
        $teacherCanSelectCourseTemplate = 'true' === $this->settingsManager->getSetting('course.teacher_can_select_course_template');
840
        $courseTemplate = isset($courseTemplate) ? (int) $courseTemplate : 0;
841
842
        $useTemplate = false;
843
        if ($teacherCanSelectCourseTemplate && $courseTemplate > 0) {
844
            $useTemplate = true;
845
            $originCourse = $this->courseRepository->findCourseAsArray((int) $courseTemplate);
846
        } elseif (!empty($templateSetting)) {
847
            $useTemplate = true;
848
            $originCourse = $this->courseRepository->findCourseAsArray((int) $templateSetting);
849
        }
850
851
        if ($useTemplate && !empty($originCourse)) {
852
            try {
853
                $originCourse['official_code'] = $originCourse['code'];
854
                $cb = new CourseBuilder(null, $originCourse);
855
                $course = $cb->build(null, $originCourse['code']);
856
                $cr = new CourseRestorer($course);
857
                $cr->set_file_option();
858
                $cr->restore($courseCode);
859
            } catch (Exception $e) {
860
                error_log('Error during course template application: '.$e->getMessage());
861
            } catch (Throwable $t) {
862
                error_log('Unexpected error during course template application: '.$t->getMessage());
863
            }
864
        }
865
    }
866
867
    private function prepareAndValidateCourseData(array $params): array
868
    {
869
        $title = str_replace('&amp;', '&', $params['title'] ?? '');
870
        $code = $params['code'] ?? '';
871
        $visualCode = $params['visual_code'] ?? '';
872
        $directory = $params['directory'] ?? '';
873
        $tutorName = $params['tutor_name'] ?? null;
874
        $courseLanguage = !empty($params['course_language']) ? $params['course_language'] : $this->getDefaultSetting('language.platform_language');
875
        $departmentName = $params['department_name'] ?? null;
876
        $departmentUrl = $this->fixDepartmentUrl($params['department_url'] ?? '');
877
        $diskQuota = $params['disk_quota'] ?? $this->getDefaultSetting('document.default_document_quotum');
878
        $visibility = $params['visibility'] ?? $this->getDefaultSetting('course.courses_default_creation_visibility', Course::OPEN_PLATFORM);
879
        $subscribe = $params['subscribe'] ?? (Course::OPEN_PLATFORM == $visibility);
880
        $unsubscribe = $params['unsubscribe'] ?? false;
881
        $expirationDate = $params['expiration_date'] ?? $this->getFutureExpirationDate();
882
        $teachers = $params['teachers'] ?? [];
883
        $categories = $params['course_categories'] ?? [];
884
        $notifyAdmins = $this->getDefaultSetting('course.send_email_to_admin_when_create_course');
885
886
        $errors = [];
887
        if (empty($code)) {
888
            $errors[] = 'courseSysCode is missing';
889
        }
890
        if (empty($visualCode)) {
891
            $errors[] = 'courseScreenCode is missing';
892
        }
893
        if (empty($directory)) {
894
            $errors[] = 'courseRepository is missing';
895
        }
896
        if (empty($title)) {
897
            $errors[] = 'title is missing';
898
        }
899
        if ($visibility < 0 || $visibility > 4) {
900
            $errors[] = 'visibility is invalid';
901
        }
902
903
        if (!empty($errors)) {
904
            throw new Exception(implode(', ', $errors));
905
        }
906
907
        return [
908
            'title' => $title,
909
            'code' => $code,
910
            'visualCode' => $visualCode,
911
            'directory' => $directory,
912
            'tutorName' => $tutorName,
913
            'courseLanguage' => $courseLanguage,
914
            'departmentName' => $departmentName,
915
            'departmentUrl' => $departmentUrl,
916
            'diskQuota' => $diskQuota,
917
            'visibility' => $visibility,
918
            'subscribe' => $subscribe,
919
            'unsubscribe' => $unsubscribe,
920
            'expirationDate' => new DateTime($expirationDate),
921
            'teachers' => $teachers,
922
            'categories' => $categories,
923
            'notifyAdmins' => $notifyAdmins,
924
        ];
925
    }
926
927
    private function getDefaultSetting(string $name, $default = null)
928
    {
929
        $settingValue = $this->settingsManager->getSetting($name);
930
931
        return null !== $settingValue ? $settingValue : $default;
932
    }
933
934
    private function fixDepartmentUrl(string $url): string
935
    {
936
        if (!empty($url) && !str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
937
            return 'http://'.$url;
938
        }
939
940
        return 'http://' === $url ? '' : $url;
941
    }
942
943
    private function getFutureExpirationDate(): string
944
    {
945
        return (new DateTime())->modify('+1 year')->format('Y-m-d H:i:s');
946
    }
947
948
    private function generateCourseCode(string $title): string
949
    {
950
        $cleanTitle = preg_replace('/[^A-Z0-9]/', '', strtoupper($this->replaceDangerousChar($title)));
951
952
        return substr($cleanTitle, 0, self::MAX_COURSE_LENGTH_CODE);
953
    }
954
955
    private function replaceDangerousChar(string $text): string
956
    {
957
        $encoding = mb_detect_encoding($text, mb_detect_order(), true);
958
        if ('UTF-8' !== $encoding) {
959
            $text = mb_convert_encoding($text, 'UTF-8', $encoding);
960
        }
961
962
        $text = str_replace(' ', '-', $text);
963
        $text = preg_replace('/[^-\w]+/', '', $text);
964
965
        return preg_replace('/\.+$/', '', $text);
966
    }
967
968
    private function handlePostCourseCreation(Course $course, array $params): void
969
    {
970
        if ($params['notifyAdmins'] ?? false) {
971
            $this->sendEmailToAdmin($course);
972
        }
973
974
        $currentUser = $this->security->getUser();
975
        if (!$currentUser instanceof User) {
976
            $currentUser = $this->getFallbackAdminUser();
977
        }
978
979
        $this->eventLoggerHelper->addEvent(
980
            'course_created',
981
            'course_id',
982
            $course->getId(),
983
            null,
984
            $currentUser->getId(),
985
            $course->getId()
986
        );
987
    }
988
989
    private function getFallbackAdminUser(): User
990
    {
991
        $qb = $this->entityManager->createQueryBuilder();
992
993
        $qb->select('u')
994
            ->from(User::class, 'u')
995
            ->where('u.roles LIKE :role')
996
            ->setParameter('role', '%ROLE_ADMIN%')
997
            ->setMaxResults(1)
998
        ;
999
1000
        $user = $qb->getQuery()->getOneOrNullResult();
1001
1002
        if (!$user instanceof User) {
1003
            throw new RuntimeException('No admin user found for fallback.');
1004
        }
1005
1006
        return $user;
1007
    }
1008
1009
    private function debugLog(string $message, array $context = []): void
1010
    {
1011
        if (!$this->debug) {
1012
            return;
1013
        }
1014
        $suffix = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) : '';
1015
        error_log('[CourseHelper] ' . $message . $suffix);
1016
    }
1017
}
1018