Passed
Pull Request — master (#6351)
by
unknown
07:48
created

CreateCoursesFromStructuredFileCommand::execute()   C

Complexity

Conditions 13
Paths 15

Size

Total Lines 141
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 13
eloc 96
c 2
b 0
f 0
nc 15
nop 2
dl 0
loc 141
rs 5.3806

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Command;
8
9
use Chamilo\CoreBundle\Entity\Course;
10
use Chamilo\CoreBundle\Entity\User;
11
use Chamilo\CoreBundle\Service\CourseService;
12
use Chamilo\CoreBundle\Settings\SettingsManager;
13
use Chamilo\CourseBundle\Entity\CDocument;
14
use Chamilo\CourseBundle\Entity\CLp;
15
use Chamilo\CourseBundle\Entity\CLpItem;
16
use Doctrine\ORM\EntityManagerInterface;
17
use Symfony\Component\Console\Attribute\AsCommand;
18
use Symfony\Component\Console\Command\Command;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\Console\Style\SymfonyStyle;
23
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
24
use Symfony\Component\Finder\Finder;
25
26
#[AsCommand(
27
    name: 'app:create-courses-from-structured-file',
28
    description: 'Create courses and learning paths from a folder containing files',
29
)]
30
class CreateCoursesFromStructuredFileCommand extends Command
31
{
32
    public function __construct(
33
        private readonly EntityManagerInterface $em,
34
        private readonly CourseService $courseService,
35
        private readonly SettingsManager $settingsManager,
36
        private readonly ParameterBagInterface $parameterBag,
37
    ) {
38
        parent::__construct();
39
    }
40
41
    protected function configure(): void
42
    {
43
        $this->addArgument(
44
            'folder',
45
            InputArgument::REQUIRED,
46
            'Absolute path to the folder that contains course files'
47
        );
48
    }
49
50
    protected function execute(InputInterface $input, OutputInterface $output): int
51
    {
52
        $io        = new SymfonyStyle($input, $output);
53
        $adminUser = $this->getFirstAdmin();
54
        if (!$adminUser) {
55
            $io->error('No admin user found in the system.');
56
            return Command::FAILURE;
57
        }
58
59
        $folder = $input->getArgument('folder');
60
        if (!is_dir($folder)) {
61
            $io->error("Invalid folder: $folder");
62
            return Command::FAILURE;
63
        }
64
65
        // Retrieve Unix permissions from platform settings
66
        $dirPermOct  = octdec(
67
            $this->settingsManager->getSetting('document.permissions_for_new_directories') ?? '0777'
68
        );
69
        $filePermOct = octdec(
70
            $this->settingsManager->getSetting('document.permissions_for_new_files') ?? '0666'
71
        );
72
73
        // Absolute base to /var/upload/resource
74
        $uploadBase = $this->parameterBag->get('kernel.project_dir') . '/var/upload/resource';
75
76
        $finder = new Finder();
77
        $finder->files()->in($folder);
78
79
        foreach ($finder as $file) {
80
            $basename   = $file->getBasename();
81
            $courseCode = pathinfo($basename, PATHINFO_FILENAME);
82
            $filePath   = $file->getRealPath();
83
84
            // 1. Skip unsupported file extensions
85
            $allowedExtensions = ['pdf', 'html', 'htm', 'mp4'];
86
            if (!in_array(strtolower($file->getExtension()), $allowedExtensions, true)) {
87
                $io->warning("Skipping unsupported file: $basename");
88
                continue;
89
            }
90
91
            $io->section("Creating course: $courseCode");
92
93
            // 2. Create course
94
            $course = $this->courseService->createCourse([
95
                'title'               => $courseCode,
96
                'wanted_code'         => $courseCode,
97
                'add_user_as_teacher' => true,
98
                'course_language'     => $this->settingsManager->getSetting('language.platform_language'),
99
                'visibility'          => Course::OPEN_PLATFORM,
100
                'subscribe'           => true,
101
                'unsubscribe'         => true,
102
                'disk_quota'          => $this->settingsManager->getSetting('document.default_document_quotum'),
103
                'expiration_date'     => (new \DateTime('+1 year'))->format('Y-m-d H:i:s'),
104
            ]);
105
106
            if (!$course) {
107
                throw new \RuntimeException("Course '$courseCode' could not be created.");
108
            }
109
110
            // 3. Create learning path
111
            $lp = (new CLp())
112
                ->setLpType(1)
113
                ->setTitle($courseCode)
114
                ->setDescription('')
115
                ->setPublishedOn(null)
116
                ->setExpiredOn(null)
117
                ->setCategory(null)
118
                ->setParent($course)
119
                ->addCourseLink($course)
120
                ->setCreator($adminUser);
121
122
            $this->em->getRepository(CLp::class)->createLp($lp);
123
124
            // 4. Create document
125
            $document = (new CDocument())
126
                ->setFiletype('file')
127
                ->setTitle($basename)
128
                ->setComment(null)
129
                ->setReadonly(false)
130
                ->setCreator($adminUser)
131
                ->setParent($course)
132
                ->addCourseLink($course);
133
134
            $this->em->persist($document);
135
            $this->em->flush();
136
137
            $documentRepo = $this->em->getRepository(CDocument::class);
138
            $resourceFile = $documentRepo->addFileFromPath($document, $basename, $filePath);
139
140
            // 4.1  Apply permissions to the real file & its directory
141
            if ($resourceFile) {
142
                $resourceNodeRepo = $this->em->getRepository(\Chamilo\CoreBundle\Entity\ResourceNode::class);
143
                $relativePath = $resourceNodeRepo->getFilename($resourceFile); // e.g. /2025/06/16/abc.pdf
144
                $fullPath     = realpath($uploadBase . $relativePath);
145
146
                if ($fullPath && is_file($fullPath)) {
147
                    @chmod($fullPath, $filePermOct);
148
                }
149
                $fullDir = dirname($fullPath ?: '');
150
                if ($fullDir && is_dir($fullDir)) {
151
                    @chmod($fullDir, $dirPermOct);
152
                }
153
            }
154
155
            // 5. Ensure learning path root item exists
156
            $lpItemRepo = $this->em->getRepository(CLpItem::class);
157
            $rootItem   = $lpItemRepo->getRootItem((int) $lp->getIid());
158
159
            if (!$rootItem) {
160
                $rootItem = (new CLpItem())
161
                    ->setTitle('root')
162
                    ->setPath('root')
163
                    ->setLp($lp)
164
                    ->setItemType('root');
165
                $this->em->persist($rootItem);
166
                $this->em->flush();
167
            }
168
169
            // 6. Create LP item linked to the document
170
            $lpItem = (new CLpItem())
171
                ->setLp($lp)
172
                ->setTitle($basename)
173
                ->setItemType('document')
174
                ->setRef((string) $document->getIid())
175
                ->setPath((string) $document->getIid())
176
                ->setDisplayOrder(1)
177
                ->setLaunchData('')
178
                ->setMinScore(0)
179
                ->setMaxScore(100)
180
                ->setParent($rootItem)
181
                ->setLvl(1)
182
                ->setRoot($rootItem);
183
184
            $this->em->persist($lpItem);
185
            $this->em->flush();
186
187
            $io->success("Course '$courseCode' created with LP and document '$basename'");
188
        }
189
190
        return Command::SUCCESS;
191
    }
192
193
    /**
194
     * Return the first user that has ROLE_ADMIN.
195
     */
196
    private function getFirstAdmin(): ?User
197
    {
198
        return $this->em->getRepository(User::class)
199
            ->createQueryBuilder('u')
200
            ->where('u.roles LIKE :role')
201
            ->setParameter('role', '%ROLE_ADMIN%')
202
            ->setMaxResults(1)
203
            ->getQuery()
204
            ->getOneOrNullResult();
205
    }
206
}
207