Skip to content

Commit 1e82b45

Browse files
Internal: Add platform file/dir permissions in structured-file course import - refs BT#22644
1 parent 88f3823 commit 1e82b45

File tree

1 file changed

+65
-33
lines changed

1 file changed

+65
-33
lines changed

src/CoreBundle/Command/CreateCoursesFromStructuredFileCommand.php

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use Symfony\Component\Console\Command\Command;
1919
use Symfony\Component\Console\Input\InputArgument;
2020
use Symfony\Component\Console\Input\InputInterface;
21-
use Symfony\Component\Console\Style\SymfonyStyle;
2221
use Symfony\Component\Console\Output\OutputInterface;
22+
use Symfony\Component\Console\Style\SymfonyStyle;
23+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
2324
use Symfony\Component\Finder\Finder;
2425

25-
2626
#[AsCommand(
2727
name: 'app:create-courses-from-structured-file',
2828
description: 'Create courses and learning paths from a folder containing files',
@@ -32,20 +32,24 @@ class CreateCoursesFromStructuredFileCommand extends Command
3232
public function __construct(
3333
private readonly EntityManagerInterface $em,
3434
private readonly CourseService $courseService,
35-
private readonly SettingsManager $settingsManager
35+
private readonly SettingsManager $settingsManager,
36+
private readonly ParameterBagInterface $parameterBag,
3637
) {
3738
parent::__construct();
3839
}
3940

4041
protected function configure(): void
4142
{
42-
$this
43-
->addArgument('folder', InputArgument::REQUIRED, 'Path to folder with course files');
43+
$this->addArgument(
44+
'folder',
45+
InputArgument::REQUIRED,
46+
'Absolute path to the folder that contains course files'
47+
);
4448
}
4549

4650
protected function execute(InputInterface $input, OutputInterface $output): int
4751
{
48-
$io = new SymfonyStyle($input, $output);
52+
$io = new SymfonyStyle($input, $output);
4953
$adminUser = $this->getFirstAdmin();
5054
if (!$adminUser) {
5155
$io->error('No admin user found in the system.');
@@ -58,41 +62,52 @@ protected function execute(InputInterface $input, OutputInterface $output): int
5862
return Command::FAILURE;
5963
}
6064

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+
6176
$finder = new Finder();
6277
$finder->files()->in($folder);
6378

6479
foreach ($finder as $file) {
65-
$basename = $file->getBasename();
80+
$basename = $file->getBasename();
6681
$courseCode = pathinfo($basename, PATHINFO_FILENAME);
67-
$filePath = $file->getRealPath();
82+
$filePath = $file->getRealPath();
6883

69-
// Skip unsupported extensions
84+
// 1. Skip unsupported file extensions
7085
$allowedExtensions = ['pdf', 'html', 'htm', 'mp4'];
71-
if (!in_array(strtolower($file->getExtension()), $allowedExtensions)) {
86+
if (!in_array(strtolower($file->getExtension()), $allowedExtensions, true)) {
7287
$io->warning("Skipping unsupported file: $basename");
7388
continue;
7489
}
7590

7691
$io->section("Creating course: $courseCode");
7792

78-
// Step 1: Create the course
93+
// 2. Create course
7994
$course = $this->courseService->createCourse([
80-
'title' => $courseCode,
81-
'wanted_code' => $courseCode,
95+
'title' => $courseCode,
96+
'wanted_code' => $courseCode,
8297
'add_user_as_teacher' => true,
83-
'course_language' => $this->settingsManager->getSetting('language.platform_language'),
84-
'visibility' => Course::OPEN_PLATFORM,
85-
'subscribe' => true,
86-
'unsubscribe' => true,
87-
'disk_quota' => $this->settingsManager->getSetting('document.default_document_quotum'),
88-
'expiration_date' => (new \DateTime('+1 year'))->format('Y-m-d H:i:s')
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'),
89104
]);
90105

91106
if (!$course) {
92-
throw new \RuntimeException('Error: Course could not be created.');
107+
throw new \RuntimeException("Course '$courseCode' could not be created.");
93108
}
94109

95-
// Step 2: Create learning path (CLp)
110+
// 3. Create learning path
96111
$lp = (new CLp())
97112
->setLpType(1)
98113
->setTitle($courseCode)
@@ -101,15 +116,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
101116
->setExpiredOn(null)
102117
->setCategory(null)
103118
->setParent($course)
104-
->addCourseLink($course);
105-
$lp->setCreator($adminUser);
119+
->addCourseLink($course)
120+
->setCreator($adminUser);
106121

107-
$lpRepo = $this->em->getRepository(CLp::class);
108-
$lpRepo->createLp($lp);
122+
$this->em->getRepository(CLp::class)->createLp($lp);
109123

110-
// Step 3: Create CDocument from uploaded file
111-
$document = new CDocument();
112-
$document->setFiletype('file')
124+
// 4. Create document
125+
$document = (new CDocument())
126+
->setFiletype('file')
113127
->setTitle($basename)
114128
->setComment(null)
115129
->setReadonly(false)
@@ -121,12 +135,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int
121135
$this->em->flush();
122136

123137
$documentRepo = $this->em->getRepository(CDocument::class);
124-
$documentRepo->addFileFromPath($document, $basename, $filePath);
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+
}
125154

126-
// Step 4: Create LP item linked to the document
127-
// Ensure root item exists
155+
// 5. Ensure learning path root item exists
128156
$lpItemRepo = $this->em->getRepository(CLpItem::class);
129-
$rootItem = $lpItemRepo->getRootItem((int) $lp->getIid());
157+
$rootItem = $lpItemRepo->getRootItem((int) $lp->getIid());
130158

131159
if (!$rootItem) {
132160
$rootItem = (new CLpItem())
@@ -138,6 +166,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
138166
$this->em->flush();
139167
}
140168

169+
// 6. Create LP item linked to the document
141170
$lpItem = (new CLpItem())
142171
->setLp($lp)
143172
->setTitle($basename)
@@ -155,12 +184,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
155184
$this->em->persist($lpItem);
156185
$this->em->flush();
157186

158-
$io->success("Course '$courseCode' created with LP and document item '$basename'");
187+
$io->success("Course '$courseCode' created with LP and document '$basename'");
159188
}
160189

161190
return Command::SUCCESS;
162191
}
163192

193+
/**
194+
* Return the first user that has ROLE_ADMIN.
195+
*/
164196
private function getFirstAdmin(): ?User
165197
{
166198
return $this->em->getRepository(User::class)

0 commit comments

Comments
 (0)