Passed
Push — master ( ea7869...3cbc1f )
by Julito
09:01
created

ResourceController::infoAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 1
dl 0
loc 34
rs 9.568
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller;
8
9
use Chamilo\CoreBundle\Entity\AbstractResource;
10
use Chamilo\CoreBundle\Entity\ResourceInterface;
11
use Chamilo\CoreBundle\Entity\ResourceLink;
12
use Chamilo\CoreBundle\Entity\ResourceNode;
13
use Chamilo\CoreBundle\Form\Type\ResourceCommentType;
14
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
15
use Chamilo\CoreBundle\Repository\ResourceWithLinkInterface;
16
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
17
use Chamilo\CoreBundle\Traits\ControllerTrait;
18
use Chamilo\CoreBundle\Traits\CourseControllerTrait;
19
use Chamilo\CoreBundle\Traits\ResourceControllerTrait;
20
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
21
use Doctrine\Common\Collections\ArrayCollection;
22
use Doctrine\Common\Collections\Criteria;
23
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
24
use Symfony\Component\HttpFoundation\JsonResponse;
25
use Symfony\Component\HttpFoundation\RedirectResponse;
26
use Symfony\Component\HttpFoundation\Request;
27
use Symfony\Component\HttpFoundation\Response;
28
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
29
use Symfony\Component\HttpFoundation\StreamedResponse;
30
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
31
use Symfony\Component\Routing\Annotation\Route;
32
use Symfony\Component\Routing\RouterInterface;
33
use ZipStream\Option\Archive;
34
use ZipStream\ZipStream;
35
36
/**
37
 * Class ResourceController.
38
 *
39
 * @Route("/r")
40
 *
41
 * @author Julio Montoya <[email protected]>.
42
 */
43
class ResourceController extends AbstractResourceController implements CourseControllerInterface
44
{
45
    use CourseControllerTrait;
46
    use ResourceControllerTrait;
47
    use ControllerTrait;
48
49
    private string $fileContentName = 'file_content';
50
51
    /**
52
     * @Route("/{tool}/{type}/{id}/disk_space", methods={"GET", "POST"}, name="chamilo_core_resource_disk_space")
53
     */
54
    public function diskSpaceAction(Request $request): Response
55
    {
56
        $nodeId = $request->get('id');
57
        $repository = $this->getRepositoryFromRequest($request);
58
59
        /** @var ResourceNode $resourceNode */
60
        $resourceNode = $repository->getResourceNodeRepository()->find($nodeId);
61
62
        $this->denyAccessUnlessGranted(
63
            ResourceNodeVoter::VIEW,
64
            $resourceNode,
65
            $this->trans('Unauthorised access to resource')
66
        );
67
68
        $this->setBreadCrumb($request, $resourceNode);
69
70
        $course = $this->getCourse();
71
        $totalSize = 0;
72
        if (null !== $course) {
73
            $totalSize = $course->getDiskQuota();
74
        }
75
76
        $size = $repository->getResourceNodeRepository()->getSize(
77
            $resourceNode,
78
            $repository->getResourceType(),
79
            $course
80
        );
81
82
        $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...
83
        $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...
84
        $sessions = $course->getSessions();
85
86
        foreach ($sessions as $sessionRelCourse) {
87
            $session = $sessionRelCourse->getSession();
88
89
            $labels[] = $course->getTitle().' - '.$session->getName();
90
            $size = $repository->getResourceNodeRepository()->getSize(
91
                $resourceNode,
92
                $repository->getResourceType(),
93
                $course,
94
                $session
95
            );
96
            $data[] = $size;
97
        }
98
99
        /*$groups = $course->getGroups();
100
        foreach ($groups as $group) {
101
            $labels[] = $course->getTitle().' - '.$group->getName();
102
            $size = $repository->getResourceNodeRepository()->getSize(
103
                $resourceNode,
104
                $repository->getResourceType(),
105
                $course,
106
                null,
107
                $group
108
            );
109
            $data[] = $size;
110
        }*/
111
112
        $used = array_sum($data);
113
        $labels[] = $this->trans('Free');
114
        $data[] = $totalSize - $used;
115
116
        return $this->render(
117
            $repository->getTemplates()->getFromAction(__FUNCTION__),
118
            [
119
                'resourceNode' => $resourceNode,
120
                'labels' => $labels,
121
                'data' => $data,
122
            ]
123
        );
124
    }
125
126
    /**
127
     * @deprecated Use Vue
128
     *
129
     * @Route("/{tool}/{type}/{id}/edit", methods={"GET", "POST"})
130
     */
131
    public function editAction(Request $request, IllustrationRepository $illustrationRepo): Response
132
    {
133
        $resourceNodeId = $request->get('id');
134
135
        $repository = $this->getRepositoryFromRequest($request);
136
        $resource = $repository->getResourceFromResourceNode($resourceNodeId);
137
        $this->denyAccessUnlessValidResource($resource);
138
        $settings = $repository->getResourceSettings();
139
        $resourceNode = $resource->getResourceNode();
140
141
        $this->denyAccessUnlessGranted(
142
            ResourceNodeVoter::EDIT,
143
            $resourceNode,
144
            $this->trans('Unauthorised access to resource')
145
        );
146
147
        $this->setBreadCrumb($request, $resourceNode);
148
        $resourceNodeParentId = $resourceNode->getId();
149
150
        $routeParams = $this->getResourceParams($request);
151
        $routeParams['id'] = $resourceNodeParentId;
152
153
        $form = $repository->getForm($this->container->get('form.factory'), $resource);
154
155
        if ($resourceNode->hasEditableTextContent() && $settings->isAllowToSaveEditorToResourceFile()) {
156
            /*$form->add(
157
                $this->fileContentName,
158
                CKEditorType::class,
159
                [
160
                    'mapped' => false,
161
                    'config' => [
162
                        'filebrowserImageBrowseRoute' => 'resources_filemanager',
163
                        'filebrowserImageBrowseRouteParameters' => $routeParams,
164
                    ],
165
                ]
166
            );
167
            $content = $repository->getResourceNodeFileContent($resourceNode);
168
            $form->get($this->fileContentName)->setData($content);*/
169
        }
170
171
        $form->handleRequest($request);
172
173
        if ($form->isSubmitted() && $form->isValid()) {
174
            /** @var AbstractResource|ResourceInterface $newResource */
175
            $newResource = $form->getData();
176
177
            if ($form->has($this->fileContentName)) {
178
                $data = $form->get($this->fileContentName)->getData();
179
                $repository->updateResourceFileContent($newResource, $data);
180
            }
181
182
            $repository->updateNodeForResource($newResource);
183
184
            if ($form->has('illustration')) {
185
                $illustration = $form->get('illustration')->getData();
186
                if ($illustration) {
187
                    $illustrationRepo->addIllustration($newResource, $this->getUser(), $illustration);
188
                }
189
            }
190
191
            $this->addFlash('success', $this->trans('Updated'));
192
            $resourceNodeParentId = $newResource->getResourceNode()->getParent()->getId();
193
            $routeParams['id'] = $resourceNodeParentId;
194
195
            return $this->redirectToRoute('chamilo_core_resource_list', $routeParams);
196
        }
197
198
        return $this->render(
199
            $repository->getTemplates()->getFromAction(__FUNCTION__),
200
            [
201
                'form' => $form->createView(),
202
                'parent' => $resourceNodeParentId,
203
            ]
204
        );
205
    }
206
207
    /**
208
     * Shows resource information.
209
     *
210
     * @Route("/{tool}/{type}/{id}/info", methods={"GET", "POST"}, name="chamilo_core_resource_info")
211
     */
212
    public function infoAction(Request $request): Response
213
    {
214
        $nodeId = (int) $request->get('id');
215
        $repository = $this->getRepositoryFromRequest($request);
216
217
        $resource = $repository->getResourceFromResourceNode($nodeId);
218
        $this->denyAccessUnlessValidResource($resource);
219
220
        $resourceNode = $resource->getResourceNode();
221
222
        $this->denyAccessUnlessGranted(
223
            ResourceNodeVoter::VIEW,
224
            $resourceNode,
225
            $this->trans(sprintf('Unauthorised access to resource #%s', $nodeId))
226
        );
227
228
        $this->setBreadCrumb($request, $resourceNode);
229
230
        $tool = $request->get('tool');
231
        $type = $request->get('type');
232
233
        $form = $this->createForm(ResourceCommentType::class, null);
234
235
        $params = [
236
            'resource' => $resource,
237
            'course' => $this->getCourse(),
238
            'tool' => $tool,
239
            'type' => $type,
240
            'comment_form' => $form->createView(),
241
        ];
242
243
        return $this->render(
244
            $repository->getTemplates()->getFromAction(__FUNCTION__, $request->isXmlHttpRequest()),
245
            $params
246
        );
247
    }
248
249
    /**
250
     * Preview a file. Mostly used when using a modal.
251
     *
252
     * @Route("/{tool}/{type}/{id}/preview", methods={"GET"}, name="chamilo_core_resource_preview")
253
     */
254
    /*public function previewAction(Request $request): Response
255
    {
256
        $nodeId = $request->get('id');
257
        $repository = $this->getRepositoryFromRequest($request);
258
259
        $resource = $repository->getResourceFromResourceNode($nodeId);
260
        $this->denyAccessUnlessValidResource($resource);
261
262
        $resourceNode = $resource->getResourceNode();
263
        $this->denyAccessUnlessGranted(
264
            ResourceNodeVoter::VIEW,
265
            $resourceNode,
266
            $this->trans('Unauthorised access to resource')
267
        );
268
269
        $this->setBreadCrumb($request, $resourceNode);
270
271
        $tool = $request->get('tool');
272
        $type = $request->get('type');
273
274
        $params = [
275
            'resource' => $resource,
276
            'tool' => $tool,
277
            'type' => $type,
278
        ];
279
280
        return $this->render($repository->getTemplates()->getFromAction(__FUNCTION__), $params);
281
    }*/
282
283
    /**
284
     * @deprecated use vue
285
     *
286
     * @Route("/{tool}/{type}/{id}/change_visibility", name="chamilo_core_resource_change_visibility")
287
     */
288
    public function changeVisibilityAction(Request $request): Response
289
    {
290
        $id = (int) $request->get('id');
291
292
        $repository = $this->getRepositoryFromRequest($request);
293
294
        $resource = $repository->getResourceFromResourceNode($id);
295
        $this->denyAccessUnlessValidResource($resource);
296
        /** @var AbstractResource $resource */
297
        $resourceNode = $resource->getResourceNode();
298
299
        $this->denyAccessUnlessGranted(
300
            ResourceNodeVoter::EDIT,
301
            $resourceNode,
302
            $this->trans('Unauthorised access to resource')
303
        );
304
305
        if ($this->hasCourse()) {
306
            $link = $resource->getFirstResourceLinkFromCourseSession($this->getCourse(), $this->getSession());
307
        } else {
308
            $link = $resource->getFirstResourceLink();
309
        }
310
311
        // Use repository to change settings easily.
312
        if ($link && ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
313
            $repository->setVisibilityDraft($resource);
314
        } else {
315
            $repository->setVisibilityPublished($resource);
316
        }
317
318
        $result = [
319
            'visibility' => $link->getVisibility(),
320
            'ok' => true,
321
        ];
322
323
        return new JsonResponse($result);
324
    }
325
326
    /**
327
     * @deprecated Use Vue + api platform
328
     *
329
     * @Route("/{tool}/{type}/{id}/delete", name="chamilo_core_resource_delete")
330
     */
331
    public function deleteAction(Request $request): Response
332
    {
333
        $em = $this->getDoctrine()->getManager();
334
335
        $id = $request->get('id');
336
        $resourceNode = $this->getDoctrine()->getRepository(ResourceNode::class)->find($id);
337
        $parentId = $resourceNode->getParent()->getId();
338
339
        $this->denyAccessUnlessGranted(
340
            ResourceNodeVoter::DELETE,
341
            $resourceNode,
342
            $this->trans('Unauthorised access to resource')
343
        );
344
345
        $children = $resourceNode->getChildren();
346
347
        if (!empty($children)) {
348
            /** @var ResourceNode $child */
349
            foreach ($children as $child) {
350
                $em->remove($child);
351
            }
352
        }
353
354
        $em->remove($resourceNode);
355
        $this->addFlash('success', $this->trans('Deleted').': '.$resourceNode->getSlug());
356
        $em->flush();
357
358
        $routeParams = $this->getResourceParams($request);
359
        $routeParams['id'] = $parentId;
360
361
        return $this->redirectToRoute('chamilo_core_resource_list', $routeParams);
362
    }
363
364
    /**
365
     * Shows the associated resource file.
366
     *
367
     * @deprecated use vue
368
     *
369
     * @Route("/{tool}/{type}/{id}/view_resource", methods={"GET"}, name="chamilo_core_resource_view_resource")
370
     */
371
    /*public function viewResourceAction(Request $request, RouterInterface $router): Response
372
    {
373
        $id = $request->get('id');
374
375
        $resourceNode = $this->getResourceNodeRepository()->find($id);
376
377
        if (null === $resourceNode) {
378
            throw new FileNotFoundException('Resource not found');
379
        }
380
381
        $this->denyAccessUnlessGranted(
382
            ResourceNodeVoter::VIEW,
383
            $resourceNode,
384
            $this->trans('Unauthorised access to resource')
385
        );
386
387
        $repository = $this->getRepositoryFromRequest($request);
388
389
        $resource = $repository->getResourceFromResourceNode($id);
390
391
        $tool = $request->get('tool');
392
        $type = $request->get('type');
393
        $this->setBreadCrumb($request, $resourceNode);
394
395
        $params = [
396
            'resource' => $resource,
397
            'tool' => $tool,
398
            'type' => $type,
399
        ];
400
401
        return $this->render($repository->getTemplates()->getFromAction(__FUNCTION__), $params);
402
    }*/
403
404
    /**
405
     * View file of a resource node.
406
     *
407
     * @Route("/{tool}/{type}/{id}/view", methods={"GET"}, name="chamilo_core_resource_view")
408
     */
409
    public function viewAction(Request $request): Response
410
    {
411
        $id = $request->get('id');
412
        $filter = (string) $request->get('filter'); // See filters definitions in /config/services.yml.
413
        $resourceNode = $this->getResourceNodeRepository()->find($id);
414
415
        if (null === $resourceNode) {
416
            throw new FileNotFoundException('Resource not found');
417
        }
418
419
        return $this->processFile($request, $resourceNode, 'show', $filter);
420
    }
421
422
    /**
423
     * Redirect resource to link.
424
     *
425
     * @Route("/{tool}/{type}/{id}/link", methods={"GET"}, name="chamilo_core_resource_link")
426
     *
427
     * @return RedirectResponse|void
428
     */
429
    public function linkAction(Request $request, RouterInterface $router)
430
    {
431
        $id = $request->get('id');
432
        $resourceNode = $this->getResourceNodeRepository()->find($id);
433
434
        if (null === $resourceNode) {
435
            throw new FileNotFoundException('Resource not found');
436
        }
437
438
        $repo = $this->getRepositoryFromRequest($request);
439
        if ($repo instanceof ResourceWithLinkInterface) {
440
            $resource = $repo->getResourceFromResourceNode($resourceNode->getId());
441
            $url = $repo->getLink($resource, $router, $this->getCourseUrlQueryToArray());
442
443
            return $this->redirect($url);
444
        }
445
446
        $this->abort('No redirect');
447
    }
448
449
    /**
450
     * Download file of a resource node.
451
     *
452
     * @Route("/{tool}/{type}/{id}/download", methods={"GET"}, name="chamilo_core_resource_download")
453
     *
454
     * @return RedirectResponse|StreamedResponse
455
     */
456
    public function downloadAction(Request $request)
457
    {
458
        $id = (int) $request->get('id');
459
        $resourceNode = $this->getResourceNodeRepository()->find($id);
460
461
        if (null === $resourceNode) {
462
            throw new FileNotFoundException('Resource not found');
463
        }
464
465
        $repo = $this->getRepositoryFromRequest($request);
466
467
        $this->denyAccessUnlessGranted(
468
            ResourceNodeVoter::VIEW,
469
            $resourceNode,
470
            $this->trans('Unauthorised access to resource')
471
        );
472
473
        // If resource node has a file just download it. Don't download the children.
474
        if ($resourceNode->hasResourceFile()) {
475
            // Redirect to download single file.
476
            return $this->processFile($request, $resourceNode, 'download');
477
        }
478
479
        $zipName = $resourceNode->getSlug().'.zip';
480
        //$rootNodePath = $resourceNode->getPathForDisplay();
481
        $resourceNodeRepo = $repo->getResourceNodeRepository();
482
        $type = $repo->getResourceType();
483
484
        $criteria = Criteria::create()
485
            ->where(Criteria::expr()->neq('resourceFile', null)) // must have a file
486
            ->andWhere(Criteria::expr()->eq('resourceType', $type)) // only download same type
487
        ;
488
489
        $qb = $resourceNodeRepo->getChildrenQueryBuilder($resourceNode);
490
        $qb->addCriteria($criteria);
491
        /** @var ArrayCollection|ResourceNode[] $children */
492
        $children = $qb->getQuery()->getResult();
493
        $count = \count($children);
494
        if (0 === $count) {
495
            $params = $this->getResourceParams($request);
496
            $params['id'] = $id;
497
498
            $this->addFlash('warning', $this->trans('No files'));
499
500
            return $this->redirectToRoute('chamilo_core_resource_list', $params);
501
        }
502
503
        $response = new StreamedResponse(
504
            function () use ($zipName, $children, $repo): void {
505
                // Define suitable options for ZipStream Archive.
506
                $options = new Archive();
507
                $options->setContentType('application/octet-stream');
508
                //initialise zipstream with output zip filename and options.
509
                $zip = new ZipStream($zipName, $options);
510
511
                /** @var ResourceNode $node */
512
                foreach ($children as $node) {
513
                    $stream = $repo->getResourceNodeFileStream($node);
514
                    $fileName = $node->getResourceFile()->getOriginalName();
515
                    //$fileToDisplay = basename($node->getPathForDisplay());
516
                    //$fileToDisplay = str_replace($rootNodePath, '', $node->getPathForDisplay());
517
                    //error_log($fileToDisplay);
518
                    $zip->addFileFromStream($fileName, $stream);
519
                }
520
                $zip->finish();
521
            }
522
        );
523
524
        $disposition = $response->headers->makeDisposition(
525
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
526
            $zipName //Transliterator::transliterate($zipName)
527
        );
528
        $response->headers->set('Content-Disposition', $disposition);
529
        $response->headers->set('Content-Type', 'application/octet-stream');
530
531
        return $response;
532
    }
533
534
    /**
535
     * @return mixed|StreamedResponse
536
     */
537
    private function processFile(Request $request, ResourceNode $resourceNode, string $mode = 'show', string $filter = '')
538
    {
539
        $this->denyAccessUnlessGranted(
540
            ResourceNodeVoter::VIEW,
541
            $resourceNode,
542
            $this->trans('Unauthorised view access to resource')
543
        );
544
545
        $resourceFile = $resourceNode->getResourceFile();
546
547
        if (null === $resourceFile) {
548
            throw new NotFoundHttpException($this->trans('File not found for resource'));
549
        }
550
551
        $fileName = $resourceNode->getResourceFile()->getOriginalName();
552
        $mimeType = $resourceFile->getMimeType();
553
        $resourceNodeRepo = $this->getResourceNodeRepository();
554
555
        switch ($mode) {
556
            case 'download':
557
                $forceDownload = true;
558
559
                break;
560
            case 'show':
561
            default:
562
                $forceDownload = false;
563
                // If it's an image then send it to Glide.
564
                if (str_contains($mimeType, 'image')) {
565
                    $glide = $this->getGlide();
566
                    $server = $glide->getServer();
567
                    $params = $request->query->all();
568
569
                    // The filter overwrites the params from GET.
570
                    if (!empty($filter)) {
571
                        $params = $glide->getFilters()[$filter] ?? [];
572
                    }
573
574
                    // The image was cropped manually by the user, so we force to render this version,
575
                    // no matter other crop parameters.
576
                    $crop = $resourceFile->getCrop();
577
                    if (!empty($crop)) {
578
                        $params['crop'] = $crop;
579
                    }
580
581
                    $fileName = $resourceNodeRepo->getFilename($resourceFile);
582
583
                    return $server->getImageResponse($fileName, $params);
584
                }
585
586
                // Modify the HTML content before displaying it.
587
                if (str_contains($mimeType, 'html')) {
588
                    $content = $resourceNodeRepo->getResourceNodeFileContent($resourceNode);
589
590
                    $response = new Response();
591
                    $disposition = $response->headers->makeDisposition(
592
                        ResponseHeaderBag::DISPOSITION_INLINE,
593
                        $fileName
594
                    );
595
                    $response->headers->set('Content-Disposition', $disposition);
596
                    $response->headers->set('Content-Type', 'text/html');
597
598
                    // @todo move into a function/class
599
                    if ('true' === $this->getSettingsManager()->getSetting('editor.translate_html')) {
600
                        $user = $this->getUser();
601
                        if (null !== $user) {
602
                            // Overwrite user_json, otherwise it will be loaded by the TwigListener.php
603
                            $userJson = json_encode(['locale' => $user->getLocale()]);
604
                            $js = $this->renderView(
605
                                '@ChamiloCore/Layout/document.html.twig',
606
                                ['breadcrumb' => '', 'user_json' => $userJson]
607
                            );
608
                            // Insert inside the head tag.
609
                            $content = str_replace('</head>', $js.'</head>', $content);
610
                        }
611
                    }
612
613
                    $response->setContent($content);
614
                    /*$contents = $this->renderView('@ChamiloCore/Resource/view_html.twig', [
615
                        'category' => '...',
616
                    ]);*/
617
618
                    return $response;
619
                }
620
621
                break;
622
        }
623
624
        $stream = $resourceNodeRepo->getResourceNodeFileStream($resourceNode);
625
626
        $response = new StreamedResponse(
627
            function () use ($stream): void {
628
                stream_copy_to_stream($stream, fopen('php://output', 'wb'));
629
            }
630
        );
631
632
        //Transliterator::transliterate($fileName)
633
        $disposition = $response->headers->makeDisposition(
634
            $forceDownload ? ResponseHeaderBag::DISPOSITION_ATTACHMENT : ResponseHeaderBag::DISPOSITION_INLINE,
635
            $fileName
636
        );
637
        $response->headers->set('Content-Disposition', $disposition);
638
        $response->headers->set('Content-Type', $mimeType ?: 'application/octet-stream');
639
640
        return $response;
641
    }
642
}
643