Passed
Pull Request — master (#7139)
by
unknown
11:28
created

BaseResourceFileAction   F

Complexity

Total Complexity 120

Size/Duplication

Total Lines 760
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 423
c 0
b 0
f 0
dl 0
loc 760
rs 2
wmc 120

11 Methods

Rating   Name   Duplication   Size   Complexity  
A extractZipFile() 0 35 2
F handleCreateFileRequest() 0 175 29
A buildFolderStructure() 0 20 4
D setLinks() 0 109 22
A handleCreateCommentRequest() 0 33 5
B handleCreateFileRequestUncompress() 0 47 10
B saveZipContentsAsDocuments() 0 81 9
A formatFolderStructure() 0 15 4
F handleUpdateRequest() 0 153 24
A generateUniqueTitle() 0 7 2
B handleCreateRequest() 0 45 9

How to fix   Complexity   

Complex Class

Complex classes like BaseResourceFileAction 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 BaseResourceFileAction, and based on these observations, apply Extract Interface, too.

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\Entity\AbstractResource;
10
use Chamilo\CoreBundle\Entity\Course;
11
use Chamilo\CoreBundle\Entity\ResourceFile;
12
use Chamilo\CoreBundle\Entity\ResourceLink;
13
use Chamilo\CoreBundle\Entity\ResourceNode;
14
use Chamilo\CoreBundle\Entity\Session;
15
use Chamilo\CoreBundle\Entity\User;
16
use Chamilo\CoreBundle\Entity\Usergroup;
17
use Chamilo\CoreBundle\Helpers\CreateUploadedFileHelper;
18
use Chamilo\CoreBundle\Repository\ResourceLinkRepository;
19
use Chamilo\CoreBundle\Repository\ResourceRepository;
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
        if (null === $resourceNode) {
40
            // Nothing to do if there is no resource node.
41
            return;
42
        }
43
44
        /** @var ResourceNode|null $parentNode */
45
        $parentNode = $resourceNode->getParent();
46
47
        /** @var ResourceLinkRepository $resourceLinkRepo */
48
        $resourceLinkRepo = $em->getRepository(ResourceLink::class);
49
50
        $links = $resource->getResourceLinkArray();
51
        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...
52
            $groupRepo = $em->getRepository(CGroup::class);
53
            $courseRepo = $em->getRepository(Course::class);
54
            $sessionRepo = $em->getRepository(Session::class);
55
            $userRepo = $em->getRepository(User::class);
56
57
            foreach ($links as $link) {
58
                $resourceLink = new ResourceLink();
59
                $linkSet = false;
60
61
                $course = null;
62
                $session = null;
63
                $group = null;
64
                $user = null;
65
66
                if (isset($link['cid']) && !empty($link['cid'])) {
67
                    $course = $courseRepo->find($link['cid']);
68
                    if (null !== $course) {
69
                        $linkSet = true;
70
                        $resourceLink->setCourse($course);
71
                    } else {
72
                        throw new InvalidArgumentException(\sprintf('Course #%s does not exist', $link['cid']));
73
                    }
74
                }
75
76
                if (isset($link['sid']) && !empty($link['sid'])) {
77
                    $session = $sessionRepo->find($link['sid']);
78
                    if (null !== $session) {
79
                        $linkSet = true;
80
                        $resourceLink->setSession($session);
81
                    } else {
82
                        throw new InvalidArgumentException(\sprintf('Session #%s does not exist', $link['sid']));
83
                    }
84
                }
85
86
                if (isset($link['gid']) && !empty($link['gid'])) {
87
                    $group = $groupRepo->find($link['gid']);
88
                    if (null !== $group) {
89
                        $linkSet = true;
90
                        $resourceLink->setGroup($group);
91
                    } else {
92
                        throw new InvalidArgumentException(\sprintf('Group #%s does not exist', $link['gid']));
93
                    }
94
                }
95
96
                if (isset($link['uid']) && !empty($link['uid'])) {
97
                    $user = $userRepo->find($link['uid']);
98
                    if (null !== $user) {
99
                        $linkSet = true;
100
                        $resourceLink->setUser($user);
101
                    } else {
102
                        throw new InvalidArgumentException(\sprintf('User #%s does not exist', $link['uid']));
103
                    }
104
                }
105
106
                if (isset($link['visibility'])) {
107
                    $resourceLink->setVisibility((int) $link['visibility']);
108
                } else {
109
                    throw new InvalidArgumentException('Link needs a visibility key');
110
                }
111
112
                if ($linkSet) {
113
                    // Attach the node to the link.
114
                    $resourceLink->setResourceNode($resourceNode);
115
116
                    // If the resource has a parent node, try to resolve the parent link
117
                    // in the same context so we can maintain a context-aware hierarchy.
118
                    if ($parentNode instanceof ResourceNode) {
119
                        $parentLink = $resourceLinkRepo->findParentLinkForContext(
120
                            $parentNode,
121
                            $course,
122
                            $session,
123
                            $group,
124
                            null,
125
                            $user
126
                        );
127
128
                        if (null !== $parentLink) {
129
                            $resourceLink->setParent($parentLink);
130
                        }
131
                    }
132
133
                    $em->persist($resourceLink);
134
                    $resourceNode->addResourceLink($resourceLink);
135
                }
136
            }
137
        }
138
139
        // Use by Chamilo not api platform.
140
        $links = $resource->getResourceLinkEntityList();
141
        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...
142
            foreach ($links as $link) {
143
                $resource->getResourceNode()->addResourceLink($link);
144
                $em->persist($link);
145
            }
146
        }
147
    }
148
149
    /**
150
     * @todo use this function inside handleCreateFileRequest
151
     */
152
    protected function handleCreateRequest(AbstractResource $resource, ResourceRepository $resourceRepository, Request $request): array
153
    {
154
        $contentData = $request->getContent();
155
156
        if (!empty($contentData)) {
157
            $contentData = json_decode($contentData, true);
158
            $title = $contentData['title'] ?? '';
159
            $parentResourceNodeId = (int) ($contentData['parentResourceNodeId'] ?? 0);
160
            $resourceLinkList = $contentData['resourceLinkList'] ?? [];
161
            if (empty($resourceLinkList)) {
162
                $resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? [];
163
            }
164
        } else {
165
            $contentData = $request->request->all();
166
            $title = $request->get('title');
167
            $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
168
            $resourceLinkList = $request->get('resourceLinkList', []);
169
            if (!empty($resourceLinkList)) {
170
                $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
171
                if (empty($resourceLinkList)) {
172
                    $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
173
174
                    throw new InvalidArgumentException($message);
175
                }
176
            }
177
        }
178
179
        if (0 === $parentResourceNodeId) {
180
            throw new Exception('Parameter parentResourceNodeId int value is needed');
181
        }
182
183
        $resource->setParentResourceNode($parentResourceNodeId);
184
185
        if (empty($title)) {
186
            throw new InvalidArgumentException('title is required');
187
        }
188
189
        $resource->setResourceName($title);
190
191
        // Set resource link list if exists.
192
        if (!empty($resourceLinkList)) {
193
            $resource->setResourceLinkArray($resourceLinkList);
194
        }
195
196
        return $contentData;
197
    }
198
199
    /**
200
     * Handles the creation logic for a student publication comment resource.
201
     */
202
    public function handleCreateCommentRequest(
203
        AbstractResource $resource,
204
        ResourceRepository $resourceRepository,
205
        Request $request,
206
        EntityManager $em,
207
        string $fileExistsOption = '',
208
        ?TranslatorInterface $translator = null
209
    ): array {
210
        $title = $request->get('comment', '');
211
        $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
212
        $fileType = $request->get('filetype');
213
        $uploadedFile = null;
214
215
        if (empty($fileType)) {
216
            throw new Exception('filetype needed: folder or file');
217
        }
218
219
        if (0 === $parentResourceNodeId) {
220
            throw new Exception('parentResourceNodeId int value needed');
221
        }
222
223
        $resource->setParentResourceNode($parentResourceNodeId);
224
225
        if ($request->files->count() > 0 && $request->files->has('uploadFile')) {
226
            /** @var UploadedFile $uploadedFile */
227
            $uploadedFile = $request->files->get('uploadFile');
228
            $resource->setUploadFile($uploadedFile);
229
        }
230
231
        return [
232
            'title' => $title,
233
            'filename' => $uploadedFile?->getClientOriginalName(),
234
            'filetype' => $fileType,
235
        ];
236
    }
237
238
    /**
239
     * Function loaded when creating a resource using the api, then the ResourceListener is executed.
240
     */
241
    public function handleCreateFileRequest(
242
        AbstractResource $resource,
243
        ResourceRepository $resourceRepository,
244
        Request $request,
245
        EntityManager $em,
246
        string $fileExistsOption = '',
247
        ?TranslatorInterface $translator = null
248
    ): array {
249
        $contentData = $request->getContent();
250
251
        if (!empty($contentData)) {
252
            $contentData = json_decode($contentData, true);
253
            $title = $contentData['title'] ?? '';
254
            $comment = $contentData['comment'] ?? '';
255
            $parentResourceNodeId = (int) ($contentData['parentResourceNodeId'] ?? 0);
256
            $fileType = $contentData['filetype'] ?? '';
257
            $resourceLinkList = $contentData['resourceLinkList'] ?? [];
258
        } else {
259
            $title = $request->get('title');
260
            $comment = $request->get('comment');
261
            $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
262
            $fileType = $request->get('filetype');
263
            $resourceLinkList = $request->get('resourceLinkList', []);
264
            if (!empty($resourceLinkList)) {
265
                $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
266
                if (empty($resourceLinkList)) {
267
                    $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
268
269
                    throw new InvalidArgumentException($message);
270
                }
271
            }
272
        }
273
274
        if (empty($fileType)) {
275
            throw new Exception('filetype needed: folder or file');
276
        }
277
278
        if (0 === $parentResourceNodeId) {
279
            throw new Exception('parentResourceNodeId int value needed');
280
        }
281
282
        $resource->setParentResourceNode($parentResourceNodeId);
283
284
        switch ($fileType) {
285
            case 'certificate':
286
            case 'file':
287
                $content = '';
288
                if ($request->request->has('contentFile')) {
289
                    $content = $request->request->get('contentFile');
290
                }
291
                $fileParsed = false;
292
                // File upload.
293
                if ($request->files->count() > 0) {
294
                    if (!$request->files->has('uploadFile')) {
295
                        throw new BadRequestHttpException('"uploadFile" is required');
296
                    }
297
298
                    /** @var UploadedFile $uploadedFile */
299
                    $uploadedFile = $request->files->get('uploadFile');
300
                    $title = $uploadedFile->getClientOriginalName();
301
302
                    if (empty($title)) {
303
                        throw new InvalidArgumentException('title is required');
304
                    }
305
306
                    // Handle the appropriate action based on the fileExistsOption
307
                    if (!empty($fileExistsOption)) {
308
                        // Check if a document with the same title and parent resource node already exists
309
                        $existingDocument = $resourceRepository->findByTitleAndParentResourceNode($title, $parentResourceNodeId);
310
                        if ($existingDocument) {
311
                            if ('overwrite' === $fileExistsOption) {
312
                                $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\PortfolioComment or Chamilo\CoreBundle\Entity\MessageAttachment or Chamilo\CoreBundle\Entity\GradebookCertificate 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

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

313
                                $existingDocument->/** @scrutinizer ignore-call */ 
314
                                                   setComment($comment);
Loading history...
314
315
                                $resourceNode = $existingDocument->getResourceNode();
316
317
                                $resourceFile = $resourceNode->getFirstResourceFile();
318
                                if ($resourceFile instanceof ResourceFile) {
319
                                    $resourceFile->setFile($uploadedFile);
320
                                    $em->persist($resourceFile);
321
                                } else {
322
                                    $existingDocument->setUploadFile($uploadedFile);
323
                                }
324
325
                                $resourceNode->setUpdatedAt(new DateTime());
326
                                $existingDocument->setResourceNode($resourceNode);
327
328
                                $em->persist($existingDocument);
329
                                $em->flush();
330
331
                                // Return any data you need for further processing
332
                                return [
333
                                    'title' => $title,
334
                                    'filetype' => 'file',
335
                                    'comment' => $comment,
336
                                ];
337
                            }
338
339
                            if ('rename' == $fileExistsOption) {
340
                                // Perform actions when file exists and 'rename' option is selected
341
                                $newTitle = $this->generateUniqueTitle($title); // Generate a unique title
342
                                $resource->setResourceName($newTitle);
343
                                $resource->setUploadFile($uploadedFile);
344
                                if (!empty($resourceLinkList)) {
345
                                    $resource->setResourceLinkArray($resourceLinkList);
346
                                }
347
                                $em->persist($resource);
348
                                $em->flush();
349
350
                                // Return any data you need for further processing
351
                                return [
352
                                    'title' => $newTitle,
353
                                    'filetype' => 'file',
354
                                    'comment' => $comment,
355
                                ];
356
                            }
357
358
                            if ('nothing' == $fileExistsOption) {
359
                                // Perform actions when file exists and 'nothing' option is selected
360
                                // Display a message indicating that the file already exists
361
                                // or perform any other desired actions based on your application's requirements
362
                                $resource->setResourceName($title);
363
                                $flashBag = $request->getSession()->getFlashBag();
364
                                $message = $translator ? $translator->trans('The operation is impossible, a file with this name already exists.') : 'Upload already exists';
365
                                $flashBag->add('warning', $message);
366
367
                                throw new BadRequestHttpException($message);
368
                            }
369
370
                            throw new InvalidArgumentException('Invalid fileExistsOption');
371
                        } else {
372
                            $resource->setResourceName($title);
373
                            $resource->setUploadFile($uploadedFile);
374
                            $fileParsed = true;
375
                        }
376
                    }
377
                }
378
379
                // Get data in content and create a HTML file.
380
                if (!$fileParsed && $content) {
381
                    $uploadedFile = CreateUploadedFileHelper::fromString($title.'.html', 'text/html', $content);
382
                    $resource->setUploadFile($uploadedFile);
383
                    $fileParsed = true;
384
                }
385
386
                if (!$fileParsed) {
387
                    throw new InvalidArgumentException('filetype was set to "file" but no upload file found');
388
                }
389
390
                break;
391
392
            case 'folder':
393
                break;
394
        }
395
396
        // Set resource link list if exists.
397
        if (!empty($resourceLinkList)) {
398
            $resource->setResourceLinkArray($resourceLinkList);
399
        }
400
401
        // Detect if file is a video
402
        $filetypeResult = $fileType;
403
404
        if (isset($uploadedFile) && $uploadedFile instanceof UploadedFile) {
405
            $mimeType = $uploadedFile->getMimeType();
406
            if (str_starts_with($mimeType, 'video/')) {
407
                $filetypeResult = 'video';
408
                $comment = trim($comment.' [video]');
409
            }
410
        }
411
412
        return [
413
            'title' => $title,
414
            'filetype' => $filetypeResult,
415
            'comment' => $comment,
416
        ];
417
    }
418
419
    protected function handleCreateFileRequestUncompress(AbstractResource $resource, Request $request, EntityManager $em, KernelInterface $kernel): array
420
    {
421
        // Get the parameters from the request
422
        $parentResourceNodeId = (int) $request->get('parentResourceNodeId');
423
        $fileType = $request->get('filetype');
424
        $resourceLinkList = $request->get('resourceLinkList', []);
425
        if (!empty($resourceLinkList)) {
426
            $resourceLinkList = !str_contains($resourceLinkList, '[') ? json_decode('['.$resourceLinkList.']', true) : json_decode($resourceLinkList, true);
427
            if (empty($resourceLinkList)) {
428
                $message = 'resourceLinkList is not a valid json. Use for example: [{"cid":1, "visibility":1}]';
429
430
                throw new InvalidArgumentException($message);
431
            }
432
        }
433
434
        if (empty($fileType)) {
435
            throw new Exception('filetype needed: folder or file');
436
        }
437
438
        if (0 === $parentResourceNodeId) {
439
            throw new Exception('parentResourceNodeId int value needed');
440
        }
441
442
        if ('file' == $fileType && $request->files->count() > 0) {
443
            if (!$request->files->has('uploadFile')) {
444
                throw new BadRequestHttpException('"uploadFile" is required');
445
            }
446
447
            $uploadedFile = $request->files->get('uploadFile');
448
            $resourceTitle = $uploadedFile->getClientOriginalName();
449
            $resource->setResourceName($resourceTitle);
450
            $resource->setUploadFile($uploadedFile);
451
452
            if ('zip' === $uploadedFile->getClientOriginalExtension()) {
453
                // Extract the files and subdirectories
454
                $extractedData = $this->extractZipFile($uploadedFile, $kernel);
455
                $folderStructure = $extractedData['folderStructure'];
456
                $extractPath = $extractedData['extractPath'];
457
                $documents = $this->saveZipContentsAsDocuments($folderStructure, $em, $resourceLinkList, $parentResourceNodeId, '', $extractPath, $processedItems);
458
            }
459
        }
460
461
        $resource->setParentResourceNode($parentResourceNodeId);
462
463
        return [
464
            'filetype' => $fileType,
465
            'comment' => 'Uncompressed',
466
        ];
467
    }
468
469
    protected function handleUpdateRequest(AbstractResource $resource, ResourceRepository $repo, Request $request, EntityManager $em): AbstractResource
470
    {
471
        $contentData = $request->getContent();
472
        $resourceLinkList = [];
473
        $parentResourceNodeId = 0;
474
        $title = null;
475
        $content = null;
476
477
        if (!empty($contentData)) {
478
            $contentData = json_decode($contentData, true);
479
480
            if (isset($contentData['parentResourceNodeId'])) {
481
                $parentResourceNodeId = (int) $contentData['parentResourceNodeId'];
482
            }
483
484
            $title = $contentData['title'] ?? null;
485
            $content = $contentData['contentFile'] ?? null;
486
            $resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? [];
487
        } else {
488
            $title = $request->get('title');
489
            $content = $request->request->get('contentFile');
490
        }
491
492
        // Only update the name when a title is explicitly provided.
493
        if (null !== $title) {
494
            $repo->setResourceName($resource, $title);
495
        }
496
497
        $resourceNode = $resource->getResourceNode();
498
        if (null === $resourceNode) {
499
            return $resource;
500
        }
501
502
        $hasFile = $resourceNode->hasResourceFile();
503
504
        if ($hasFile && !empty($content)) {
505
            // The content is updated by the ResourceNodeListener.php
506
            $resourceNode->setContent($content);
507
            foreach ($resourceNode->getResourceFiles() as $resourceFile) {
508
                $resourceFile->setSize(\strlen($content));
509
            }
510
            $resource->setResourceNode($resourceNode);
511
        }
512
513
        $link = null;
514
        if (!empty($resourceLinkList)) {
515
            foreach ($resourceLinkList as $key => &$linkArray) {
516
                // Find the exact link.
517
                $linkId = $linkArray['id'] ?? 0;
518
                if (!empty($linkId)) {
519
                    /** @var ResourceLink $link */
520
                    $link = $resourceNode->getResourceLinks()->filter(
521
                        static fn ($link) => $link->getId() === $linkId
522
                    )->first();
523
524
                    if (null !== $link) {
525
                        $link->setVisibility((int) $linkArray['visibility']);
526
                        unset($resourceLinkList[$key]);
527
528
                        $em->persist($link);
529
                    }
530
                }
531
            }
532
533
            $resource->setResourceLinkArray($resourceLinkList);
534
            self::setLinks($resource, $em);
535
        }
536
537
        $isRecursive = !$hasFile;
538
        // If it's a folder then change the visibility to the children (that have the same link).
539
        if ($isRecursive && null !== $link) {
540
            $repo->copyVisibilityToChildren($resource->getResourceNode(), $link);
541
        }
542
543
        // If a new parent node was provided, update the ResourceNode parent
544
        // and the ResourceLink parent in the current context.
545
        if ($parentResourceNodeId > 0) {
546
            $parentResourceNode = $em->getRepository(ResourceNode::class)->find($parentResourceNodeId);
547
548
            if ($parentResourceNode) {
549
                $resourceNode->setParent($parentResourceNode);
550
            }
551
552
            // Only documents use the hierarchical link structure in this way.
553
            if ($resource instanceof CDocument) {
554
                /** @var ResourceLinkRepository $linkRepo */
555
                $linkRepo = $em->getRepository(ResourceLink::class);
556
557
                // Resolve context from query parameters (course/session/group/user).
558
                $course = null;
559
                $session = null;
560
                $group = null;
561
                $usergroup = null;
562
                $user = null;
563
564
                $courseId = $request->query->getInt('cid', 0);
565
                $sessionId = $request->query->getInt('sid', 0);
566
                $groupId = $request->query->getInt('gid', 0);
567
                $userId = $request->query->getInt('uid', 0);
568
                $usergroupId = $request->query->getInt('ugid', 0);
569
570
                if ($courseId > 0) {
571
                    $course = $em->getRepository(Course::class)->find($courseId);
572
                }
573
574
                if ($sessionId > 0) {
575
                    $session = $em->getRepository(Session::class)->find($sessionId);
576
                }
577
578
                if ($groupId > 0) {
579
                    $group = $em->getRepository(CGroup::class)->find($groupId);
580
                }
581
582
                if ($userId > 0) {
583
                    $user = $em->getRepository(User::class)->find($userId);
584
                }
585
586
                if ($usergroupId > 0) {
587
                    $usergroup = $em->getRepository(Usergroup::class)->find($usergroupId);
588
                }
589
590
                $parentLink = null;
591
                if ($parentResourceNode) {
592
                    $parentLink = $linkRepo->findParentLinkForContext(
593
                        $parentResourceNode,
594
                        $course,
595
                        $session,
596
                        $group,
597
                        $usergroup,
598
                        $user
599
                    );
600
                }
601
602
                $currentLink = $linkRepo->findLinkForResourceInContext(
603
                    $resource,
604
                    $course,
605
                    $session,
606
                    $group,
607
                    $usergroup,
608
                    $user
609
                );
610
611
                if (null !== $currentLink) {
612
                    // When parentLink is null, the document becomes a root-level item in this context.
613
                    $currentLink->setParent($parentLink);
614
                    $em->persist($currentLink);
615
                }
616
            }
617
        }
618
619
        $resourceNode->setUpdatedAt(new DateTime());
620
621
        return $resource;
622
    }
623
624
    private function saveZipContentsAsDocuments(array $folderStructure, EntityManager $em, $resourceLinkList = [], $parentResourceId = null, $currentPath = '', $extractPath = '', &$processedItems = []): array
625
    {
626
        $documents = [];
627
628
        foreach ($folderStructure as $key => $item) {
629
            if (\is_array($item)) {
630
                $folderName = $key;
631
                $subFolderStructure = $item;
632
633
                $document = new CDocument();
634
                $document->setTitle($folderName);
635
                $document->setFiletype('folder');
636
637
                if (null !== $parentResourceId) {
638
                    $document->setParentResourceNode($parentResourceId);
639
                }
640
641
                if (!empty($resourceLinkList)) {
642
                    $document->setResourceLinkArray($resourceLinkList);
643
                }
644
645
                $em->persist($document);
646
                $em->flush();
647
648
                $documentId = $document->getResourceNode()->getId();
649
                $documents[$documentId] = [
650
                    'name' => $document->getTitle(),
651
                    'files' => [],
652
                ];
653
654
                $subDocuments = $this->saveZipContentsAsDocuments($subFolderStructure, $em, $resourceLinkList, $documentId, $currentPath.$folderName.'/', $extractPath, $processedItems);
655
                $documents[$documentId]['files'] = $subDocuments;
656
            } else {
657
                $fileName = $item;
658
659
                $document = new CDocument();
660
                $document->setTitle($fileName);
661
                $document->setFiletype('file');
662
663
                if (null !== $parentResourceId) {
664
                    $document->setParentResourceNode($parentResourceId);
665
                }
666
667
                if (!empty($resourceLinkList)) {
668
                    $document->setResourceLinkArray($resourceLinkList);
669
                }
670
671
                $filePath = $extractPath.'/'.$currentPath.$fileName;
672
673
                if (file_exists($filePath)) {
674
                    $uploadedFile = new UploadedFile(
675
                        $filePath,
676
                        $fileName
677
                    );
678
679
                    $mimeType = $uploadedFile->getMimeType();
680
                    if (str_starts_with($mimeType, 'video/')) {
681
                        $document->setFiletype('video');
682
                        $document->setComment('[video]');
683
                    } else {
684
                        $document->setFiletype('file');
685
                    }
686
687
                    $document->setUploadFile($uploadedFile);
688
                    $em->persist($document);
689
                    $em->flush();
690
691
                    $documentId = $document->getResourceNode()->getId();
692
                    $documents[$documentId] = [
693
                        'name' => $document->getTitle(),
694
                        'files' => [],
695
                    ];
696
                } else {
697
                    error_log('File does not exist: '.$filePath);
698
699
                    continue;
700
                }
701
            }
702
        }
703
704
        return $documents;
705
    }
706
707
    private function extractZipFile(UploadedFile $file, KernelInterface $kernel): array
708
    {
709
        // Get the temporary path of the ZIP file
710
        $zipFilePath = $file->getRealPath();
711
712
        // Create an instance of the ZipArchive class
713
        $zip = new ZipArchive();
714
        $zip->open($zipFilePath);
715
716
        $cacheDirectory = $kernel->getCacheDir();
717
        $extractPath = $cacheDirectory.'/'.uniqid('extracted_', true);
718
        mkdir($extractPath);
719
720
        // Extract the contents of the ZIP file
721
        $zip->extractTo($extractPath);
722
723
        // Array to store the sorted extracted paths
724
        $extractedPaths = [];
725
726
        // Iterate over each file or directory in the ZIP file
727
        for ($i = 0; $i < $zip->numFiles; $i++) {
728
            $filename = $zip->getNameIndex($i);
729
            $extractedPaths[] = $extractPath.'/'.$filename;
730
        }
731
732
        // Close the ZIP file
733
        $zip->close();
734
735
        // Build the folder structure and file associations
736
        $folderStructure = $this->buildFolderStructure($extractedPaths, $extractPath);
737
738
        // Return the array of folder structure and the extraction path
739
        return [
740
            'folderStructure' => $folderStructure,
741
            'extractPath' => $extractPath,
742
        ];
743
    }
744
745
    private function buildFolderStructure(array $paths, string $extractPath): array
746
    {
747
        $folderStructure = [];
748
749
        foreach ($paths as $path) {
750
            $relativePath = str_replace($extractPath.'/', '', $path);
751
            $parts = explode('/', $relativePath);
752
753
            $currentLevel = &$folderStructure;
754
755
            foreach ($parts as $part) {
756
                if (!isset($currentLevel[$part])) {
757
                    $currentLevel[$part] = [];
758
                }
759
760
                $currentLevel = &$currentLevel[$part];
761
            }
762
        }
763
764
        return $this->formatFolderStructure($folderStructure);
765
    }
766
767
    private function formatFolderStructure(array $folderStructure): array
768
    {
769
        $result = [];
770
771
        foreach ($folderStructure as $folder => $contents) {
772
            $formattedContents = $this->formatFolderStructure($contents);
773
774
            if (!empty($formattedContents)) {
775
                $result[$folder] = $formattedContents;
776
            } elseif (!empty($folder)) {
777
                $result[] = $folder;
778
            }
779
        }
780
781
        return $result;
782
    }
783
784
    /**
785
     * Generates a unique filename by appending a random suffix.
786
     */
787
    private function generateUniqueTitle(string $title): string
788
    {
789
        $info = pathinfo($title);
790
        $filename = $info['filename'];
791
        $extension = isset($info['extension']) ? '.'.$info['extension'] : '';
792
793
        return $filename.'_'.uniqid().$extension;
794
    }
795
}
796