Completed
Push — master ( b582f4...6580e5 )
by
unknown
02:29 queued 01:29
created

BaseResourceFileAction::setLinks()   D

Complexity

Conditions 19
Paths 37

Size

Total Lines 96
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 50
nop 2
dl 0
loc 96
rs 4.5166
c 0
b 0
f 0
nc 37

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\ResourceLink;
13
use Chamilo\CoreBundle\Entity\ResourceNode;
14
use Chamilo\CoreBundle\Entity\ResourceRight;
15
use Chamilo\CoreBundle\Entity\Session;
16
use Chamilo\CoreBundle\Entity\User;
17
use Chamilo\CoreBundle\Repository\ResourceRepository;
18
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
19
use Chamilo\CourseBundle\Entity\CDocument;
20
use Chamilo\CourseBundle\Entity\CGroup;
21
use DateTime;
22
use Doctrine\ORM\EntityManager;
23
use Doctrine\ORM\EntityManagerInterface;
24
use Exception;
25
use InvalidArgumentException;
26
use Symfony\Component\HttpFoundation\File\UploadedFile;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
29
use Symfony\Component\HttpKernel\KernelInterface;
30
use ZipArchive;
31
32
class BaseResourceFileAction
33
{
34
    public static function setLinks(AbstractResource $resource, EntityManagerInterface $em): void
35
    {
36
        $resourceNode = $resource->getResourceNode();
37
        $links = $resource->getResourceLinkArray();
38
        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...
39
            $groupRepo = $em->getRepository(CGroup::class);
40
            $courseRepo = $em->getRepository(Course::class);
41
            $sessionRepo = $em->getRepository(Session::class);
42
            $userRepo = $em->getRepository(User::class);
43
44
            foreach ($links as $link) {
45
                $resourceLink = new ResourceLink();
46
                $linkSet = false;
47
                if (isset($link['cid']) && !empty($link['cid'])) {
48
                    $course = $courseRepo->find($link['cid']);
49
                    if (null !== $course) {
50
                        $linkSet = true;
51
                        $resourceLink->setCourse($course);
52
                    } else {
53
                        throw new InvalidArgumentException(\sprintf('Course #%s does not exists', $link['cid']));
54
                    }
55
                }
56
57
                if (isset($link['sid']) && !empty($link['sid'])) {
58
                    $session = $sessionRepo->find($link['sid']);
59
                    if (null !== $session) {
60
                        $linkSet = true;
61
                        $resourceLink->setSession($session);
62
                    } else {
63
                        throw new InvalidArgumentException(\sprintf('Session #%s does not exists', $link['sid']));
64
                    }
65
                }
66
67
                if (isset($link['gid']) && !empty($link['gid'])) {
68
                    $group = $groupRepo->find($link['gid']);
69
                    if (null !== $group) {
70
                        $linkSet = true;
71
                        $resourceLink->setGroup($group);
72
                    } else {
73
                        throw new InvalidArgumentException(\sprintf('Group #%s does not exists', $link['gid']));
74
                    }
75
                }
76
77
                if (isset($link['uid']) && !empty($link['uid'])) {
78
                    $user = $userRepo->find($link['uid']);
79
                    if (null !== $user) {
80
                        $linkSet = true;
81
                        $resourceLink->setUser($user);
82
                    } else {
83
                        throw new InvalidArgumentException(\sprintf('User #%s does not exists', $link['uid']));
84
                    }
85
                }
86
87
                if (isset($link['visibility'])) {
88
                    $resourceLink->setVisibility((int) $link['visibility']);
89
                } else {
90
                    throw new InvalidArgumentException('Link needs a visibility key');
91
                }
92
93
                if ($linkSet) {
94
                    $em->persist($resourceLink);
95
                    $resourceNode->addResourceLink($resourceLink);
96
                    // $em->persist($resourceNode);
97
                    // $em->persist($resource->getResourceNode());
98
                }
99
            }
100
        }
101
102
        // Use by Chamilo not api platform.
103
        $links = $resource->getResourceLinkEntityList();
104
        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...
105
            // error_log('$resource->getResourceLinkEntityList()');
106
            foreach ($links as $link) {
107
                /*$rights = [];
108
                 * switch ($link->getVisibility()) {
109
                 * case ResourceLink::VISIBILITY_PENDING:
110
                 * case ResourceLink::VISIBILITY_DRAFT:
111
                 * $editorMask = ResourceNodeVoter::getEditorMask();
112
                 * $resourceRight = new ResourceRight();
113
                 * $resourceRight
114
                 * ->setMask($editorMask)
115
                 * ->setRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER)
116
                 * ;
117
                 * $rights[] = $resourceRight;
118
                 * break;
119
                 * }
120
                 * if (!empty($rights)) {
121
                 * foreach ($rights as $right) {
122
                 * $link->addResourceRight($right);
123
                 * }
124
                 * }*/
125
                // error_log('link adding to node: '.$resource->getResourceNode()->getId());
126
                // error_log('link with user : '.$link->getUser()->getUsername());
127
                $resource->getResourceNode()->addResourceLink($link);
128
129
                $em->persist($link);
130
            }
131
        }
132
    }
133
134
    /**
135
     * @todo use this function inside handleCreateFileRequest
136
     */
137
    protected function handleCreateRequest(AbstractResource $resource, ResourceRepository $resourceRepository, Request $request): array
138
    {
139
        $contentData = $request->getContent();
140
141
        if (!empty($contentData)) {
142
            $contentData = json_decode($contentData, true);
143
            $title = $contentData['title'] ?? '';
144
            $parentResourceNodeId = (int) ($contentData['parentResourceNodeId'] ?? 0);
145
            $resourceLinkList = $contentData['resourceLinkList'] ?? [];
146
            if (empty($resourceLinkList)) {
147
                $resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? [];
148
            }
149
        } else {
150
            $contentData = $request->request->all();
151
            $title = $request->get('title');
152
            $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
153
            $resourceLinkList = $request->get('resourceLinkList', []);
154
            if (!empty($resourceLinkList)) {
155
                $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
156
                if (empty($resourceLinkList)) {
157
                    $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
158
159
                    throw new InvalidArgumentException($message);
160
                }
161
            }
162
        }
163
164
        if (0 === $parentResourceNodeId) {
165
            throw new Exception('Parameter parentResourceNodeId int value is needed');
166
        }
167
168
        $resource->setParentResourceNode($parentResourceNodeId);
169
170
        if (empty($title)) {
171
            throw new InvalidArgumentException('title is required');
172
        }
173
174
        $resource->setResourceName($title);
175
176
        // Set resource link list if exists.
177
        if (!empty($resourceLinkList)) {
178
            $resource->setResourceLinkArray($resourceLinkList);
179
        }
180
181
        return $contentData;
182
    }
183
184
    /**
185
     * Function loaded when creating a resource using the api, then the ResourceListener is executed.
186
     */
187
    public function handleCreateFileRequest(
188
        AbstractResource $resource,
189
        ResourceRepository $resourceRepository,
190
        Request $request,
191
        EntityManager $em,
192
        string $fileExistsOption = ''
193
    ): array {
194
        $contentData = $request->getContent();
195
196
        if (!empty($contentData)) {
197
            $contentData = json_decode($contentData, true);
198
            $title = $contentData['title'] ?? '';
199
            $comment = $contentData['comment'] ?? '';
200
            $parentResourceNodeId = (int) ($contentData['parentResourceNodeId'] ?? 0);
201
            $fileType = $contentData['filetype'] ?? '';
202
            $resourceLinkList = $contentData['resourceLinkList'] ?? [];
203
        } else {
204
            $title = $request->get('title');
205
            $comment = $request->get('comment');
206
            $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
207
            $fileType = $request->get('filetype');
208
            $resourceLinkList = $request->get('resourceLinkList', []);
209
            if (!empty($resourceLinkList)) {
210
                $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
211
                if (empty($resourceLinkList)) {
212
                    $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
213
214
                    throw new InvalidArgumentException($message);
215
                }
216
            }
217
        }
218
219
        if (empty($fileType)) {
220
            throw new Exception('filetype needed: folder or file');
221
        }
222
223
        if (0 === $parentResourceNodeId) {
224
            throw new Exception('parentResourceNodeId int value needed');
225
        }
226
227
        $resource->setParentResourceNode($parentResourceNodeId);
228
229
        switch ($fileType) {
230
            case 'certificate':
231
            case 'file':
232
                $content = '';
233
                if ($request->request->has('contentFile')) {
234
                    $content = $request->request->get('contentFile');
235
                }
236
                $fileParsed = false;
237
                // File upload.
238
                if ($request->files->count() > 0) {
239
                    if (!$request->files->has('uploadFile')) {
240
                        throw new BadRequestHttpException('"uploadFile" is required');
241
                    }
242
243
                    /** @var UploadedFile $uploadedFile */
244
                    $uploadedFile = $request->files->get('uploadFile');
245
                    $title = $uploadedFile->getClientOriginalName();
246
247
                    if (empty($title)) {
248
                        throw new InvalidArgumentException('title is required');
249
                    }
250
251
                    // Handle the appropriate action based on the fileExistsOption
252
                    if (!empty($fileExistsOption)) {
253
                        // Check if a document with the same title and parent resource node already exists
254
                        $existingDocument = $resourceRepository->findByTitleAndParentResourceNode($title, $parentResourceNodeId);
255
                        if ($existingDocument) {
256
                            if ('overwrite' == $fileExistsOption) {
257
                                // Perform actions when file exists and 'overwrite' option is selected
258
                                $resource->setResourceName($title);
259
                                $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 or Chamilo\CourseBundle\Ent...udentPublicationComment. 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

259
                                $existingDocument->/** @scrutinizer ignore-call */ 
260
                                                   setTitle($title);
Loading history...
260
                                $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

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