Skip to content

Commit 59a40d6

Browse files
committed
Merge branch 'master' of github.com:chamilo/chamilo-lms
2 parents cec8970 + 6538535 commit 59a40d6

File tree

12 files changed

+296
-251
lines changed

12 files changed

+296
-251
lines changed

public/main/inc/lib/PortfolioController.php

Lines changed: 31 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3795,123 +3795,53 @@ private function getItemsForIndex(
37953795
&& true === api_get_configuration_value('portfolio_show_base_course_post_in_sessions');
37963796

37973797
$portfolioRepo = Container::getPortfolioRepository();
3798-
$queryBuilder = $portfolioRepo->getResources();
3799-
$portfolioRepo->addCourseQueryBuilder($this->course, $queryBuilder);
3798+
$portfolioCategoryHelper = Container::getPortfolioCategoryHelper();
38003799

3801-
if ($this->session) {
3802-
if ($showBaseContentInSession) {
3803-
$portfolioRepo->addSessionAndBaseContentQueryBuilder($this->session, $queryBuilder);
3804-
} else {
3805-
$portfolioRepo->addSessionOnlyQueryBuilder($this->session, $queryBuilder);
3806-
}
3807-
} else {
3808-
$portfolioRepo->addSessionNullQueryBuilder($queryBuilder);
3809-
}
3800+
$filters = $frmFilterList && $frmFilterList->validate() ? $frmFilterList->exportValues() : [];
38103801

3811-
if ($frmFilterList && $frmFilterList->validate()) {
3812-
$values = $frmFilterList->exportValues();
3802+
$searchInCategories = [];
38133803

3814-
if (!empty($values['date'])) {
3815-
$queryBuilder
3816-
->andWhere('resource.creationDate >= :date')
3817-
->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
3818-
;
3819-
}
3804+
if ($categoryId = $filters['categoryId'] ?? null) {
3805+
$searchInCategories[] = $categoryId;
38203806

3821-
if (!empty($values['tags'])) {
3822-
$queryBuilder
3823-
->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = resource.id')
3824-
->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
3825-
->andWhere('ef.extraFieldType = :efType')
3826-
->andWhere('ef.variable = :variable')
3827-
->andWhere('efrt.tagId IN (:tags)');
3828-
3829-
$queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
3830-
$queryBuilder->setParameter('variable', 'tags');
3831-
$queryBuilder->setParameter('tags', $values['tags']);
3807+
foreach ($portfolioCategoryHelper->getListForIndex($categoryId) as $subCategory) {
3808+
$searchInCategories[] = $subCategory->getId();
38323809
}
3810+
}
38333811

3834-
if (!empty($values['text'])) {
3835-
$queryBuilder->andWhere(
3836-
$queryBuilder->expr()->orX(
3837-
$queryBuilder->expr()->like('resource.title', ':text'),
3838-
$queryBuilder->expr()->like('resource.content', ':text')
3839-
)
3840-
);
3812+
$searchNotInCategories = [];
38413813

3842-
$queryBuilder->setParameter('text', '%'.$values['text'].'%');
3843-
}
3814+
if ($subCategoryIdList = $filters['subCategoryIds'] ?? '') {
3815+
$diff = [];
38443816

3845-
// Filters by category level 0
3846-
$searchCategories = [];
3847-
if (!empty($values['categoryId'])) {
3848-
$searchCategories[] = $values['categoryId'];
3849-
$subCategories = $this->getCategoriesForIndex($values['categoryId']);
3850-
if (count($subCategories) > 0) {
3851-
foreach ($subCategories as $subCategory) {
3852-
$searchCategories[] = $subCategory->getId();
3853-
}
3854-
}
3855-
$queryBuilder->andWhere('resource.category IN('.implode(',', $searchCategories).')');
3817+
if ('all' !== $subCategoryIdList) {
3818+
$subCategoryIds = explode(',', $subCategoryIdList);
3819+
$diff = array_diff($searchInCategories, $subCategoryIds);
3820+
} elseif (trim($subCategoryIdList) === '') {
3821+
$diff = $searchInCategories;
38563822
}
38573823

3858-
// Filters by sub-category, don't show the selected values
3859-
$diff = [];
3860-
if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) {
3861-
$subCategoryIds = explode(',', $values['subCategoryIds']);
3862-
$diff = array_diff($searchCategories, $subCategoryIds);
3863-
} else {
3864-
if (trim($values['subCategoryIds']) === '') {
3865-
$diff = $searchCategories;
3866-
}
3867-
}
38683824
if (!empty($diff)) {
38693825
unset($diff[0]);
3870-
if (!empty($diff)) {
3871-
$queryBuilder->andWhere('resource.category NOT IN('.implode(',', $diff).')');
3872-
}
3873-
}
3874-
}
38753826

3876-
if ($listByUser) {
3877-
$queryBuilder
3878-
->andWhere('resource.user = :user')
3879-
->setParameter('user', $this->owner);
3880-
}
3881-
3882-
if ($this->advancedSharingEnabled) {
3883-
$queryBuilder->andWhere(
3884-
$queryBuilder->expr()->orX(
3885-
$queryBuilder->expr()->eq('resource.visibility', Portfolio::VISIBILITY_VISIBLE),
3886-
$queryBuilder->expr()->eq('links.user', ':current_user')
3887-
)
3888-
);
3889-
} else {
3890-
$visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
3891-
3892-
if (api_is_allowed_to_edit()) {
3893-
$visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
3827+
$searchNotInCategories = $diff;
38943828
}
3895-
3896-
$queryBuilder->andWhere(
3897-
$queryBuilder->expr()->orX(
3898-
'node.creator = :current_user',
3899-
$queryBuilder->expr()->andX(
3900-
'node.creator != :current_user',
3901-
$queryBuilder->expr()->in('resource.visibility', $visibilityCriteria)
3902-
)
3903-
)
3904-
);
3905-
}
3906-
3907-
$queryBuilder->setParameter('current_user', $currentUserId);
3908-
if ($alphabeticalOrder || true === api_get_configuration_value('portfolio_order_post_by_alphabetical_order')) {
3909-
$queryBuilder->orderBy('resource.title', 'ASC');
3910-
} else {
3911-
$queryBuilder->orderBy('node.createdAt', 'DESC');
39123829
}
39133830

3914-
$items = $queryBuilder->getQuery()->getResult();
3831+
$items = $portfolioRepo->getIndexCourseItems(
3832+
api_get_user_entity(),
3833+
$this->owner,
3834+
$this->course,
3835+
$this->session,
3836+
$showBaseContentInSession,
3837+
$listByUser,
3838+
$filters['date'] ?? null,
3839+
$filters['tags'] ?? [],
3840+
$filters['text'] ?? '',
3841+
$searchInCategories,
3842+
$searchNotInCategories,
3843+
$this->advancedSharingEnabled
3844+
);
39153845

39163846
if ($showBaseContentInSession) {
39173847
$items = array_filter(

src/CoreBundle/Controller/Admin/AdminController.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ public function listFilesInfo(
114114
// Avoid duplicates for the same course.
115115
if (!isset($coursesForThisFile[$courseId])) {
116116
$coursesForThisFile[$courseId] = [
117-
'id' => $courseId,
118-
'code' => $course->getCode(),
119-
'title' => $course->getTitle(),
117+
'id' => $courseId,
118+
'code' => $course->getCode(),
119+
'title' => $course->getTitle(),
120120
'resourceNodeId' => $courseResourceNodeId,
121121
];
122122
}
@@ -144,22 +144,22 @@ public function listFilesInfo(
144144
$courseResourceNodeId = $courseResourceNode ? $courseResourceNode->getId() : null;
145145

146146
$courseOptions[] = [
147-
'id' => $course->getId(),
148-
'code' => $course->getCode(),
149-
'title' => $course->getTitle(),
147+
'id' => $course->getId(),
148+
'code' => $course->getCode(),
149+
'title' => $course->getTitle(),
150150
'resourceNodeId' => $courseResourceNodeId,
151151
];
152152
}
153153

154154
return $this->render('@ChamiloCore/Admin/files_info.html.twig', [
155-
'files' => $files,
156-
'fileUrls' => $fileUrls,
157-
'filePaths' => $filePaths,
158-
'totalPages' => $totalPages,
159-
'currentPage' => $page,
160-
'search' => $search,
161-
'orphanFlags' => $orphanFlags,
162-
'linksCount' => $linksCount,
155+
'files' => $files,
156+
'fileUrls' => $fileUrls,
157+
'filePaths' => $filePaths,
158+
'totalPages' => $totalPages,
159+
'currentPage' => $page,
160+
'search' => $search,
161+
'orphanFlags' => $orphanFlags,
162+
'linksCount' => $linksCount,
163163
'coursesByFile' => $coursesByFile,
164164
'courseOptions' => $courseOptions,
165165
]);
@@ -315,7 +315,7 @@ public function attachOrphanFileToCourse(
315315
'File "%s" has been attached to %d course(s): %s.',
316316
(string) ($resourceFile->getOriginalName() ?? $resourceFile->getTitle() ?? $resourceFile->getId()),
317317
\count($attachedTitles),
318-
\implode(', ', $attachedTitles)
318+
implode(', ', $attachedTitles)
319319
)
320320
);
321321
}
@@ -325,7 +325,7 @@ public function attachOrphanFileToCourse(
325325
'warning',
326326
\sprintf(
327327
'Some courses were skipped: %s.',
328-
\implode(', ', $skippedTitles)
328+
implode(', ', $skippedTitles)
329329
)
330330
);
331331
}
@@ -399,7 +399,7 @@ public function detachFileFromCourse(
399399

400400
$this->addFlash(
401401
'success',
402-
sprintf(
402+
\sprintf(
403403
'File has been detached from %d course link(s).',
404404
$removed
405405
)

src/CoreBundle/Controller/Api/DownloadSelectedDocumentsAction.php

Lines changed: 37 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
use Exception;
1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
1617
use Symfony\Component\HttpFoundation\StreamedResponse;
1718
use Symfony\Component\HttpKernel\KernelInterface;
18-
use ZipArchive;
19+
use ZipStream\Option\Archive;
20+
use ZipStream\ZipStream;
1921

2022
class DownloadSelectedDocumentsAction
2123
{
@@ -27,7 +29,7 @@ public function __construct(
2729
private readonly KernelInterface $kernel,
2830
private readonly ResourceNodeRepository $resourceNodeRepository,
2931
private readonly CDocumentRepository $documentRepo,
30-
) { }
32+
) {}
3133

3234
/**
3335
* @throws Exception
@@ -50,108 +52,67 @@ public function __invoke(Request $request): Response
5052
return new Response('No documents found.', Response::HTTP_NOT_FOUND);
5153
}
5254

53-
$zipFilePath = $this->createZipFile($documents);
55+
$zipName = 'selected_documents.zip';
5456

55-
if (!$zipFilePath || !file_exists($zipFilePath)) {
56-
return new Response('ZIP file not found or could not be created.', Response::HTTP_INTERNAL_SERVER_ERROR);
57-
}
57+
$response = new StreamedResponse(
58+
function () use ($documents, $zipName): void {
59+
// Creates a ZIP file containing the specified documents.
60+
$options = new Archive();
61+
$options->setSendHttpHeaders(false);
62+
$options->setContentType(self::CONTENT_TYPE);
5863

59-
$fileSize = filesize($zipFilePath);
60-
if (false === $fileSize || 0 === $fileSize) {
61-
error_log('ZIP file is empty or unreadable.');
64+
$zip = new ZipStream($zipName, $options);
6265

63-
throw new Exception('ZIP file is empty or unreadable.');
64-
}
66+
foreach ($documents as $document) {
67+
$node = $document->getResourceNode();
6568

66-
[$start, $end, $length] = $this->getRange($request, $fileSize);
69+
if (!$node) {
70+
error_log('ResourceNode not found for document ID: '.$document->getIid());
6771

68-
$response = new StreamedResponse(
69-
function () use ($start, $length, $zipFilePath): void {
70-
$handle = fopen($zipFilePath, 'rb');
72+
continue;
73+
}
7174

72-
$this->echoBuffer($handle, $start, $length);
73-
}
74-
);
75+
$this->addNodeToZip($zip, $node);
76+
}
7577

76-
$this->setHeadersToStreamedResponse(
77-
$response,
78-
false,
79-
'selected_documents.zip',
80-
self::CONTENT_TYPE,
81-
$length,
82-
$start,
83-
$end,
84-
$fileSize,
78+
$zip->finish();
79+
},
8580
Response::HTTP_CREATED
8681
);
8782

88-
return $response;
89-
}
90-
91-
/**
92-
* Creates a ZIP file containing the specified documents.
93-
*
94-
* @return string the path to the created ZIP file
95-
*
96-
* @throws Exception if the ZIP file cannot be created or closed
97-
*/
98-
private function createZipFile(array $documents): string
99-
{
100-
$cacheDir = $this->kernel->getCacheDir();
101-
$zipFilePath = $cacheDir.'/selected_documents_'.uniqid().'.zip';
102-
103-
$zip = new ZipArchive();
104-
$result = $zip->open($zipFilePath, ZipArchive::CREATE);
83+
// Convert the file name to ASCII using iconv
84+
$zipName = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $zipName);
10585

106-
if (true !== $result) {
107-
throw new Exception('Unable to create ZIP file');
108-
}
109-
110-
$projectDir = $this->kernel->getProjectDir();
111-
$baseUploadDir = $projectDir.'/var/upload/resource';
112-
113-
foreach ($documents as $document) {
114-
$resourceNode = $document->getResourceNode();
115-
if (!$resourceNode) {
116-
error_log('ResourceNode not found for document ID: '.$document->getId());
117-
118-
continue;
119-
}
120-
121-
$this->addNodeToZip($zip, $resourceNode, $baseUploadDir);
122-
}
123-
124-
if (!$zip->close()) {
125-
error_log('Failed to close ZIP file.');
86+
$disposition = $response->headers->makeDisposition(
87+
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
88+
$zipName
89+
);
12690

127-
throw new Exception('Failed to close ZIP archive');
128-
}
91+
$response->headers->set('Content-Disposition', $disposition);
92+
$response->headers->set('Content-Type', self::CONTENT_TYPE);
12993

130-
return $zipFilePath;
94+
return $response;
13195
}
13296

13397
/**
13498
* Adds a resource node and its files or children to the ZIP archive.
13599
*/
136-
private function addNodeToZip(ZipArchive $zip, ResourceNode $node, string $baseUploadDir, string $currentPath = ''): void
100+
private function addNodeToZip(ZipStream $zip, ResourceNode $node, string $currentPath = ''): void
137101
{
138102
if ($node->getChildren()->count() > 0) {
139103
$relativePath = $currentPath.$node->getTitle().'/';
140-
$zip->addEmptyDir($relativePath);
104+
105+
$zip->addFile($relativePath, '');
141106

142107
foreach ($node->getChildren() as $childNode) {
143-
$this->addNodeToZip($zip, $childNode, $baseUploadDir, $relativePath);
108+
$this->addNodeToZip($zip, $childNode, $relativePath);
144109
}
145110
} elseif ($node->hasResourceFile()) {
146111
foreach ($node->getResourceFiles() as $resourceFile) {
147-
$filePath = $baseUploadDir.$this->resourceNodeRepository->getFilename($resourceFile);
148112
$fileName = $currentPath.$resourceFile->getOriginalName();
113+
$stream = $this->documentRepo->getResourceNodeFileStream($node);
149114

150-
if (file_exists($filePath)) {
151-
$zip->addFile($filePath, $fileName);
152-
} else {
153-
error_log('File not found: '.$filePath);
154-
}
115+
$zip->addFileFromStream($fileName, $stream);
155116
}
156117
} else {
157118
error_log('Node has no children or files: '.$node->getTitle());

0 commit comments

Comments
 (0)