Passed
Push — master ( b46613...680ccf )
by
unknown
20:27 queued 10:50
created

ResourceController::processFile()   D

Complexity

Conditions 19
Paths 70

Size

Total Lines 131
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 77
nc 70
nop 6
dl 0
loc 131
rs 4.5166
c 0
b 0
f 0

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;
8
9
use Chamilo\CoreBundle\Entity\Course;
10
use Chamilo\CoreBundle\Entity\ResourceFile;
11
use Chamilo\CoreBundle\Entity\ResourceLink;
12
use Chamilo\CoreBundle\Entity\ResourceNode;
13
use Chamilo\CoreBundle\Entity\Session;
14
use Chamilo\CoreBundle\Entity\User;
15
use Chamilo\CoreBundle\Helpers\AccessUrlHelper;
16
use Chamilo\CoreBundle\Helpers\UserHelper;
17
use Chamilo\CoreBundle\Repository\ResourceFileRepository;
18
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
19
use Chamilo\CoreBundle\Repository\ResourceWithLinkInterface;
20
use Chamilo\CoreBundle\Repository\TrackEDownloadsRepository;
21
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
22
use Chamilo\CoreBundle\Settings\SettingsManager;
23
use Chamilo\CoreBundle\Tool\ToolChain;
24
use Chamilo\CoreBundle\Traits\ControllerTrait;
25
use Chamilo\CoreBundle\Traits\CourseControllerTrait;
26
use Chamilo\CoreBundle\Traits\GradebookControllerTrait;
27
use Chamilo\CoreBundle\Traits\ResourceControllerTrait;
28
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
29
use Chamilo\CourseBundle\Entity\CTool;
30
use Chamilo\CourseBundle\Repository\CLinkRepository;
31
use Chamilo\CourseBundle\Repository\CShortcutRepository;
32
use Chamilo\CourseBundle\Repository\CToolRepository;
33
use Doctrine\Common\Collections\ArrayCollection;
34
use Doctrine\Common\Collections\Criteria;
35
use Doctrine\ORM\EntityManagerInterface;
36
use Symfony\Bundle\SecurityBundle\Security;
37
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
38
use Symfony\Component\HttpFoundation\JsonResponse;
39
use Symfony\Component\HttpFoundation\RedirectResponse;
40
use Symfony\Component\HttpFoundation\Request;
41
use Symfony\Component\HttpFoundation\Response;
42
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
43
use Symfony\Component\HttpFoundation\StreamedResponse;
44
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
45
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
46
use Symfony\Component\Routing\Attribute\Route;
47
use Symfony\Component\Routing\RouterInterface;
48
use Symfony\Component\Serializer\SerializerInterface;
49
use ZipStream\Option\Archive;
50
use ZipStream\ZipStream;
51
52
/**
53
 * @author Julio Montoya <[email protected]>.
54
 */
55
#[Route('/r')]
56
class ResourceController extends AbstractResourceController implements CourseControllerInterface
57
{
58
    use ControllerTrait;
59
    use CourseControllerTrait;
60
    use GradebookControllerTrait;
61
    use ResourceControllerTrait;
62
63
    public function __construct(
64
        private readonly UserHelper $userHelper,
65
        private readonly ResourceNodeRepository $resourceNodeRepository,
66
        private readonly ResourceFileRepository $resourceFileRepository
67
    ) {}
68
69
    #[Route(path: '/{tool}/{type}/{id}/disk_space', methods: ['GET', 'POST'], name: 'chamilo_core_resource_disk_space')]
70
    public function diskSpace(Request $request): Response
71
    {
72
        $nodeId = $request->get('id');
73
        $repository = $this->getRepositoryFromRequest($request);
74
75
        /** @var ResourceNode $resourceNode */
76
        $resourceNode = $repository->getResourceNodeRepository()->find($nodeId);
77
78
        $this->denyAccessUnlessGranted(
79
            ResourceNodeVoter::VIEW,
80
            $resourceNode,
81
            $this->trans('Unauthorised access to resource')
82
        );
83
84
        $course = $this->getCourse();
85
        $totalSize = 0;
86
        if (null !== $course) {
87
            $totalSize = $course->getDiskQuota();
88
        }
89
90
        $size = $repository->getResourceNodeRepository()->getSize(
91
            $resourceNode,
92
            $repository->getResourceType(),
93
            $course
94
        );
95
96
        $labels[] = $course->getTitle();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$labels was never initialized. Although not strictly required by PHP, it is generally a good practice to add $labels = array(); before regardless.
Loading history...
97
        $data[] = $size;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
98
        $sessions = $course->getSessions();
99
100
        foreach ($sessions as $sessionRelCourse) {
101
            $session = $sessionRelCourse->getSession();
102
103
            $labels[] = $course->getTitle().' - '.$session->getTitle();
104
            $size = $repository->getResourceNodeRepository()->getSize(
105
                $resourceNode,
106
                $repository->getResourceType(),
107
                $course,
108
                $session
109
            );
110
            $data[] = $size;
111
        }
112
113
        /*$groups = $course->getGroups();
114
        foreach ($groups as $group) {
115
            $labels[] = $course->getTitle().' - '.$group->getTitle();
116
            $size = $repository->getResourceNodeRepository()->getSize(
117
                $resourceNode,
118
                $repository->getResourceType(),
119
                $course,
120
                null,
121
                $group
122
            );
123
            $data[] = $size;
124
        }*/
125
126
        $used = array_sum($data);
127
        $labels[] = $this->trans('Free space on disk');
128
        $data[] = $totalSize - $used;
129
130
        return $this->render(
131
            '@ChamiloCore/Resource/disk_space.html.twig',
132
            [
133
                'resourceNode' => $resourceNode,
134
                'labels' => $labels,
135
                'data' => $data,
136
            ]
137
        );
138
    }
139
140
    /**
141
     * View file of a resource node.
142
     */
143
    #[Route('/{tool}/{type}/{id}/view', name: 'chamilo_core_resource_view', methods: ['GET'])]
144
    public function view(
145
        Request $request,
146
        TrackEDownloadsRepository $trackEDownloadsRepository,
147
        SettingsManager $settingsManager,
148
        AccessUrlHelper $accessUrlHelper
149
    ): Response {
150
        $id = $request->get('id');
151
        $resourceFileId = $request->get('resourceFileId');
152
        $filter = (string) $request->get('filter');
153
        $resourceNode = $this->getResourceNodeRepository()->findOneBy(['uuid' => $id]);
154
155
        if (null === $resourceNode) {
156
            throw new FileNotFoundException($this->trans('Resource not found'));
157
        }
158
159
        $resourceFile = null;
160
        if ($resourceFileId) {
161
            $resourceFile = $this->resourceFileRepository->find($resourceFileId);
162
        }
163
164
        if (!$resourceFile) {
165
            $accessUrlSpecificFiles = $settingsManager->getSetting('document.access_url_specific_files') && $accessUrlHelper->isMultiple();
166
            $currentUrl = $accessUrlHelper->getCurrent()?->getUrl();
167
168
            $resourceFiles = $resourceNode->getResourceFiles();
169
170
            if ($accessUrlSpecificFiles) {
171
                foreach ($resourceFiles as $file) {
172
                    if ($file->getAccessUrl() && $file->getAccessUrl()->getUrl() === $currentUrl) {
173
                        $resourceFile = $file;
174
175
                        break;
176
                    }
177
                }
178
            }
179
180
            if (!$resourceFile) {
181
                $resourceFile = $resourceFiles->filter(fn ($file) => null === $file->getAccessUrl())->first();
182
            }
183
        }
184
185
        if (!$resourceFile) {
186
            throw new FileNotFoundException($this->trans('Resource file not found for the given resource node'));
187
        }
188
189
        $user = $this->userHelper->getCurrent();
190
        $firstResourceLink = $resourceNode->getResourceLinks()->first();
191
        if ($firstResourceLink && $user) {
192
            $url = $resourceFile->getOriginalName();
193
            $trackEDownloadsRepository->saveDownload($user, $firstResourceLink, $url);
194
        }
195
196
        $cid = (int) $request->query->get('cid');
197
        $sid = (int) $request->query->get('sid');
198
        $allUserInfo = null;
199
        if ($cid && $user) {
200
            $allUserInfo = $this->getAllInfoToCertificate(
201
                $user->getId(),
202
                $cid,
203
                $sid,
204
                false
205
            );
206
        }
207
208
        return $this->processFile($request, $resourceNode, 'show', $filter, $allUserInfo, $resourceFile);
209
    }
210
211
    /**
212
     * Redirect resource to link.
213
     *
214
     * @return RedirectResponse|void
215
     */
216
    #[Route('/{tool}/{type}/{id}/link', name: 'chamilo_core_resource_link', methods: ['GET'])]
217
    public function link(Request $request, RouterInterface $router, CLinkRepository $cLinkRepository): RedirectResponse
218
    {
219
        $tool = $request->get('tool');
220
        $type = $request->get('type');
221
        $id = $request->get('id');
222
        $resourceNode = $this->getResourceNodeRepository()->find($id);
223
224
        if (null === $resourceNode) {
225
            throw new FileNotFoundException('Resource not found');
226
        }
227
228
        if ('course_tool' === $tool && 'links' === $type) {
229
            $cLink = $cLinkRepository->findOneBy(['resourceNode' => $resourceNode]);
230
            if ($cLink) {
231
                $url = $cLink->getUrl();
232
233
                return $this->redirect($url);
234
            }
235
236
            throw new FileNotFoundException('CLink not found for the given resource node');
237
        } else {
238
            $repo = $this->getRepositoryFromRequest($request);
239
            if ($repo instanceof ResourceWithLinkInterface) {
240
                $resource = $repo->getResourceFromResourceNode($resourceNode->getId());
241
                $url = $repo->getLink($resource, $router, $this->getCourseUrlQueryToArray());
242
243
                return $this->redirect($url);
244
            }
245
246
            $this->abort('No redirect');
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Symfony\Component\HttpFoundation\RedirectResponse. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
247
        }
248
    }
249
250
    /**
251
     * Download file of a resource node.
252
     */
253
    #[Route('/{tool}/{type}/{id}/download', name: 'chamilo_core_resource_download', methods: ['GET'])]
254
    public function download(
255
        Request $request,
256
        TrackEDownloadsRepository $trackEDownloadsRepository,
257
        SettingsManager $settingsManager,
258
        AccessUrlHelper $accessUrlHelper
259
    ): Response {
260
        $id = $request->get('id');
261
        $resourceNode = $this->getResourceNodeRepository()->findOneBy(['uuid' => $id]);
262
263
        if (null === $resourceNode) {
264
            throw new FileNotFoundException($this->trans('Resource not found'));
265
        }
266
267
        $repo = $this->getRepositoryFromRequest($request);
268
269
        $this->denyAccessUnlessGranted(
270
            ResourceNodeVoter::VIEW,
271
            $resourceNode,
272
            $this->trans('Unauthorised access to resource')
273
        );
274
275
        $accessUrlSpecificFiles = $settingsManager->getSetting('document.access_url_specific_files') && $accessUrlHelper->isMultiple();
276
        $currentUrl = $accessUrlHelper->getCurrent()?->getUrl();
277
278
        $resourceFiles = $resourceNode->getResourceFiles();
279
        $resourceFile = null;
280
281
        if ($accessUrlSpecificFiles) {
282
            foreach ($resourceFiles as $file) {
283
                if ($file->getAccessUrl() && $file->getAccessUrl()->getUrl() === $currentUrl) {
284
                    $resourceFile = $file;
285
286
                    break;
287
                }
288
            }
289
        }
290
291
        $resourceFile ??= $resourceFiles->filter(fn ($file) => null === $file->getAccessUrl())->first();
292
293
        // If resource node has a file just download it. Don't download the children.
294
        if ($resourceFile) {
295
            $user = $this->userHelper->getCurrent();
296
            $firstResourceLink = $resourceNode->getResourceLinks()->first();
297
298
            if ($firstResourceLink && $user) {
299
                $url = $resourceFile->getOriginalName();
300
                $trackEDownloadsRepository->saveDownload($user, $firstResourceLink, $url);
301
            }
302
303
            // Redirect to download single file.
304
            return $this->processFile($request, $resourceNode, 'download', '', null, $resourceFile);
305
        }
306
307
        $zipName = $resourceNode->getSlug().'.zip';
308
        $resourceNodeRepo = $repo->getResourceNodeRepository();
309
        $type = $repo->getResourceType();
310
311
        $criteria = Criteria::create()
312
            ->where(Criteria::expr()->neq('resourceFiles', null)) // must have a file
313
            ->andWhere(Criteria::expr()->eq('resourceType', $type)) // only download same type
314
        ;
315
316
        $qb = $resourceNodeRepo->getChildrenQueryBuilder($resourceNode);
317
        $qbAlias = $qb->getRootAliases()[0];
318
319
        $qb
320
            ->leftJoin(\sprintf('%s.resourceFiles', $qbAlias), 'resourceFiles') // must have a file
321
            ->addCriteria($criteria)
322
        ;
323
324
        /** @var ArrayCollection|ResourceNode[] $children */
325
        $children = $qb->getQuery()->getResult();
326
        $count = \count($children);
327
        if (0 === $count) {
328
            $params = $this->getResourceParams($request);
329
            $params['id'] = $id;
330
331
            $this->addFlash('warning', $this->trans('No files'));
332
333
            return $this->redirectToRoute('chamilo_core_resource_list', $params);
334
        }
335
336
        $response = new StreamedResponse(
337
            function () use ($zipName, $children, $repo): void {
338
                // Define suitable options for ZipStream Archive.
339
                $options = new Archive();
340
                $options->setContentType('application/octet-stream');
341
                // initialise zipstream with output zip filename and options.
342
                $zip = new ZipStream($zipName, $options);
343
344
                /** @var ResourceNode $node */
345
                foreach ($children as $node) {
346
                    $resourceFiles = $node->getResourceFiles();
347
                    $resourceFile = $resourceFiles->filter(fn ($file) => null === $file->getAccessUrl())->first();
348
349
                    if ($resourceFile) {
350
                        $stream = $repo->getResourceNodeFileStream($node);
351
                        $fileName = $resourceFile->getOriginalName();
352
                        $zip->addFileFromStream($fileName, $stream);
353
                    }
354
                }
355
                $zip->finish();
356
            }
357
        );
358
359
        // Convert the file name to ASCII using iconv
360
        $zipName = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $zipName);
361
362
        $disposition = $response->headers->makeDisposition(
363
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
364
            $zipName // Transliterator::transliterate($zipName)
365
        );
366
        $response->headers->set('Content-Disposition', $disposition);
367
        $response->headers->set('Content-Type', 'application/octet-stream');
368
369
        return $response;
370
    }
371
372
    #[Route('/{tool}/{type}/{id}/change_visibility', name: 'chamilo_core_resource_change_visibility', methods: ['POST'])]
373
    public function changeVisibility(
374
        Request $request,
375
        EntityManagerInterface $entityManager,
376
        SerializerInterface $serializer,
377
        Security $security,
378
    ): Response {
379
        /** @var User $user */
380
        $user = $security->getUser();
381
        $isAdmin = ($user->isSuperAdmin() || $user->isAdmin());
382
        $isCourseTeacher = ($user->hasRole('ROLE_CURRENT_COURSE_TEACHER') || $user->hasRole('ROLE_CURRENT_COURSE_SESSION_TEACHER'));
383
384
        if (!($isCourseTeacher || $isAdmin)) {
385
            throw new AccessDeniedHttpException();
386
        }
387
388
        $session = null;
389
        if ($this->getSession()) {
390
            $sessionId = $this->getSession()->getId();
391
            $session = $entityManager->getRepository(Session::class)->find($sessionId);
392
        }
393
        $courseId = $this->getCourse()->getId();
394
        $course = $entityManager->getRepository(Course::class)->find($courseId);
395
        $id = $request->attributes->getInt('id');
396
        $resourceNode = $this->getResourceNodeRepository()->findOneBy(['id' => $id]);
397
398
        if (null === $resourceNode) {
399
            throw new NotFoundHttpException($this->trans('Resource not found'));
400
        }
401
402
        $link = null;
403
        foreach ($resourceNode->getResourceLinks() as $resourceLink) {
404
            if ($resourceLink->getSession() === $session) {
405
                $link = $resourceLink;
406
407
                break;
408
            }
409
        }
410
411
        if (null === $link) {
412
            $link = new ResourceLink();
413
            $link->setResourceNode($resourceNode)
414
                ->setSession($session)
415
                ->setCourse($course)
416
                ->setVisibility(ResourceLink::VISIBILITY_DRAFT)
417
            ;
418
            $entityManager->persist($link);
419
        } else {
420
            if (ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
421
                $link->setVisibility(ResourceLink::VISIBILITY_DRAFT);
422
            } else {
423
                $link->setVisibility(ResourceLink::VISIBILITY_PUBLISHED);
424
            }
425
        }
426
427
        $entityManager->flush();
428
429
        $json = $serializer->serialize(
430
            $link,
431
            'json',
432
            [
433
                'groups' => ['ctool:read'],
434
            ]
435
        );
436
437
        return JsonResponse::fromJsonString($json);
438
    }
439
440
    #[Route(
441
        '/{tool}/{type}/change_visibility/{visibility}',
442
        name: 'chamilo_core_resource_change_visibility_all',
443
        methods: ['POST']
444
    )]
445
    public function changeVisibilityAll(
446
        Request $request,
447
        CToolRepository $toolRepository,
448
        CShortcutRepository $shortcutRepository,
449
        ToolChain $toolChain,
450
        EntityManagerInterface $entityManager,
451
        Security $security
452
    ): Response {
453
        /** @var User $user */
454
        $user = $security->getUser();
455
        $isAdmin = ($user->isSuperAdmin() || $user->isAdmin());
456
        $isCourseTeacher = ($user->hasRole('ROLE_CURRENT_COURSE_TEACHER') || $user->hasRole('ROLE_CURRENT_COURSE_SESSION_TEACHER'));
457
458
        if (!($isCourseTeacher || $isAdmin)) {
459
            throw new AccessDeniedHttpException();
460
        }
461
462
        $visibility = $request->attributes->get('visibility');
463
464
        $session = null;
465
        if ($this->getSession()) {
466
            $sessionId = $this->getSession()->getId();
467
            $session = $entityManager->getRepository(Session::class)->find($sessionId);
468
        }
469
        $courseId = $this->getCourse()->getId();
470
        $course = $entityManager->getRepository(Course::class)->find($courseId);
471
472
        $result = $toolRepository->getResourcesByCourse($course, $session)
473
            ->addSelect('tool')
474
            ->innerJoin('resource.tool', 'tool')
475
            ->getQuery()
476
            ->getResult()
477
        ;
478
479
        $skipTools = ['course_tool',
480
            // 'chat',
481
            // 'notebook',
482
            // 'wiki'
483
        ];
484
485
        /** @var CTool $item */
486
        foreach ($result as $item) {
487
            if (\in_array($item->getTitle(), $skipTools, true)) {
488
                continue;
489
            }
490
            $toolModel = $toolChain->getToolFromName($item->getTool()->getTitle());
491
492
            if (!\in_array($toolModel->getCategory(), ['authoring', 'interaction'], true)) {
0 ignored issues
show
Bug introduced by
The method getCategory() does not exist on Chamilo\CoreBundle\Tool\AbstractTool. It seems like you code against a sub-type of said class. However, the method does not exist in Chamilo\CoreBundle\Tool\AbstractCourseTool. 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

492
            if (!\in_array($toolModel->/** @scrutinizer ignore-call */ getCategory(), ['authoring', 'interaction'], true)) {
Loading history...
493
                continue;
494
            }
495
496
            $resourceNode = $item->getResourceNode();
497
498
            /** @var ResourceLink $link */
499
            $link = null;
500
            foreach ($resourceNode->getResourceLinks() as $resourceLink) {
501
                if ($resourceLink->getSession() === $session) {
502
                    $link = $resourceLink;
503
504
                    break;
505
                }
506
            }
507
508
            if (null === $link) {
509
                $link = new ResourceLink();
510
                $link->setResourceNode($resourceNode)
511
                    ->setSession($session)
512
                    ->setCourse($course)
513
                    ->setVisibility(ResourceLink::VISIBILITY_DRAFT)
514
                ;
515
                $entityManager->persist($link);
516
            }
517
518
            if ('show' === $visibility) {
519
                $link->setVisibility(ResourceLink::VISIBILITY_PUBLISHED);
520
            } elseif ('hide' === $visibility) {
521
                $link->setVisibility(ResourceLink::VISIBILITY_DRAFT);
522
            }
523
        }
524
525
        $entityManager->flush();
526
527
        return new Response(null, Response::HTTP_NO_CONTENT);
528
    }
529
530
    #[Route('/resource_files/{resourceNodeId}/variants', name: 'chamilo_core_resource_files_variants', methods: ['GET'])]
531
    public function getVariants(string $resourceNodeId, EntityManagerInterface $em): JsonResponse
532
    {
533
        $variants = $em->getRepository(ResourceFile::class)->createQueryBuilder('rf')
534
            ->join('rf.resourceNode', 'rn')
535
            ->leftJoin('rn.creator', 'creator')
536
            ->where('rf.resourceNode = :resourceNodeId')
537
            ->andWhere('rf.accessUrl IS NOT NULL')
538
            ->setParameter('resourceNodeId', $resourceNodeId)
539
            ->getQuery()
540
            ->getResult()
541
        ;
542
543
        $data = [];
544
545
        /** @var ResourceFile $variant */
546
        foreach ($variants as $variant) {
547
            $data[] = [
548
                'id' => $variant->getId(),
549
                'title' => $variant->getOriginalName(),
550
                'mimeType' => $variant->getMimeType(),
551
                'size' => $variant->getSize(),
552
                'updatedAt' => $variant->getUpdatedAt()->format('Y-m-d H:i:s'),
553
                'url' => $variant->getAccessUrl() ? $variant->getAccessUrl()->getUrl() : null,
554
                'path' => $this->resourceNodeRepository->getResourceFileUrl($variant->getResourceNode(), [], null, $variant),
555
                'creator' => $variant->getResourceNode()->getCreator() ? $variant->getResourceNode()->getCreator()->getFullName() : 'Unknown',
556
            ];
557
        }
558
559
        return $this->json($data);
560
    }
561
562
    #[Route('/resource_files/{id}/delete_variant', methods: ['DELETE'], name: 'chamilo_core_resource_files_delete_variant')]
563
    public function deleteVariant(int $id, EntityManagerInterface $em): JsonResponse
564
    {
565
        $variant = $em->getRepository(ResourceFile::class)->find($id);
566
        if (!$variant) {
567
            return $this->json(['error' => 'Variant not found'], Response::HTTP_NOT_FOUND);
568
        }
569
570
        $em->remove($variant);
571
        $em->flush();
572
573
        return $this->json(['success' => true]);
574
    }
575
576
    private function processFile(Request $request, ResourceNode $resourceNode, string $mode = 'show', string $filter = '', ?array $allUserInfo = null, ?ResourceFile $resourceFile = null): mixed
577
    {
578
        $this->denyAccessUnlessGranted(
579
            ResourceNodeVoter::VIEW,
580
            $resourceNode,
581
            $this->trans('Unauthorised view access to resource')
582
        );
583
584
        $resourceFile ??= $resourceNode->getResourceFiles()->first();
585
586
        if (!$resourceFile) {
587
            throw $this->createNotFoundException($this->trans('File not found for resource'));
588
        }
589
590
        $fileName = $resourceFile->getOriginalName();
591
        $fileSize = $resourceFile->getSize();
592
        $mimeType = $resourceFile->getMimeType() ?: '';
593
        [$start, $end, $length] = $this->getRange($request, $fileSize);
594
        $resourceNodeRepo = $this->getResourceNodeRepository();
595
596
        // Convert the file name to ASCII using iconv
597
        $fileName = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $fileName);
598
599
        // MIME normalization for HTML
600
        $looksLikeHtmlByExt = (bool) preg_match('/\.x?html?$/i', (string) $fileName);
601
        if ($mimeType === '' || stripos($mimeType, 'html') === false) {
602
            if ($looksLikeHtmlByExt) {
603
                $mimeType = 'text/html; charset=UTF-8';
604
            }
605
        }
606
607
        switch ($mode) {
608
            case 'download':
609
                $forceDownload = true;
610
611
                break;
612
613
            case 'show':
614
            default:
615
                $forceDownload = false;
616
                // If it's an image then send it to Glide.
617
                if (str_contains($mimeType, 'image')) {
618
                    $glide = $this->getGlide();
619
                    $server = $glide->getServer();
620
                    $params = $request->query->all();
621
622
                    // The filter overwrites the params from GET.
623
                    if (!empty($filter)) {
624
                        $params = $glide->getFilters()[$filter] ?? [];
625
                    }
626
627
                    // The image was cropped manually by the user, so we force to render this version,
628
                    // no matter other crop parameters.
629
                    $crop = $resourceFile->getCrop();
630
                    if (!empty($crop)) {
631
                        $params['crop'] = $crop;
632
                    }
633
634
                    $filePath = $resourceNodeRepo->getFilename($resourceFile);
635
636
                    $response = $server->getImageResponse($filePath, $params);
637
638
                    $disposition = $response->headers->makeDisposition(
639
                        ResponseHeaderBag::DISPOSITION_INLINE,
640
                        $fileName
641
                    );
642
                    $response->headers->set('Content-Disposition', $disposition);
643
644
                    return $response;
645
                }
646
647
                // Modify the HTML content before displaying it.
648
                if (str_contains($mimeType, 'html')) {
649
                    $content = $resourceNodeRepo->getResourceNodeFileContent($resourceNode, $resourceFile);
650
651
                    if (null !== $allUserInfo) {
652
                        $tagsToReplace = $allUserInfo[0];
653
                        $replacementValues = $allUserInfo[1];
654
                        $content = str_replace($tagsToReplace, $replacementValues, $content);
655
                    }
656
657
                    $response = new Response();
658
                    $disposition = $response->headers->makeDisposition(
659
                        ResponseHeaderBag::DISPOSITION_INLINE,
660
                        $fileName
661
                    );
662
                    $response->headers->set('Content-Disposition', $disposition);
663
                    $response->headers->set('Content-Type', 'text/html; charset=UTF-8');
664
665
                    // @todo move into a function/class
666
                    if ('true' === $this->getSettingsManager()->getSetting('editor.translate_html')) {
667
                        $user = $this->userHelper->getCurrent();
668
                        if (null !== $user) {
669
                            // Overwrite user_json, otherwise it will be loaded by the TwigListener.php
670
                            $userJson = json_encode(['locale' => $user->getLocale()]);
671
                            $js = $this->renderView(
672
                                '@ChamiloCore/Layout/document.html.twig',
673
                                ['breadcrumb' => '', 'user_json' => $userJson]
674
                            );
675
                            // Insert inside the head tag.
676
                            $content = str_replace('</head>', $js.'</head>', $content);
677
                        }
678
                    }
679
                    $response->setContent($content);
680
681
                    return $response;
682
                }
683
684
                break;
685
        }
686
687
        $response = new StreamedResponse(
688
            function () use ($resourceNodeRepo, $resourceFile, $start, $length): void {
689
                $this->streamFileContent($resourceNodeRepo, $resourceFile, $start, $length);
690
            }
691
        );
692
693
        $disposition = $response->headers->makeDisposition(
694
            $forceDownload ? ResponseHeaderBag::DISPOSITION_ATTACHMENT : ResponseHeaderBag::DISPOSITION_INLINE,
695
            $fileName
696
        );
697
        $response->headers->set('Content-Disposition', $disposition);
698
        $response->headers->set('Content-Type', $mimeType ?: 'application/octet-stream');
699
        $response->headers->set('Content-Length', (string) $length);
700
        $response->headers->set('Accept-Ranges', 'bytes');
701
        $response->headers->set('Content-Range', "bytes $start-$end/$fileSize");
702
        $response->setStatusCode(
703
            $start > 0 || $end < $fileSize - 1 ? Response::HTTP_PARTIAL_CONTENT : Response::HTTP_OK
704
        );
705
706
        return $response;
707
    }
708
709
    private function getRange(Request $request, int $fileSize): array
710
    {
711
        $range = $request->headers->get('Range');
712
713
        if ($range) {
714
            [, $range] = explode('=', $range, 2);
715
            [$start, $end] = explode('-', $range);
716
717
            $start = (int) $start;
718
            $end = ('' === $end) ? $fileSize - 1 : (int) $end;
719
720
            $length = $end - $start + 1;
721
        } else {
722
            $start = 0;
723
            $end = $fileSize - 1;
724
            $length = $fileSize;
725
        }
726
727
        return [$start, $end, $length];
728
    }
729
730
    private function streamFileContent(ResourceNodeRepository $resourceNodeRepo, ResourceFile $resourceFile, int $start, int $length): void
731
    {
732
        $stream = $resourceNodeRepo->getResourceNodeFileStream($resourceFile->getResourceNode(), $resourceFile);
733
734
        fseek($stream, $start);
735
736
        $bytesSent = 0;
737
738
        while ($bytesSent < $length && !feof($stream)) {
739
            $buffer = fread($stream, min(1024 * 8, $length - $bytesSent));
740
741
            echo $buffer;
742
743
            $bytesSent += \strlen($buffer);
744
        }
745
746
        fclose($stream);
747
    }
748
}
749