Skip to content

Commit 0b9eb28

Browse files
Merge pull request #7494 from christianbeeznest/fixes-updates251
Internal: Hide system folders in Documents tool using platform settings
2 parents 7101c7b + 92cb696 commit 0b9eb28

2 files changed

Lines changed: 167 additions & 9 deletions

File tree

src/CoreBundle/Migrations/Schema/V200/Version20201212203625.php

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function getDescription(): string
2727

2828
public function up(Schema $schema): void
2929
{
30+
$this->ensureCDocumentFiletypeVarchar15();
3031
$documentRepo = $this->container->get(CDocumentRepository::class);
3132
$courseRepo = $this->container->get(CourseRepository::class);
3233
$attemptRepo = $this->entityManager->getRepository(TrackEAttempt::class);
@@ -195,16 +196,24 @@ public function up(Schema $schema): void
195196
$counter = 1;
196197
$courseId = (int) $course->getId();
197198

198-
$sql = "SELECT iid, path FROM c_document
199+
// We fetch session_id and filetype for debugging/consistency checks during migration.
200+
// The legacy "path" is the only reliable identifier for system folders (language-independent).
201+
$sql = "SELECT iid, path, session_id, filetype FROM c_document
199202
WHERE c_id = {$courseId}
200203
AND path NOT LIKE '/../exercises/%'
201204
AND path NOT LIKE '/chat_files/%'
202205
ORDER BY filetype DESC, path";
203206
$documents = $this->connection->executeQuery($sql)->fetchAllAssociative();
204207

205208
foreach ($documents as $documentData) {
206-
$documentId = (int) $documentData['iid'];
207-
$documentPath = (string) $documentData['path'];
209+
$documentId = (int) ($documentData['iid'] ?? 0);
210+
$documentPath = (string) ($documentData['path'] ?? '');
211+
$legacySessionId = (int) ($documentData['session_id'] ?? 0);
212+
$legacyFiletype = (string) ($documentData['filetype'] ?? '');
213+
214+
if ($documentId <= 0 || '' === $documentPath) {
215+
continue;
216+
}
208217

209218
$courseEntity = $courseRepo->find($courseId);
210219
if (!$courseEntity) {
@@ -221,6 +230,28 @@ public function up(Schema $schema): void
221230
continue;
222231
}
223232

233+
// Detect and mark legacy system folders before linking/persisting.
234+
// This avoids relying on translated titles and keeps identification stable over time.
235+
$normalizedLegacyPath = $this->normalizeLegacyDocumentPath($documentPath);
236+
$systemFolderType = $this->detectSystemFolderType($normalizedLegacyPath);
237+
238+
// Only overwrite filetype for folders to keep behavior safe and predictable.
239+
if (null !== $systemFolderType) {
240+
$effectiveFiletype = $document->getFiletype() ?? $legacyFiletype;
241+
242+
if ('folder' === $effectiveFiletype) {
243+
$document->setFiletype($systemFolderType);
244+
245+
error_log(sprintf(
246+
'[MIGRATION][documents] Marked system folder (iid=%d, legacyPath=%s, legacySid=%d, type=%s).',
247+
$documentId,
248+
$normalizedLegacyPath,
249+
$legacySessionId,
250+
$systemFolderType
251+
));
252+
}
253+
}
254+
224255
$parent = null;
225256
if ('.' !== \dirname($documentPath)) {
226257
$currentPath = \dirname($documentPath);
@@ -362,4 +393,75 @@ private function insertAttemptFileRow(int $attemptId, Asset $asset): void
362393

363394
error_log(sprintf('[MIGRATION][attempt_file] Linked asset to attempt (attemptId=%d).', $attemptId));
364395
}
396+
397+
/**
398+
* Ensure c_document.filetype is large enough BEFORE we update it during data migration.
399+
* Execute the ALTER immediately to avoid truncation during flush().
400+
*/
401+
private function ensureCDocumentFiletypeVarchar15(): void
402+
{
403+
try {
404+
// We run this upfront because this migration updates c_document.filetype during flush().
405+
$this->connection->executeStatement('ALTER TABLE c_document MODIFY filetype VARCHAR(15);');
406+
error_log('[MIGRATION][documents] Ensured c_document.filetype is VARCHAR(15) before data migration.');
407+
} catch (Throwable $e) {
408+
error_log('[MIGRATION][documents] Failed to resize c_document.filetype: '.$e->getMessage());
409+
}
410+
}
411+
412+
/**
413+
* Normalize legacy paths so system folders can be detected reliably.
414+
* Keep it strict and language-independent (do not use titles).
415+
*/
416+
private function normalizeLegacyDocumentPath(string $path): string
417+
{
418+
$p = str_replace('//', '/', trim($path));
419+
if ('' === $p) {
420+
return '';
421+
}
422+
423+
// Ensure leading slash for consistent prefix checks.
424+
if ('/' !== $p[0]) {
425+
$p = '/'.$p;
426+
}
427+
428+
return $p;
429+
}
430+
431+
/**
432+
* Detect legacy system folder type from the original C1 document path.
433+
*/
434+
private function detectSystemFolderType(string $legacyPath): ?string
435+
{
436+
if ('' === $legacyPath) {
437+
return null;
438+
}
439+
440+
// Session-scoped shared folders: keep a distinct type to hide them in base course views.
441+
if (preg_match('#^/shared_folder_session_\d+(/|$)#', $legacyPath)) {
442+
return 'user_folder_ses'; // <= 15 chars
443+
}
444+
445+
// Base course shared folder.
446+
if (0 === strpos($legacyPath, '/shared_folder')) {
447+
return 'user_folder';
448+
}
449+
450+
if (preg_match('#^/(images|audio|flash|video)(/|$)#', $legacyPath)) {
451+
return 'media_folder';
452+
}
453+
454+
if (0 === strpos($legacyPath, '/certificates')) {
455+
return 'cert_folder'; // <= 15 chars
456+
}
457+
458+
if (
459+
0 === strpos($legacyPath, '/chat_history')
460+
|| 0 === strpos($legacyPath, '/chat_files')
461+
) {
462+
return 'chat_folder';
463+
}
464+
465+
return null;
466+
}
365467
}

src/CoreBundle/State/DocumentCollectionStateProvider.php

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Chamilo\CoreBundle\Entity\ResourceShowCourseResourcesInSessionInterface;
1515
use Chamilo\CoreBundle\Entity\Session;
1616
use Chamilo\CoreBundle\Repository\ResourceLinkRepository;
17+
use Chamilo\CoreBundle\Settings\SettingsManager;
1718
use Chamilo\CourseBundle\Entity\CDocument;
1819
use Chamilo\CourseBundle\Entity\CGroup;
1920
use Doctrine\ORM\EntityManagerInterface;
@@ -27,6 +28,7 @@ final class DocumentCollectionStateProvider implements ProviderInterface
2728
public function __construct(
2829
private readonly EntityManagerInterface $entityManager,
2930
private readonly RequestStack $requestStack,
31+
private readonly SettingsManager $settingsManager,
3032
) {}
3133

3234
/**
@@ -68,6 +70,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
6870

6971
// Gradebook mode (e.g. gradebook=1)
7072
$isGradebook = !empty($query['gradebook']) && 1 === (int) $query['gradebook'];
73+
$showUsersFolders = 'true' === (string) ($this->settingsManager->getSetting('document.show_users_folders', true) ?? '');
74+
$showDefaultFolders = 'false' !== (string) ($this->settingsManager->getSetting('document.show_default_folders', true) ?? '');
75+
$showChatFolder = 'true' === (string) ($this->settingsManager->getSetting('chat.show_chat_folder', true) ?? '');
76+
7177
$rawFiletype = $query['filetype'] ?? null;
7278
$filetypes = [];
7379

@@ -78,21 +84,32 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7884
}
7985

8086
// Normalize & unique
81-
$filetypes = array_values(array_unique(array_filter(array_map('strval', $filetypes))));
87+
$requestedFiletypes = array_values(array_unique(array_filter(array_map('strval', $filetypes))));
8288

8389
// Compatibility: treat "html" as a subtype of "file"
84-
if (\in_array('file', $filetypes, true) && !\in_array('html', $filetypes, true)) {
85-
$filetypes[] = 'html';
90+
$effectiveFiletypes = $requestedFiletypes;
91+
if (\in_array('file', $effectiveFiletypes, true) && !\in_array('html', $effectiveFiletypes, true)) {
92+
$effectiveFiletypes[] = 'html';
93+
}
94+
95+
// System folder subtypes
96+
$systemFolderTypes = ['user_folder', 'user_folder_ses', 'media_folder', 'chat_folder', 'cert_folder'];
97+
98+
// If the client asks for "folder", include system folder subtypes as well.
99+
// This keeps folder browsing working after migration (system folders have custom filetypes).
100+
if (\in_array('folder', $effectiveFiletypes, true)) {
101+
$effectiveFiletypes = array_values(array_unique(array_merge($effectiveFiletypes, $systemFolderTypes)));
86102
}
87103

88-
if (!empty($filetypes)) {
104+
if (!empty($effectiveFiletypes)) {
89105
$qb
90106
->andWhere($qb->expr()->in('d.filetype', ':filetypes'))
91-
->setParameter('filetypes', $filetypes)
107+
->setParameter('filetypes', $effectiveFiletypes)
92108
;
93109
}
94110

95-
$wantsCertificateList = \in_array('certificate', $filetypes, true);
111+
// NOTE: this is for "certificate" filetype lists, not the certificates folder filetype.
112+
$wantsCertificateList = \in_array('certificate', $effectiveFiletypes, true);
96113
$showSystemCertificates = !empty($query['showSystemCertificates']) && 1 === (int) $query['showSystemCertificates'];
97114

98115
if (!$showSystemCertificates && !($isGradebook && $wantsCertificateList)) {
@@ -105,6 +122,45 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
105122
}
106123
}
107124

125+
// Hide system folders depending on settings.
126+
$explicitSystemTypes = array_values(array_intersect($requestedFiletypes, $systemFolderTypes));
127+
$hiddenSystemTypes = [];
128+
129+
// User folders
130+
if (!$showUsersFolders) {
131+
$hiddenSystemTypes[] = 'user_folder';
132+
$hiddenSystemTypes[] = 'user_folder_ses';
133+
} else {
134+
// When enabled, never show session-scoped shared folders in base course context.
135+
if ($sid <= 0) {
136+
$hiddenSystemTypes[] = 'user_folder_ses';
137+
}
138+
}
139+
140+
// Default media folders
141+
if (!$showDefaultFolders) {
142+
$hiddenSystemTypes[] = 'media_folder';
143+
}
144+
145+
// Chat history folder (controlled by chat.show_chat_folder)
146+
if (!$showChatFolder) {
147+
$hiddenSystemTypes[] = 'chat_folder';
148+
}
149+
150+
// Certificates folder: hide unless explicitly requested.
151+
if (!$showSystemCertificates && !($isGradebook && $wantsCertificateList)) {
152+
$hiddenSystemTypes[] = 'cert_folder';
153+
}
154+
155+
$hiddenSystemTypes = array_values(array_unique(array_diff($hiddenSystemTypes, $explicitSystemTypes)));
156+
157+
if (!empty($hiddenSystemTypes)) {
158+
$qb
159+
->andWhere($qb->expr()->notIn('d.filetype', ':hiddenFiletypes'))
160+
->setParameter('hiddenFiletypes', $hiddenSystemTypes)
161+
;
162+
}
163+
108164
$loadNode = !empty($query['loadNode']) && 1 === (int) $query['loadNode'];
109165

110166
$parentNodeId = 0;

0 commit comments

Comments
 (0)