Total Complexity | 109 |
Total Lines | 681 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like DropboxController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DropboxController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | #[Route('/dropbox')] |
||
28 | class DropboxController extends AbstractController |
||
29 | { |
||
30 | private array $userNameCache = []; |
||
31 | |||
32 | public function __construct( |
||
33 | private readonly EntityManagerInterface $em, |
||
34 | private readonly CDropboxCategoryRepository $categoryRepo, |
||
35 | private readonly CDropboxFileRepository $fileRepo, |
||
36 | private readonly CDropboxFeedbackRepository $feedbackRepo, |
||
37 | private readonly SluggerInterface $slugger, |
||
38 | private readonly ResourceNodeRepository $resourceNodeRepository |
||
39 | ) {} |
||
40 | |||
41 | private function humanSize(int $bytes): string |
||
42 | { |
||
43 | $units = ['B','KB','MB','GB','TB']; |
||
44 | $i = $bytes > 0 ? (int)floor(log($bytes, 1024)) : 0; |
||
45 | return sprintf('%.1f %s', $bytes / (1024 ** $i), $units[$i]); |
||
46 | } |
||
47 | |||
48 | private function ago(DateTimeImmutable $dt): string |
||
49 | { |
||
50 | $diff = (new DateTimeImmutable())->getTimestamp() - $dt->getTimestamp(); |
||
51 | if ($diff < 60) return 'just now'; |
||
52 | if ($diff < 3600) return floor($diff/60).' min ago'; |
||
53 | if ($diff < 86400) return floor($diff/3600).' h ago'; |
||
54 | return floor($diff/86400).' d ago'; |
||
55 | } |
||
56 | |||
57 | /** Pull Chamilo context (cid/sid/gid) from query string */ |
||
58 | private function context(Request $r): array |
||
59 | { |
||
60 | $cid = (int) $r->query->get('cid', 0); |
||
61 | $sid = $r->query->get('sid') ? (int) $r->query->get('sid') : null; |
||
62 | $gid = $r->query->get('gid') ? (int) $r->query->get('gid') : null; |
||
63 | |||
64 | return [$cid, $sid, $gid]; |
||
65 | } |
||
66 | |||
67 | #[Route('/recipients', name: 'dropbox_recipients', methods: ['GET'])] |
||
68 | public function recipients(Request $r): JsonResponse |
||
69 | { |
||
70 | [$cid, $sid, $gid] = $this->context($r); |
||
71 | $me = (int) $this->getUser()?->getId(); |
||
72 | |||
73 | if ($cid <= 0) { |
||
74 | $ref = (string) $r->headers->get('referer', ''); |
||
75 | if ($ref && preg_match('#/resources/dropbox/(\d+)/#', $ref, $m)) { |
||
76 | $cid = (int) $m[1]; |
||
77 | } |
||
78 | } |
||
79 | if ($cid <= 0) { |
||
80 | return $this->json(['message' => 'Missing course id (cid)'], 400); |
||
81 | } |
||
82 | |||
83 | $conn = $this->em->getConnection(); |
||
84 | $userRows = []; |
||
85 | |||
86 | $sqlCourse = <<<SQL |
||
87 | SELECT DISTINCT u.id, u.firstname, u.lastname |
||
88 | FROM course_rel_user cru |
||
89 | INNER JOIN user u ON u.id = cru.user_id |
||
90 | WHERE cru.c_id = :cid |
||
91 | SQL; |
||
92 | $userRows = $conn->fetchAllAssociative($sqlCourse, ['cid' => $cid]); |
||
93 | |||
94 | if (!empty($sid)) { |
||
95 | $sqlSess = <<<SQL |
||
96 | SELECT DISTINCT u.id, u.firstname, u.lastname |
||
97 | FROM session_rel_course_rel_user scru |
||
98 | INNER JOIN user u ON u.id = scru.user_id |
||
99 | WHERE scru.c_id = :cid AND scru.session_id = :sid |
||
100 | SQL; |
||
101 | $more = $conn->fetchAllAssociative($sqlSess, ['cid' => $cid, 'sid' => (int) $sid]); |
||
102 | |||
103 | $seen = []; |
||
104 | foreach ($userRows as $row) { $seen[(int)$row['id']] = true; } |
||
105 | foreach ($more as $row) { |
||
106 | $uid = (int) $row['id']; |
||
107 | if (!isset($seen[$uid])) { |
||
108 | $userRows[] = $row; $seen[$uid] = true; |
||
109 | } |
||
110 | } |
||
111 | } |
||
112 | |||
113 | $options = []; |
||
114 | foreach ($userRows as $u) { |
||
115 | $uid = (int) $u['id']; |
||
116 | if ($uid === $me) { continue; } |
||
117 | $label = trim(($u['firstname'] ?? '').' '.($u['lastname'] ?? '')) ?: ('User #'.$uid); |
||
118 | $options[] = ['value' => 'user_'.$uid, 'label' => $label]; |
||
119 | } |
||
120 | |||
121 | array_unshift($options, ['value' => 'self', 'label' => '— Just upload —']); |
||
122 | |||
123 | return $this->json($options); |
||
124 | } |
||
125 | |||
126 | #[Route('/categories', name: 'dropbox_categories_list', methods: ['GET'])] |
||
127 | public function listCategories(Request $r): JsonResponse |
||
128 | { |
||
129 | [$cid, $sid] = $this->context($r); |
||
130 | $uid = (int) $this->getUser()?->getId(); |
||
131 | $area = (string) $r->query->get('area', 'sent'); |
||
132 | |||
133 | $cats = $this->categoryRepo->findByContextAndArea($cid, $sid, $uid, $area); |
||
134 | |||
135 | $rows = array_map(fn(CDropboxCategory $c) => [ |
||
136 | 'id' => $c->getCatId(), |
||
137 | 'title' => $c->getTitle(), |
||
138 | ], $cats); |
||
139 | |||
140 | array_unshift($rows, ['id' => 0, 'title' => 'Root']); |
||
141 | |||
142 | return $this->json($rows); |
||
143 | } |
||
144 | |||
145 | #[Route('/categories', name: 'dropbox_categories_create', methods: ['POST'])] |
||
146 | public function createCategory(Request $r): JsonResponse |
||
147 | { |
||
148 | [$cid, $sid] = $this->context($r); |
||
149 | $uid = (int) $this->getUser()?->getId(); |
||
150 | $payload = json_decode($r->getContent(), true) ?: []; |
||
151 | $title = trim((string) ($payload['title'] ?? '')); |
||
152 | $area = (string) ($payload['area'] ?? 'sent'); |
||
153 | |||
154 | if ($title === '' || !\in_array($area, ['sent','received'], true)) { |
||
155 | return $this->json(['message' => 'Invalid payload'], 400); |
||
156 | } |
||
157 | |||
158 | $cat = $this->categoryRepo->createForUser($cid, $sid, $uid, $title, $area); |
||
159 | return $this->json(['id' => (int) $cat->getCatId(), 'title' => $cat->getTitle()], 201); |
||
160 | } |
||
161 | |||
162 | #[Route('/files', name: 'dropbox_files_list', methods: ['GET'])] |
||
163 | public function listFiles(Request $r): JsonResponse |
||
164 | { |
||
165 | [$cid, $sid] = $this->context($r); |
||
166 | $uid = (int) $this->getUser()?->getId(); |
||
167 | $area = (string) $r->query->get('area', 'sent'); |
||
168 | $categoryId = (int) $r->query->get('categoryId', 0); |
||
169 | |||
170 | if ($area === 'sent') { |
||
171 | $files = $this->fileRepo->findSentByContextAndCategory($cid, $sid, $uid, $categoryId); |
||
172 | |||
173 | $out = array_map(function (array $row) { |
||
174 | $dt = new DateTimeImmutable($row['lastUploadDate']); |
||
175 | return [ |
||
176 | 'id' => (int) $row['id'], |
||
177 | 'title' => $row['title'], |
||
178 | 'description' => $row['description'], |
||
179 | 'size' => (int) $row['filesize'], |
||
180 | 'sizeHuman' => $this->humanSize((int) $row['filesize']), |
||
181 | 'lastUploadDate'=> $dt->format(DATE_ATOM), |
||
182 | 'lastUploadAgo' => $this->ago($dt), |
||
183 | 'recipients' => $row['recipients'], |
||
184 | 'categoryId' => (int) $row['catId'], |
||
185 | ]; |
||
186 | }, $files); |
||
187 | |||
188 | return $this->json($out); |
||
189 | } |
||
190 | |||
191 | $files = $this->fileRepo->findReceivedByContextAndCategory($cid, $sid, $uid, $categoryId); |
||
192 | |||
193 | $out = array_map(function (array $row) { |
||
194 | $dt = new DateTimeImmutable($row['lastUploadDate']); |
||
195 | return [ |
||
196 | 'id' => (int) $row['id'], |
||
197 | 'title' => $row['title'], |
||
198 | 'description' => $row['description'], |
||
199 | 'size' => (int) $row['filesize'], |
||
200 | 'sizeHuman' => $this->humanSize((int) $row['filesize']), |
||
201 | 'lastUploadDate'=> $dt->format(DATE_ATOM), |
||
202 | 'lastUploadAgo' => $this->ago($dt), |
||
203 | 'uploader' => $row['uploader'], |
||
204 | 'categoryId' => (int) $row['catId'], |
||
205 | ]; |
||
206 | }, $files); |
||
207 | |||
208 | return $this->json($out); |
||
209 | } |
||
210 | |||
211 | #[Route('/files/{id<\d+>}/move', name: 'dropbox_file_move', methods: ['PATCH'])] |
||
212 | public function moveFile(int $id, Request $r): JsonResponse |
||
213 | { |
||
214 | [$cid, $sid] = $this->context($r); |
||
215 | $uid = (int) $this->getUser()?->getId(); |
||
216 | $payload = json_decode($r->getContent(), true) ?: []; |
||
217 | $targetCatId = (int) ($payload['targetCatId'] ?? 0); |
||
218 | $area = (string) ($payload['area'] ?? 'sent'); |
||
219 | |||
220 | if (!\in_array($area, ['sent','received'], true)) { |
||
221 | return $this->json(['message' => 'Invalid "area"'], 400); |
||
222 | } |
||
223 | |||
224 | $affected = $this->fileRepo->moveFileForArea($id, $cid, $sid, $uid, $targetCatId, $area); |
||
225 | return $this->json(['moved' => $affected > 0]); |
||
226 | } |
||
227 | |||
228 | #[Route('/files', name: 'dropbox_files_delete', methods: ['DELETE'])] |
||
229 | public function deleteFiles(Request $r): JsonResponse |
||
230 | { |
||
231 | [$cid, $sid] = $this->context($r); |
||
232 | $uid = (int) $this->getUser()?->getId(); |
||
233 | $payload = json_decode($r->getContent(), true) ?: []; |
||
234 | $ids = array_map('intval', $payload['ids'] ?? []); |
||
235 | $area = (string) ($payload['area'] ?? 'sent'); |
||
236 | |||
237 | if (!$ids) { |
||
|
|||
238 | return $this->json(['deleted' => 0]); |
||
239 | } |
||
240 | |||
241 | $deleted = $this->fileRepo->deleteVisibility($ids, $cid, $sid, $uid, $area); |
||
242 | return $this->json(['deleted' => $deleted]); |
||
243 | } |
||
244 | |||
245 | #[Route('/files/{id<\d+>}/feedback', name: 'dropbox_feedback_list', methods: ['GET'])] |
||
246 | public function listFeedback(int $id, Request $r): JsonResponse |
||
247 | { |
||
248 | [$cid] = $this->context($r); |
||
249 | $rows = $this->feedbackRepo->listByFile($cid, $id); |
||
250 | |||
251 | return $this->json(array_map(function(CDropboxFeedback $f) { |
||
252 | return [ |
||
253 | 'id' => $f->getFeedbackId(), |
||
254 | 'authorId' => $f->getAuthorUserId(), |
||
255 | 'authorName' => $this->userFullName($f->getAuthorUserId()), |
||
256 | 'text' => $f->getFeedback(), |
||
257 | 'date' => $f->getFeedbackDate()->format(DATE_ATOM), |
||
258 | ]; |
||
259 | }, $rows)); |
||
260 | } |
||
261 | |||
262 | #[Route('/files/{id<\d+>}/feedback', name: 'dropbox_feedback_create', methods: ['POST'])] |
||
263 | public function createFeedback(int $id, Request $r): JsonResponse |
||
264 | { |
||
265 | [$cid] = $this->context($r); |
||
266 | $uid = (int) $this->getUser()?->getId(); |
||
267 | $payload= json_decode($r->getContent(), true) ?: []; |
||
268 | $text = trim((string) ($payload['text'] ?? '')); |
||
269 | |||
270 | if ($text === '') { |
||
271 | return $this->json(['message' => 'Empty feedback'], 400); |
||
272 | } |
||
273 | |||
274 | $this->feedbackRepo->createForFile($cid, $id, $uid, $text); |
||
275 | return $this->json(['ok' => true], 201); |
||
276 | } |
||
277 | |||
278 | #[Route('/categories/{id<\d+>}', name: 'dropbox_categories_rename', methods: ['PATCH'])] |
||
279 | public function renameCategory(int $id, Request $r): JsonResponse |
||
280 | { |
||
281 | [$cid, $sid] = $this->context($r); |
||
282 | $uid = (int) $this->getUser()?->getId(); |
||
283 | $payload = json_decode($r->getContent(), true) ?: []; |
||
284 | $title = trim((string) ($payload['title'] ?? '')); |
||
285 | $area = (string) ($payload['area'] ?? 'sent'); |
||
286 | |||
287 | if ($title === '' || !\in_array($area, ['sent','received'], true)) { |
||
288 | return $this->json(['message' => 'Invalid payload'], 400); |
||
289 | } |
||
290 | |||
291 | $cat = $this->categoryRepo->findOneBy([ |
||
292 | 'cId' => $cid, |
||
293 | 'sessionId' => (int) ($sid ?? 0), |
||
294 | 'userId' => $uid, |
||
295 | 'catId' => $id, |
||
296 | 'sent' => $area === 'sent', |
||
297 | 'received' => $area === 'received', |
||
298 | ]); |
||
299 | |||
300 | if (!$cat) { |
||
301 | return $this->json(['message' => 'Category not found'], 404); |
||
302 | } |
||
303 | |||
304 | $cat->setTitle($title); |
||
305 | $this->em->persist($cat); |
||
306 | $this->em->flush(); |
||
307 | |||
308 | return $this->json(['ok' => true]); |
||
309 | } |
||
310 | |||
311 | #[Route('/categories/{id<\d+>}', name: 'dropbox_categories_delete', methods: ['DELETE'])] |
||
312 | public function deleteCategory(int $id, Request $r): JsonResponse |
||
313 | { |
||
314 | [$cid, $sid] = $this->context($r); |
||
315 | $uid = (int) $this->getUser()?->getId(); |
||
316 | $area = (string) $r->query->get('area', 'sent'); |
||
317 | |||
318 | if (!\in_array($area, ['sent','received'], true)) { |
||
319 | return $this->json(['message' => 'Invalid area'], 400); |
||
320 | } |
||
321 | if ($id === 0) { |
||
322 | return $this->json(['message' => 'Cannot delete root category'], 400); |
||
323 | } |
||
324 | |||
325 | $conn = $this->em->getConnection(); |
||
326 | $sid = (int) ($sid ?? 0); |
||
327 | |||
328 | if ($area === 'sent') { |
||
329 | $ids = $conn->fetchFirstColumn( |
||
330 | <<<SQL |
||
331 | SELECT f.iid |
||
332 | FROM c_dropbox_file f |
||
333 | WHERE f.c_id = :cid |
||
334 | AND f.session_id = :sid |
||
335 | AND f.uploader_id = :uid |
||
336 | AND f.cat_id = :cat |
||
337 | SQL, |
||
338 | ['cid' => $cid, 'sid' => $sid, 'uid' => $uid, 'cat' => $id] |
||
339 | ); |
||
340 | |||
341 | $deletedFiles = 0; |
||
342 | if ($ids) { |
||
343 | $deletedFiles = $this->fileRepo->deleteVisibility(array_map('intval', $ids), $cid, $sid, $uid, 'sent'); |
||
344 | } |
||
345 | |||
346 | $cat = $this->categoryRepo->findOneBy([ |
||
347 | 'cId' => $cid, |
||
348 | 'sessionId' => $sid, |
||
349 | 'userId' => $uid, |
||
350 | 'catId' => $id, |
||
351 | 'sent' => true, |
||
352 | 'received' => false, |
||
353 | ]); |
||
354 | if ($cat) { |
||
355 | $this->em->remove($cat); |
||
356 | $this->em->flush(); |
||
357 | } |
||
358 | |||
359 | return $this->json(['ok' => true, 'deletedFiles' => (int) $deletedFiles]); |
||
360 | } |
||
361 | |||
362 | $ids = $conn->fetchFirstColumn( |
||
363 | <<<SQL |
||
364 | SELECT p.file_id |
||
365 | FROM c_dropbox_person p |
||
366 | WHERE p.c_id = :cid |
||
367 | AND p.user_id = :uid |
||
368 | AND p.cat_id = :cat |
||
369 | SQL, |
||
370 | ['cid' => $cid, 'uid' => $uid, 'cat' => $id] |
||
371 | ); |
||
372 | |||
373 | $removedVisibilities = 0; |
||
374 | if ($ids) { |
||
375 | $removedVisibilities = $this->fileRepo->deleteVisibility(array_map('intval', $ids), $cid, $sid, $uid, 'received'); |
||
376 | } |
||
377 | |||
378 | $cat = $this->categoryRepo->findOneBy([ |
||
379 | 'cId' => $cid, |
||
380 | 'sessionId' => $sid, |
||
381 | 'userId' => $uid, |
||
382 | 'catId' => $id, |
||
383 | 'sent' => false, |
||
384 | 'received' => true, |
||
385 | ]); |
||
386 | if ($cat) { |
||
387 | $this->em->remove($cat); |
||
388 | $this->em->flush(); |
||
389 | } |
||
390 | |||
391 | return $this->json(['ok' => true, 'removedVisibilities' => (int) $removedVisibilities]); |
||
392 | } |
||
393 | |||
394 | #[Route('/files/{id<\d+>}/download', name: 'dropbox_file_download', methods: ['GET'])] |
||
395 | public function download(int $id, Request $r): Response |
||
396 | { |
||
397 | [$cid] = $this->context($r); |
||
398 | |||
399 | $file = $this->fileRepo->find($id); |
||
400 | if (!$file || (int) $file->getCId() !== $cid) { |
||
401 | throw $this->createNotFoundException('File not found'); |
||
402 | } |
||
403 | |||
404 | // Resolve the resource file attached to this dropbox entry |
||
405 | $resourceNode = $file->getResourceNode(); |
||
406 | $resourceFile = $resourceNode?->getFirstResourceFile(); |
||
407 | |||
408 | if (!$resourceFile) { |
||
409 | throw $this->createNotFoundException('Resource file not found'); |
||
410 | } |
||
411 | |||
412 | // Display name: prefer the dropbox visible title; fallback to original name |
||
413 | $downloadName = trim($file->getTitle() ?: $resourceFile->getOriginalName() ?: 'file.bin'); |
||
414 | |||
415 | // Guess mime |
||
416 | $mime = $resourceFile->getMimeType() ?: 'application/octet-stream'; |
||
417 | if ($mime === 'application/octet-stream' && class_exists(MimeTypes::class)) { |
||
418 | $types = new MimeTypes(); |
||
419 | $guess = $types->guessMimeType($downloadName); |
||
420 | if ($guess) { |
||
421 | $mime = $guess; |
||
422 | } |
||
423 | } |
||
424 | |||
425 | // Stream from ResourceNode FS (no tmp-path fallback) |
||
426 | $stream = $this->resourceNodeRepository->getResourceNodeFileStream($resourceNode, $resourceFile); |
||
427 | if (!\is_resource($stream)) { |
||
428 | throw $this->createNotFoundException('Resource stream not available'); |
||
429 | } |
||
430 | |||
431 | $size = (int) $resourceFile->getSize(); |
||
432 | |||
433 | $response = new StreamedResponse(function () use ($stream) { |
||
434 | // Stream file in chunks |
||
435 | while (!feof($stream)) { |
||
436 | $buffer = fread($stream, 8192); |
||
437 | if ($buffer === false) { |
||
438 | break; |
||
439 | } |
||
440 | echo $buffer; |
||
441 | @ob_flush(); |
||
442 | flush(); |
||
443 | } |
||
444 | fclose($stream); |
||
445 | }); |
||
446 | |||
447 | $disposition = $response->headers->makeDisposition( |
||
448 | ResponseHeaderBag::DISPOSITION_ATTACHMENT, |
||
449 | $downloadName |
||
450 | ); |
||
451 | |||
452 | $response->headers->set('Content-Type', $mime); |
||
453 | if ($size > 0) { |
||
454 | $response->headers->set('Content-Length', (string) $size); |
||
455 | $response->headers->set('Accept-Ranges', 'none'); // simple download (no Range) |
||
456 | } |
||
457 | $response->headers->set('Content-Disposition', $disposition); |
||
458 | |||
459 | return $response; |
||
460 | } |
||
461 | |||
462 | #[Route('/files/{id<\d+>}', name: 'dropbox_file_get', methods: ['GET'])] |
||
463 | public function getFile(int $id, Request $r): JsonResponse |
||
464 | { |
||
465 | [$cid] = $this->context($r); |
||
466 | $row = $this->fileRepo->find($id); |
||
467 | if (!$row || (int) $row->getCId() !== $cid) { |
||
468 | return $this->json(['message' => 'File not found'], 404); |
||
469 | } |
||
470 | return $this->json([ |
||
471 | 'id' => $row->getIid(), |
||
472 | 'title' => $row->getTitle(), |
||
473 | 'description' => $row->getDescription(), |
||
474 | 'categoryId' => $row->getCatId(), |
||
475 | ]); |
||
476 | } |
||
477 | |||
478 | #[Route('/files/{id<\d+>}/update', name: 'dropbox_file_update', methods: ['POST'])] |
||
542 | ]); |
||
543 | } |
||
544 | |||
545 | #[Route('/categories/{id<\d+>}/zip', name: 'dropbox_category_zip', methods: ['GET'])] |
||
546 | public function downloadCategoryZip(int $id, Request $r): Response |
||
547 | { |
||
548 | [$cid, $sid] = $this->context($r); |
||
549 | $uid = (int) $this->getUser()?->getId(); |
||
550 | $area = (string) $r->query->get('area', 'sent'); |
||
551 | $catId = (int) $id; |
||
552 | |||
553 | if (!\in_array($area, ['sent','received'], true)) { |
||
554 | return $this->json(['message' => 'Invalid area'], 400); |
||
555 | } |
||
556 | |||
557 | // Fetch candidate rows |
||
558 | $conn = $this->em->getConnection(); |
||
559 | $sid = (int) ($sid ?? 0); |
||
560 | |||
561 | if ($area === 'sent') { |
||
562 | $sql = <<<SQL |
||
563 | SELECT f.iid, f.title, f.filename, f.filesize |
||
564 | FROM c_dropbox_file f |
||
565 | WHERE f.c_id = :cid |
||
566 | AND f.session_id = :sid |
||
567 | AND f.uploader_id = :uid |
||
568 | AND f.cat_id = :cat |
||
569 | ORDER BY f.last_upload_date DESC, f.iid DESC |
||
570 | SQL; |
||
571 | $rows = $conn->fetchAllAssociative($sql, [ |
||
572 | 'cid' => $cid, 'sid' => $sid, 'uid' => $uid, 'cat' => $catId, |
||
573 | ]); |
||
574 | $zipLabel = 'sent'; |
||
575 | } else { |
||
576 | $sql = <<<SQL |
||
577 | SELECT f.iid, f.title, f.filename, f.filesize |
||
578 | FROM c_dropbox_person p |
||
579 | INNER JOIN c_dropbox_file f |
||
580 | ON f.iid = p.file_id |
||
581 | AND f.c_id = p.c_id |
||
582 | WHERE p.c_id = :cid |
||
583 | AND p.user_id = :uid |
||
584 | AND f.session_id = :sid |
||
585 | AND f.cat_id = :cat |
||
586 | ORDER BY f.last_upload_date DESC, f.iid DESC |
||
587 | SQL; |
||
588 | $rows = $conn->fetchAllAssociative($sql, [ |
||
589 | 'cid' => $cid, 'uid' => $uid, 'sid' => $sid, 'cat' => $catId, |
||
590 | ]); |
||
591 | $zipLabel = 'received'; |
||
592 | } |
||
593 | |||
594 | if (!$rows) { |
||
595 | return $this->json(['message' => 'No files in this category'], 404); |
||
596 | } |
||
597 | |||
598 | // Prepare ZIP |
||
599 | $coursePath = sprintf('%s/course_%d/dropbox', sys_get_temp_dir(), $cid); |
||
600 | $tmpZipPath = tempnam(sys_get_temp_dir(), 'dbxzip_'); |
||
601 | if ($tmpZipPath === false) { |
||
602 | return $this->json(['message' => 'Unable to create temp file'], 500); |
||
603 | } |
||
604 | $finalZipPath = $tmpZipPath . '.zip'; |
||
605 | @rename($tmpZipPath, $finalZipPath); |
||
606 | |||
607 | $zip = new \ZipArchive(); |
||
608 | if (true !== $zip->open($finalZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { |
||
609 | @unlink($finalZipPath); |
||
610 | return $this->json(['message' => 'Unable to open zip archive'], 500); |
||
611 | } |
||
612 | |||
613 | $added = 0; |
||
614 | |||
615 | // Add files – try physical temp path, then ResourceNode FS |
||
616 | foreach ($rows as $row) { |
||
617 | $safePhysical = (string) ($row['filename'] ?? ''); |
||
618 | $downloadName = trim((string) ($row['title'] ?? '')) ?: ($safePhysical ?: 'file.bin'); |
||
619 | $downloadName = str_replace(['\\', '/', "\0"], '_', $downloadName); |
||
620 | |||
621 | // Ensure unique entry name |
||
622 | $entryName = $downloadName; |
||
623 | $i = 1; |
||
624 | while ($zip->locateName($entryName, \ZipArchive::FL_NOCASE | \ZipArchive::FL_NODIR) !== false) { |
||
625 | $pi = pathinfo($downloadName); |
||
626 | $base = $pi['filename'] ?? $downloadName; |
||
627 | $ext = isset($pi['extension']) && $pi['extension'] !== '' ? ('.'.$pi['extension']) : ''; |
||
628 | $entryName = $base . ' (' . (++$i) . ')' . $ext; |
||
629 | } |
||
630 | |||
631 | $addedThis = false; |
||
632 | |||
633 | // (a) Try physical temp path |
||
634 | if ($safePhysical !== '') { |
||
635 | $fullPath = $coursePath . '/' . $safePhysical; |
||
636 | if (is_file($fullPath)) { |
||
637 | $zip->addFile($fullPath, $entryName); |
||
638 | $added++; $addedThis = true; |
||
639 | } |
||
640 | } |
||
641 | |||
642 | // (b) Fallback: ResourceNode filesystem |
||
643 | if (!$addedThis) { |
||
644 | // Load entity to reach ResourceNode |
||
645 | $fileEntity = $this->fileRepo->find((int) $row['iid']); |
||
646 | $resourceNode = $fileEntity?->getResourceNode(); |
||
647 | $resourceFile = $resourceNode?->getFirstResourceFile(); |
||
648 | if ($resourceFile) { |
||
649 | try { |
||
650 | $path = $this->resourceNodeRepository->getFilename($resourceFile); |
||
651 | $content = $this->resourceNodeRepository->getFileSystem()->read($path); |
||
652 | if ($content !== false && $content !== null) { |
||
653 | $zip->addFromString($entryName, $content); |
||
654 | $added++; $addedThis = true; |
||
655 | } |
||
656 | } catch (\Throwable $e) { |
||
657 | // ignore and continue |
||
658 | } |
||
659 | } |
||
660 | } |
||
661 | } |
||
662 | |||
663 | $zip->close(); |
||
664 | |||
665 | if ($added === 0) { |
||
666 | @unlink($finalZipPath); |
||
667 | return $this->json(['message' => 'No files found to include'], 404); |
||
668 | } |
||
669 | |||
670 | // Build download name |
||
671 | $catTitle = 'Root'; |
||
672 | if ($catId !== 0) { |
||
673 | $cat = $this->categoryRepo->findOneBy([ |
||
674 | 'cId' => $cid, |
||
675 | 'sessionId' => $sid, |
||
676 | 'userId' => $uid, |
||
677 | 'catId' => $catId, |
||
678 | 'sent' => $area === 'sent', |
||
679 | 'received' => $area === 'received', |
||
680 | ]); |
||
681 | if ($cat) { $catTitle = $cat->getTitle(); } |
||
682 | } |
||
683 | $slug = $this->slugger->slug($catTitle ?: 'category')->lower(); |
||
684 | $downloadZipName = sprintf('dropbox-%s-%s-%s.zip', $zipLabel, $slug, date('Ymd_His')); |
||
685 | |||
686 | $resp = new BinaryFileResponse($finalZipPath); |
||
687 | $resp->setContentDisposition( |
||
688 | ResponseHeaderBag::DISPOSITION_ATTACHMENT, |
||
689 | $downloadZipName |
||
690 | ); |
||
691 | $resp->deleteFileAfterSend(true); |
||
692 | |||
693 | return $resp; |
||
694 | } |
||
695 | |||
696 | private function userFullName(int $userId): string |
||
708 | } |
||
709 | } |
||
710 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.