Passed
Pull Request — master (#6257)
by
unknown
08:48
created

BaseResourceFileAction::extractZipFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 16
nc 2
nop 2
dl 0
loc 35
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller\Api;
8
9
use Chamilo\CoreBundle\Component\Utils\CreateUploadedFile;
10
use Chamilo\CoreBundle\Entity\AbstractResource;
11
use Chamilo\CoreBundle\Entity\Course;
12
use Chamilo\CoreBundle\Entity\ResourceFile;
13
use Chamilo\CoreBundle\Entity\ResourceLink;
14
use Chamilo\CoreBundle\Entity\ResourceNode;
15
use Chamilo\CoreBundle\Entity\ResourceRight;
16
use Chamilo\CoreBundle\Entity\Session;
17
use Chamilo\CoreBundle\Entity\User;
18
use Chamilo\CoreBundle\Repository\ResourceRepository;
19
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
20
use Chamilo\CourseBundle\Entity\CDocument;
21
use Chamilo\CourseBundle\Entity\CGroup;
22
use DateTime;
23
use Doctrine\ORM\EntityManager;
24
use Doctrine\ORM\EntityManagerInterface;
25
use Exception;
26
use InvalidArgumentException;
27
use Symfony\Component\HttpFoundation\File\UploadedFile;
28
use Symfony\Component\HttpFoundation\Request;
29
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
30
use Symfony\Component\HttpKernel\KernelInterface;
31
use Symfony\Contracts\Translation\TranslatorInterface;
32
use ZipArchive;
33
34
class BaseResourceFileAction
35
{
36
    public static function setLinks(AbstractResource $resource, EntityManagerInterface $em): void
37
    {
38
        $resourceNode = $resource->getResourceNode();
39
        $links = $resource->getResourceLinkArray();
40
        if ($links) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $links of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
41
            $groupRepo = $em->getRepository(CGroup::class);
42
            $courseRepo = $em->getRepository(Course::class);
43
            $sessionRepo = $em->getRepository(Session::class);
44
            $userRepo = $em->getRepository(User::class);
45
46
            foreach ($links as $link) {
47
                $resourceLink = new ResourceLink();
48
                $linkSet = false;
49
                if (isset($link['cid']) && !empty($link['cid'])) {
50
                    $course = $courseRepo->find($link['cid']);
51
                    if (null !== $course) {
52
                        $linkSet = true;
53
                        $resourceLink->setCourse($course);
54
                    } else {
55
                        throw new InvalidArgumentException(\sprintf('Course #%s does not exists', $link['cid']));
56
                    }
57
                }
58
59
                if (isset($link['sid']) && !empty($link['sid'])) {
60
                    $session = $sessionRepo->find($link['sid']);
61
                    if (null !== $session) {
62
                        $linkSet = true;
63
                        $resourceLink->setSession($session);
64
                    } else {
65
                        throw new InvalidArgumentException(\sprintf('Session #%s does not exists', $link['sid']));
66
                    }
67
                }
68
69
                if (isset($link['gid']) && !empty($link['gid'])) {
70
                    $group = $groupRepo->find($link['gid']);
71
                    if (null !== $group) {
72
                        $linkSet = true;
73
                        $resourceLink->setGroup($group);
74
                    } else {
75
                        throw new InvalidArgumentException(\sprintf('Group #%s does not exists', $link['gid']));
76
                    }
77
                }
78
79
                if (isset($link['uid']) && !empty($link['uid'])) {
80
                    $user = $userRepo->find($link['uid']);
81
                    if (null !== $user) {
82
                        $linkSet = true;
83
                        $resourceLink->setUser($user);
84
                    } else {
85
                        throw new InvalidArgumentException(\sprintf('User #%s does not exists', $link['uid']));
86
                    }
87
                }
88
89
                if (isset($link['visibility'])) {
90
                    $resourceLink->setVisibility((int) $link['visibility']);
91
                } else {
92
                    throw new InvalidArgumentException('Link needs a visibility key');
93
                }
94
95
                if ($linkSet) {
96
                    $em->persist($resourceLink);
97
                    $resourceNode->addResourceLink($resourceLink);
98
                    // $em->persist($resourceNode);
99
                    // $em->persist($resource->getResourceNode());
100
                }
101
            }
102
        }
103
104
        // Use by Chamilo not api platform.
105
        $links = $resource->getResourceLinkEntityList();
106
        if ($links) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $links of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
107
            // error_log('$resource->getResourceLinkEntityList()');
108
            foreach ($links as $link) {
109
                /*$rights = [];
110
                 * switch ($link->getVisibility()) {
111
                 * case ResourceLink::VISIBILITY_PENDING:
112
                 * case ResourceLink::VISIBILITY_DRAFT:
113
                 * $editorMask = ResourceNodeVoter::getEditorMask();
114
                 * $resourceRight = new ResourceRight();
115
                 * $resourceRight
116
                 * ->setMask($editorMask)
117
                 * ->setRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER)
118
                 * ;
119
                 * $rights[] = $resourceRight;
120
                 * break;
121
                 * }
122
                 * if (!empty($rights)) {
123
                 * foreach ($rights as $right) {
124
                 * $link->addResourceRight($right);
125
                 * }
126
                 * }*/
127
                // error_log('link adding to node: '.$resource->getResourceNode()->getId());
128
                // error_log('link with user : '.$link->getUser()->getUsername());
129
                $resource->getResourceNode()->addResourceLink($link);
130
131
                $em->persist($link);
132
            }
133
        }
134
    }
135
136
    /**
137
     * @todo use this function inside handleCreateFileRequest
138
     */
139
    protected function handleCreateRequest(AbstractResource $resource, ResourceRepository $resourceRepository, Request $request): array
140
    {
141
        $contentData = $request->getContent();
142
143
        if (!empty($contentData)) {
144
            $contentData = json_decode($contentData, true);
145
            $title = $contentData['title'] ?? '';
146
            $parentResourceNodeId = (int) ($contentData['parentResourceNodeId'] ?? 0);
147
            $resourceLinkList = $contentData['resourceLinkList'] ?? [];
148
            if (empty($resourceLinkList)) {
149
                $resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? [];
150
            }
151
        } else {
152
            $contentData = $request->request->all();
153
            $title = $request->get('title');
154
            $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
155
            $resourceLinkList = $request->get('resourceLinkList', []);
156
            if (!empty($resourceLinkList)) {
157
                $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
158
                if (empty($resourceLinkList)) {
159
                    $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
160
161
                    throw new InvalidArgumentException($message);
162
                }
163
            }
164
        }
165
166
        if (0 === $parentResourceNodeId) {
167
            throw new Exception('Parameter parentResourceNodeId int value is needed');
168
        }
169
170
        $resource->setParentResourceNode($parentResourceNodeId);
171
172
        if (empty($title)) {
173
            throw new InvalidArgumentException('title is required');
174
        }
175
176
        $resource->setResourceName($title);
177
178
        // Set resource link list if exists.
179
        if (!empty($resourceLinkList)) {
180
            $resource->setResourceLinkArray($resourceLinkList);
181
        }
182
183
        return $contentData;
184
    }
185
186
    /**
187
     * Handles the creation logic for a student publication comment resource.
188
     */
189
    public function handleCreateCommentRequest(
190
        AbstractResource $resource,
191
        ResourceRepository $resourceRepository,
192
        Request $request,
193
        EntityManager $em,
194
        string $fileExistsOption = '',
195
        ?TranslatorInterface $translator = null
196
    ): array {
197
        $title = $request->get('comment', '');
198
        $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
199
        $fileType = $request->get('filetype');
200
        $uploadedFile = null;
201
202
        if (empty($fileType)) {
203
            throw new Exception('filetype needed: folder or file');
204
        }
205
206
        if (0 === $parentResourceNodeId) {
207
            throw new Exception('parentResourceNodeId int value needed');
208
        }
209
210
        $resource->setParentResourceNode($parentResourceNodeId);
211
212
        if ($request->files->count() > 0 && $request->files->has('uploadFile')) {
213
            /** @var UploadedFile $uploadedFile */
214
            $uploadedFile = $request->files->get('uploadFile');
215
            $resource->setUploadFile($uploadedFile);
216
        }
217
218
        return [
219
            'title' => $title,
220
            'filename' => $uploadedFile?->getClientOriginalName(),
221
            'filetype' => $fileType,
222
        ];
223
    }
224
225
    /**
226
     * Function loaded when creating a resource using the api, then the ResourceListener is executed.
227
     */
228
    public function handleCreateFileRequest(
229
        AbstractResource $resource,
230
        ResourceRepository $resourceRepository,
231
        Request $request,
232
        EntityManager $em,
233
        string $fileExistsOption = '',
234
        ?TranslatorInterface $translator = null
235
    ): array {
236
        $contentData = $request->getContent();
237
238
        if (!empty($contentData)) {
239
            $contentData = json_decode($contentData, true);
240
            $title = $contentData['title'] ?? '';
241
            $comment = $contentData['comment'] ?? '';
242
            $parentResourceNodeId = (int) ($contentData['parentResourceNodeId'] ?? 0);
243
            $fileType = $contentData['filetype'] ?? '';
244
            $resourceLinkList = $contentData['resourceLinkList'] ?? [];
245
        } else {
246
            $title = $request->get('title');
247
            $comment = $request->get('comment');
248
            $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
249
            $fileType = $request->get('filetype');
250
            $resourceLinkList = $request->get('resourceLinkList', []);
251
            if (!empty($resourceLinkList)) {
252
                $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
253
                if (empty($resourceLinkList)) {
254
                    $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
255
256
                    throw new InvalidArgumentException($message);
257
                }
258
            }
259
        }
260
261
        if (empty($fileType)) {
262
            throw new Exception('filetype needed: folder or file');
263
        }
264
265
        if (0 === $parentResourceNodeId) {
266
            throw new Exception('parentResourceNodeId int value needed');
267
        }
268
269
        $resource->setParentResourceNode($parentResourceNodeId);
270
271
        switch ($fileType) {
272
            case 'certificate':
273
            case 'file':
274
                $content = '';
275
                if ($request->request->has('contentFile')) {
276
                    $content = $request->request->get('contentFile');
277
                }
278
                $fileParsed = false;
279
                // File upload.
280
                if ($request->files->count() > 0) {
281
                    if (!$request->files->has('uploadFile')) {
282
                        throw new BadRequestHttpException('"uploadFile" is required');
283
                    }
284
285
                    /** @var UploadedFile $uploadedFile */
286
                    $uploadedFile = $request->files->get('uploadFile');
287
                    $title = $uploadedFile->getClientOriginalName();
288
289
                    if (empty($title)) {
290
                        throw new InvalidArgumentException('title is required');
291
                    }
292
293
                    // Handle the appropriate action based on the fileExistsOption
294
                    if (!empty($fileExistsOption)) {
295
                        // Check if a document with the same title and parent resource node already exists
296
                        $existingDocument = $resourceRepository->findByTitleAndParentResourceNode($title, $parentResourceNodeId);
297
                        if ($existingDocument) {
298
                            if ('overwrite' === $fileExistsOption) {
299
                                $existingDocument->setTitle($title);
0 ignored issues
show
Bug introduced by
The method setTitle() does not exist on Chamilo\CoreBundle\Entity\AbstractResource. It seems like you code against a sub-type of said class. However, the method does not exist in Chamilo\CourseBundle\Ent...CAnnouncementAttachment or Chamilo\CoreBundle\Entity\MessageAttachment or Chamilo\CoreBundle\Entity\SocialPostAttachment or Chamilo\CourseBundle\Entity\CForumAttachment or Chamilo\CoreBundle\Entity\AccessUrl or Chamilo\CoreBundle\Entity\TicketMessageAttachment or Chamilo\CourseBundle\Entity\CToolIntro or Chamilo\CourseBundle\Entity\CQuizQuestion or Chamilo\CourseBundle\Ent...CalendarEventAttachment. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

299
                                $existingDocument->/** @scrutinizer ignore-call */ 
300
                                                   setTitle($title);
Loading history...
300
                                $existingDocument->setComment($comment);
0 ignored issues
show
Bug introduced by
The method setComment() does not exist on Chamilo\CoreBundle\Entity\AbstractResource. It seems like you code against a sub-type of Chamilo\CoreBundle\Entity\AbstractResource such as Chamilo\CourseBundle\Ent...CAnnouncementAttachment or Chamilo\CoreBundle\Entity\MessageAttachment or Chamilo\CourseBundle\Entity\CWiki or Chamilo\CourseBundle\Entity\CForumAttachment or Chamilo\CoreBundle\Entity\PersonalFile or Chamilo\CourseBundle\Entity\CDocument or Chamilo\CourseBundle\Ent...CalendarEventAttachment or Chamilo\CourseBundle\Ent...udentPublicationComment or Chamilo\CourseBundle\Entity\CCalendarEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

300
                                $existingDocument->/** @scrutinizer ignore-call */ 
301
                                                   setComment($comment);
Loading history...
301
302
                                $resourceNode = $existingDocument->getResourceNode();
303
304
                                $resourceFile = $resourceNode->getFirstResourceFile();
305
                                if ($resourceFile instanceof ResourceFile) {
306
                                    $resourceFile->setFile($uploadedFile);
307
                                    $em->persist($resourceFile);
308
                                } else {
309
                                    $existingDocument->setUploadFile($uploadedFile);
310
                                }
311
312
                                $resourceNode->setUpdatedAt(new DateTime());
313
                                $existingDocument->setResourceNode($resourceNode);
314
315
                                $em->persist($existingDocument);
316
                                $em->flush();
317
318
                                // Return any data you need for further processing
319
                                return [
320
                                    'title' => $title,
321
                                    'filetype' => 'file',
322
                                    'comment' => $comment,
323
                                ];
324
                            }
325
326
                            if ('rename' == $fileExistsOption) {
327
                                // Perform actions when file exists and 'rename' option is selected
328
                                $newTitle = $this->generateUniqueTitle($title); // Generate a unique title
329
                                $resource->setResourceName($newTitle);
330
                                $resource->setUploadFile($uploadedFile);
331
                                if (!empty($resourceLinkList)) {
332
                                    $resource->setResourceLinkArray($resourceLinkList);
333
                                }
334
                                $em->persist($resource);
335
                                $em->flush();
336
337
                                // Return any data you need for further processing
338
                                return [
339
                                    'title' => $newTitle,
340
                                    'filetype' => 'file',
341
                                    'comment' => $comment,
342
                                ];
343
                            }
344
345
                            if ('nothing' == $fileExistsOption) {
346
                                // Perform actions when file exists and 'nothing' option is selected
347
                                // Display a message indicating that the file already exists
348
                                // or perform any other desired actions based on your application's requirements
349
                                $resource->setResourceName($title);
350
                                $flashBag = $request->getSession()->getFlashBag();
351
                                $message = $translator ? $translator->trans('upload.already_exists') : 'Upload Already Exists';
352
                                $flashBag->add('warning', $message);
353
354
                                throw new BadRequestHttpException($translator ? $translator->trans('file.already_exists') : 'The file already exists and was not uploaded.');
355
                            }
356
357
                            throw new InvalidArgumentException('Invalid fileExistsOption');
358
                        } else {
359
                            $resource->setResourceName($title);
360
                            $resource->setUploadFile($uploadedFile);
361
                            $fileParsed = true;
362
                        }
363
                    }
364
                }
365
366
                // Get data in content and create a HTML file.
367
                if (!$fileParsed && $content) {
368
                    $uploadedFile = CreateUploadedFile::fromString($title.'.html', 'text/html', $content);
369
                    $resource->setUploadFile($uploadedFile);
370
                    $fileParsed = true;
371
                }
372
373
                if (!$fileParsed) {
374
                    throw new InvalidArgumentException('filetype was set to "file" but no upload file found');
375
                }
376
377
                break;
378
379
            case 'folder':
380
                break;
381
        }
382
383
        // Set resource link list if exists.
384
        if (!empty($resourceLinkList)) {
385
            $resource->setResourceLinkArray($resourceLinkList);
386
        }
387
388
        return [
389
            'title' => $title,
390
            'filetype' => $fileType,
391
            'comment' => $comment,
392
        ];
393
    }
394
395
    protected function handleCreateFileRequestUncompress(AbstractResource $resource, Request $request, EntityManager $em, KernelInterface $kernel): array
396
    {
397
        // Get the parameters from the request
398
        $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
399
        $fileType = $request->get('filetype');
400
        $resourceLinkList = $request->get('resourceLinkList', []);
401
        if (!empty($resourceLinkList)) {
402
            $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
403
            if (empty($resourceLinkList)) {
404
                $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
405
406
                throw new InvalidArgumentException($message);
407
            }
408
        }
409
410
        if (empty($fileType)) {
411
            throw new Exception('filetype needed: folder or file');
412
        }
413
414
        if (0 === $parentResourceNodeId) {
415
            throw new Exception('parentResourceNodeId int value needed');
416
        }
417
418
        if ('file' == $fileType && $request->files->count() > 0) {
419
            if (!$request->files->has('uploadFile')) {
420
                throw new BadRequestHttpException('"uploadFile" is required');
421
            }
422
423
            $uploadedFile = $request->files->get('uploadFile');
424
            $resourceTitle = $uploadedFile->getClientOriginalName();
425
            $resource->setResourceName($resourceTitle);
426
            $resource->setUploadFile($uploadedFile);
427
428
            if ('zip' === $uploadedFile->getClientOriginalExtension()) {
429
                // Extract the files and subdirectories
430
                $extractedData = $this->extractZipFile($uploadedFile, $kernel);
431
                $folderStructure = $extractedData['folderStructure'];
432
                $extractPath = $extractedData['extractPath'];
433
                $documents = $this->saveZipContentsAsDocuments($folderStructure, $em, $resourceLinkList, $parentResourceNodeId, '', $extractPath, $processedItems);
434
            }
435
        }
436
437
        $resource->setParentResourceNode($parentResourceNodeId);
438
439
        return [
440
            'filetype' => $fileType,
441
            'comment' => 'Uncompressed',
442
        ];
443
    }
444
445
    protected function handleUpdateRequest(AbstractResource $resource, ResourceRepository $repo, Request $request, EntityManager $em): AbstractResource
446
    {
447
        $contentData = $request->getContent();
448
        $resourceLinkList = [];
449
        if (!empty($contentData)) {
450
            $contentData = json_decode($contentData, true);
451
            if (isset($contentData['parentResourceNodeId']) && 1 === \count($contentData)) {
452
                $parentResourceNodeId = (int) $contentData['parentResourceNodeId'];
453
            }
454
            $title = $contentData['title'] ?? '';
455
            $content = $contentData['contentFile'] ?? '';
456
            $resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? [];
457
        } else {
458
            $title = $request->get('title');
459
            $content = $request->request->get('contentFile');
460
        }
461
462
        $repo->setResourceName($resource, $title);
463
464
        $resourceNode = $resource->getResourceNode();
465
        $hasFile = $resourceNode->hasResourceFile();
466
467
        if ($hasFile && !empty($content)) {
468
            // The content is updated by the ResourceNodeListener.php
469
            $resourceNode->setContent($content);
470
            foreach ($resourceNode->getResourceFiles() as $resourceFile) {
471
                $resourceFile->setSize(\strlen($content));
472
            }
473
            $resource->setResourceNode($resourceNode);
474
        }
475
476
        $link = null;
477
        if (!empty($resourceLinkList)) {
478
            foreach ($resourceLinkList as $key => &$linkArray) {
479
                // Find the exact link.
480
                $linkId = $linkArray['id'] ?? 0;
481
                if (!empty($linkId)) {
482
                    /** @var ResourceLink $link */
483
                    $link = $resourceNode->getResourceLinks()->filter(fn ($link) => $link->getId() === $linkId)->first();
484
485
                    if (null !== $link) {
486
                        $link->setVisibility((int) $linkArray['visibility']);
487
                        unset($resourceLinkList[$key]);
488
489
                        $em->persist($link);
490
                    }
491
                }
492
            }
493
494
            $resource->setResourceLinkArray($resourceLinkList);
495
            self::setLinks($resource, $em);
496
        }
497
498
        $isRecursive = !$hasFile;
499
        // If it's a folder then change the visibility to the children (That have the same link).
500
        if ($isRecursive && null !== $link) {
501
            $repo->copyVisibilityToChildren($resource->getResourceNode(), $link);
502
        }
503
504
        if (!empty($parentResourceNodeId)) {
505
            $parentResourceNode = $em->getRepository(ResourceNode::class)->find($parentResourceNodeId);
506
            if ($parentResourceNode) {
507
                $resourceNode->setParent($parentResourceNode);
508
            }
509
        }
510
511
        $resourceNode->setUpdatedAt(new DateTime());
512
513
        return $resource;
514
    }
515
516
    private function saveZipContentsAsDocuments(array $folderStructure, EntityManager $em, $resourceLinkList = [], $parentResourceId = null, $currentPath = '', $extractPath = '', &$processedItems = []): array
517
    {
518
        $documents = [];
519
520
        foreach ($folderStructure as $key => $item) {
521
            if (\is_array($item)) {
522
                $folderName = $key;
523
                $subFolderStructure = $item;
524
525
                $document = new CDocument();
526
                $document->setTitle($folderName);
527
                $document->setFiletype('folder');
528
529
                if (null !== $parentResourceId) {
530
                    $document->setParentResourceNode($parentResourceId);
531
                }
532
533
                if (!empty($resourceLinkList)) {
534
                    $document->setResourceLinkArray($resourceLinkList);
535
                }
536
537
                $em->persist($document);
538
                $em->flush();
539
540
                $documentId = $document->getResourceNode()->getId();
541
                $documents[$documentId] = [
542
                    'name' => $document->getTitle(),
543
                    'files' => [],
544
                ];
545
546
                $subDocuments = $this->saveZipContentsAsDocuments($subFolderStructure, $em, $resourceLinkList, $documentId, $currentPath.$folderName.'/', $extractPath, $processedItems);
547
                $documents[$documentId]['files'] = $subDocuments;
548
            } else {
549
                $fileName = $item;
550
551
                $document = new CDocument();
552
                $document->setTitle($fileName);
553
                $document->setFiletype('file');
554
555
                if (null !== $parentResourceId) {
556
                    $document->setParentResourceNode($parentResourceId);
557
                }
558
559
                if (!empty($resourceLinkList)) {
560
                    $document->setResourceLinkArray($resourceLinkList);
561
                }
562
563
                $filePath = $extractPath.'/'.$currentPath.$fileName;
564
565
                if (file_exists($filePath)) {
566
                    $uploadedFile = new UploadedFile(
567
                        $filePath,
568
                        $fileName
569
                    );
570
571
                    $document->setUploadFile($uploadedFile);
572
                    $em->persist($document);
573
                    $em->flush();
574
575
                    $documentId = $document->getResourceNode()->getId();
576
                    $documents[$documentId] = [
577
                        'name' => $document->getTitle(),
578
                        'files' => [],
579
                    ];
580
                } else {
581
                    error_log('File does not exist: '.$filePath);
582
583
                    continue;
584
                }
585
            }
586
        }
587
588
        return $documents;
589
    }
590
591
    private function extractZipFile(UploadedFile $file, KernelInterface $kernel): array
592
    {
593
        // Get the temporary path of the ZIP file
594
        $zipFilePath = $file->getRealPath();
595
596
        // Create an instance of the ZipArchive class
597
        $zip = new ZipArchive();
598
        $zip->open($zipFilePath);
599
600
        $cacheDirectory = $kernel->getCacheDir();
601
        $extractPath = $cacheDirectory.'/'.uniqid('extracted_', true);
602
        mkdir($extractPath);
603
604
        // Extract the contents of the ZIP file
605
        $zip->extractTo($extractPath);
606
607
        // Array to store the sorted extracted paths
608
        $extractedPaths = [];
609
610
        // Iterate over each file or directory in the ZIP file
611
        for ($i = 0; $i < $zip->numFiles; $i++) {
612
            $filename = $zip->getNameIndex($i);
613
            $extractedPaths[] = $extractPath.'/'.$filename;
614
        }
615
616
        // Close the ZIP file
617
        $zip->close();
618
619
        // Build the folder structure and file associations
620
        $folderStructure = $this->buildFolderStructure($extractedPaths, $extractPath);
621
622
        // Return the array of folder structure and the extraction path
623
        return [
624
            'folderStructure' => $folderStructure,
625
            'extractPath' => $extractPath,
626
        ];
627
    }
628
629
    private function buildFolderStructure(array $paths, string $extractPath): array
630
    {
631
        $folderStructure = [];
632
633
        foreach ($paths as $path) {
634
            $relativePath = str_replace($extractPath.'/', '', $path);
635
            $parts = explode('/', $relativePath);
636
637
            $currentLevel = &$folderStructure;
638
639
            foreach ($parts as $part) {
640
                if (!isset($currentLevel[$part])) {
641
                    $currentLevel[$part] = [];
642
                }
643
644
                $currentLevel = &$currentLevel[$part];
645
            }
646
        }
647
648
        return $this->formatFolderStructure($folderStructure);
649
    }
650
651
    private function formatFolderStructure(array $folderStructure): array
652
    {
653
        $result = [];
654
655
        foreach ($folderStructure as $folder => $contents) {
656
            $formattedContents = $this->formatFolderStructure($contents);
657
658
            if (!empty($formattedContents)) {
659
                $result[$folder] = $formattedContents;
660
            } elseif (!empty($folder)) {
661
                $result[] = $folder;
662
            }
663
        }
664
665
        return $result;
666
    }
667
668
    /**
669
     * Generates a unique filename by appending a random suffix.
670
     */
671
    private function generateUniqueTitle(string $title): string
672
    {
673
        $info = pathinfo($title);
674
        $filename = $info['filename'];
675
        $extension = isset($info['extension']) ? '.'.$info['extension'] : '';
676
677
        return $filename.'_'.uniqid().$extension;
678
    }
679
}
680