Passed
Push — master ( ffb0a0...116c25 )
by Julito
08:25
created

ResourceController::deleteAction()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 2
nop 1
dl 0
loc 31
rs 9.6666
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\ResourceLink;
11
use Chamilo\CoreBundle\Entity\ResourceNode;
12
use Chamilo\CoreBundle\Form\Type\ResourceCommentType;
13
use Chamilo\CoreBundle\Repository\ResourceWithLinkInterface;
14
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
15
use Chamilo\CoreBundle\Traits\ControllerTrait;
16
use Chamilo\CoreBundle\Traits\CourseControllerTrait;
17
use Chamilo\CoreBundle\Traits\ResourceControllerTrait;
18
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
19
use Doctrine\Common\Collections\ArrayCollection;
20
use Doctrine\Common\Collections\Criteria;
21
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
22
use Symfony\Component\HttpFoundation\JsonResponse;
23
use Symfony\Component\HttpFoundation\RedirectResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
27
use Symfony\Component\HttpFoundation\StreamedResponse;
28
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
29
use Symfony\Component\Routing\Annotation\Route;
30
use Symfony\Component\Routing\RouterInterface;
31
use ZipStream\Option\Archive;
32
use ZipStream\ZipStream;
33
34
/**
35
 * @author Julio Montoya <[email protected]>.
36
 */
37
#[Route('/r')]
38
class ResourceController extends AbstractResourceController implements CourseControllerInterface
39
{
40
    use CourseControllerTrait;
41
    use ResourceControllerTrait;
42
    use ControllerTrait;
43
44
    private string $fileContentName = 'file_content';
0 ignored issues
show
introduced by
The private property $fileContentName is not used, and could be removed.
Loading history...
45
46
    /**
47
     * @Route("/{tool}/{type}/{id}/disk_space", methods={"GET", "POST"}, name="chamilo_core_resource_disk_space")
48
     */
49
    public function diskSpaceAction(Request $request): Response
50
    {
51
        $nodeId = $request->get('id');
52
        $repository = $this->getRepositoryFromRequest($request);
53
54
        /** @var ResourceNode $resourceNode */
55
        $resourceNode = $repository->getResourceNodeRepository()->find($nodeId);
56
57
        $this->denyAccessUnlessGranted(
58
            ResourceNodeVoter::VIEW,
59
            $resourceNode,
60
            $this->trans('Unauthorised access to resource')
61
        );
62
63
        $course = $this->getCourse();
64
        $totalSize = 0;
65
        if (null !== $course) {
66
            $totalSize = $course->getDiskQuota();
67
        }
68
69
        $size = $repository->getResourceNodeRepository()->getSize(
70
            $resourceNode,
71
            $repository->getResourceType(),
72
            $course
73
        );
74
75
        $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...
76
        $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...
77
        $sessions = $course->getSessions();
78
79
        foreach ($sessions as $sessionRelCourse) {
80
            $session = $sessionRelCourse->getSession();
81
82
            $labels[] = $course->getTitle().' - '.$session->getName();
83
            $size = $repository->getResourceNodeRepository()->getSize(
84
                $resourceNode,
85
                $repository->getResourceType(),
86
                $course,
87
                $session
88
            );
89
            $data[] = $size;
90
        }
91
92
        /*$groups = $course->getGroups();
93
        foreach ($groups as $group) {
94
            $labels[] = $course->getTitle().' - '.$group->getName();
95
            $size = $repository->getResourceNodeRepository()->getSize(
96
                $resourceNode,
97
                $repository->getResourceType(),
98
                $course,
99
                null,
100
                $group
101
            );
102
            $data[] = $size;
103
        }*/
104
105
        $used = array_sum($data);
106
        $labels[] = $this->trans('Free');
107
        $data[] = $totalSize - $used;
108
109
        return $this->render(
110
            '@ChamiloCore/Resource/disk_space.html.twig',
111
            [
112
                'resourceNode' => $resourceNode,
113
                'labels' => $labels,
114
                'data' => $data,
115
            ]
116
        );
117
    }
118
119
    /**
120
     * Shows resource information.
121
     *
122
     * @Route("/{tool}/{type}/{id}/info", methods={"GET", "POST"}, name="chamilo_core_resource_info")
123
     */
124
    public function infoAction(Request $request): Response
125
    {
126
        $nodeId = (int) $request->get('id');
127
        $repository = $this->getRepositoryFromRequest($request);
128
129
        $resource = $repository->getResourceFromResourceNode($nodeId);
130
        $this->denyAccessUnlessValidResource($resource);
131
132
        $resourceNode = $resource->getResourceNode();
133
134
        $this->denyAccessUnlessGranted(
135
            ResourceNodeVoter::VIEW,
136
            $resourceNode,
137
            $this->trans(sprintf('Unauthorised access to resource #%s', $nodeId))
138
        );
139
140
        //$this->setBreadCrumb($request, $resourceNode);
141
142
        $tool = $request->get('tool');
143
        $type = $request->get('type');
144
145
        $form = $this->createForm(ResourceCommentType::class, null);
146
147
        $params = [
148
            'resource' => $resource,
149
            'course' => $this->getCourse(),
150
            'tool' => $tool,
151
            'type' => $type,
152
            'comment_form' => $form->createView(),
153
        ];
154
155
        return $this->render(
156
            '@ChamiloCore/Resource/info.html.twig',
157
            $params
158
        );
159
    }
160
161
    /**
162
     * @deprecated use vue
163
     *
164
     * @Route("/{tool}/{type}/{id}/change_visibility", name="chamilo_core_resource_change_visibility")
165
     */
166
    public function changeVisibilityAction(Request $request): Response
167
    {
168
        $id = (int) $request->get('id');
169
170
        $repository = $this->getRepositoryFromRequest($request);
171
172
        $resource = $repository->getResourceFromResourceNode($id);
173
        $this->denyAccessUnlessValidResource($resource);
174
        /** @var AbstractResource $resource */
175
        $resourceNode = $resource->getResourceNode();
176
177
        $this->denyAccessUnlessGranted(
178
            ResourceNodeVoter::EDIT,
179
            $resourceNode,
180
            $this->trans('Unauthorised access to resource')
181
        );
182
183
        if ($this->hasCourse()) {
184
            $link = $resource->getFirstResourceLinkFromCourseSession($this->getCourse(), $this->getSession());
185
        } else {
186
            $link = $resource->getFirstResourceLink();
187
        }
188
189
        // Use repository to change settings easily.
190
        if ($link && ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
191
            $repository->setVisibilityDraft($resource);
192
        } else {
193
            $repository->setVisibilityPublished($resource);
194
        }
195
196
        $result = [
197
            'visibility' => $link->getVisibility(),
198
            'ok' => true,
199
        ];
200
201
        return new JsonResponse($result);
202
    }
203
204
    /**
205
     * View file of a resource node.
206
     */
207
    #[Route('/{tool}/{type}/{id}/view', name: 'chamilo_core_resource_view', methods: ['GET'])]
208
    public function viewAction(Request $request): Response
209
    {
210
        $id = $request->get('id');
211
        $filter = (string) $request->get('filter'); // See filters definitions in /config/services.yml.
212
        $resourceNode = $this->getResourceNodeRepository()->findOneBy(['uuid' => $id]);
213
214
        if (null === $resourceNode) {
215
            throw new FileNotFoundException($this->trans('Resource not found'));
216
        }
217
218
        return $this->processFile($request, $resourceNode, 'show', $filter);
219
    }
220
221
    /**
222
     * Redirect resource to link.
223
     *
224
     * @return RedirectResponse|void
225
     */
226
    #[Route('/{tool}/{type}/{id}/link', name: 'chamilo_core_resource_link', methods: ['GET'])]
227
    public function linkAction(Request $request, RouterInterface $router)
228
    {
229
        $id = $request->get('id');
230
        $resourceNode = $this->getResourceNodeRepository()->find($id);
231
232
        if (null === $resourceNode) {
233
            throw new FileNotFoundException('Resource not found');
234
        }
235
236
        $repo = $this->getRepositoryFromRequest($request);
237
        if ($repo instanceof ResourceWithLinkInterface) {
238
            $resource = $repo->getResourceFromResourceNode($resourceNode->getId());
239
            $url = $repo->getLink($resource, $router, $this->getCourseUrlQueryToArray());
240
241
            return $this->redirect($url);
242
        }
243
244
        $this->abort('No redirect');
245
    }
246
247
    /**
248
     * Download file of a resource node.
249
     *
250
     * @return RedirectResponse|StreamedResponse
251
     */
252
    #[Route('/{tool}/{type}/{id}/download', name: 'chamilo_core_resource_download', methods: ['GET'])]
253
    public function downloadAction(Request $request)
254
    {
255
        $id = $request->get('id');
256
        $resourceNode = $this->getResourceNodeRepository()->findOneBy(['uuid' => $id]);
257
        //$resourceNode = $this->getResourceNodeRepository()->find($id);
258
259
        if (null === $resourceNode) {
260
            throw new FileNotFoundException($this->trans('Resource not found'));
261
        }
262
263
        $repo = $this->getRepositoryFromRequest($request);
264
265
        $this->denyAccessUnlessGranted(
266
            ResourceNodeVoter::VIEW,
267
            $resourceNode,
268
            $this->trans('Unauthorised access to resource')
269
        );
270
271
        // If resource node has a file just download it. Don't download the children.
272
        if ($resourceNode->hasResourceFile()) {
273
            // Redirect to download single file.
274
            return $this->processFile($request, $resourceNode, 'download');
275
        }
276
277
        $zipName = $resourceNode->getSlug().'.zip';
278
        //$rootNodePath = $resourceNode->getPathForDisplay();
279
        $resourceNodeRepo = $repo->getResourceNodeRepository();
280
        $type = $repo->getResourceType();
281
282
        $criteria = Criteria::create()
283
            ->where(Criteria::expr()->neq('resourceFile', null)) // must have a file
284
            ->andWhere(Criteria::expr()->eq('resourceType', $type)) // only download same type
285
        ;
286
287
        $qb = $resourceNodeRepo->getChildrenQueryBuilder($resourceNode);
288
        $qb->addCriteria($criteria);
289
        /** @var ArrayCollection|ResourceNode[] $children */
290
        $children = $qb->getQuery()->getResult();
291
        $count = \count($children);
292
        if (0 === $count) {
293
            $params = $this->getResourceParams($request);
294
            $params['id'] = $id;
295
296
            $this->addFlash('warning', $this->trans('No files'));
297
298
            return $this->redirectToRoute('chamilo_core_resource_list', $params);
299
        }
300
301
        $response = new StreamedResponse(
302
            function () use ($zipName, $children, $repo): void {
303
                // Define suitable options for ZipStream Archive.
304
                $options = new Archive();
305
                $options->setContentType('application/octet-stream');
306
                //initialise zipstream with output zip filename and options.
307
                $zip = new ZipStream($zipName, $options);
308
309
                /** @var ResourceNode $node */
310
                foreach ($children as $node) {
311
                    $stream = $repo->getResourceNodeFileStream($node);
312
                    $fileName = $node->getResourceFile()->getOriginalName();
313
                    //$fileToDisplay = basename($node->getPathForDisplay());
314
                    //$fileToDisplay = str_replace($rootNodePath, '', $node->getPathForDisplay());
315
                    //error_log($fileToDisplay);
316
                    $zip->addFileFromStream($fileName, $stream);
317
                }
318
                $zip->finish();
319
            }
320
        );
321
322
        $disposition = $response->headers->makeDisposition(
323
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
324
            $zipName //Transliterator::transliterate($zipName)
325
        );
326
        $response->headers->set('Content-Disposition', $disposition);
327
        $response->headers->set('Content-Type', 'application/octet-stream');
328
329
        return $response;
330
    }
331
332
    /**
333
     * @return mixed|StreamedResponse
334
     */
335
    private function processFile(Request $request, ResourceNode $resourceNode, string $mode = 'show', string $filter = '')
336
    {
337
        $this->denyAccessUnlessGranted(
338
            ResourceNodeVoter::VIEW,
339
            $resourceNode,
340
            $this->trans('Unauthorised view access to resource')
341
        );
342
343
        $resourceFile = $resourceNode->getResourceFile();
344
345
        if (null === $resourceFile) {
346
            throw new NotFoundHttpException($this->trans('File not found for resource'));
347
        }
348
349
        $fileName = $resourceNode->getResourceFile()->getOriginalName();
350
        $mimeType = $resourceFile->getMimeType();
351
        $resourceNodeRepo = $this->getResourceNodeRepository();
352
353
        switch ($mode) {
354
            case 'download':
355
                $forceDownload = true;
356
357
                break;
358
            case 'show':
359
            default:
360
                $forceDownload = false;
361
                // If it's an image then send it to Glide.
362
                if (str_contains($mimeType, 'image')) {
363
                    $glide = $this->getGlide();
364
                    $server = $glide->getServer();
365
                    $params = $request->query->all();
366
367
                    // The filter overwrites the params from GET.
368
                    if (!empty($filter)) {
369
                        $params = $glide->getFilters()[$filter] ?? [];
370
                    }
371
372
                    // The image was cropped manually by the user, so we force to render this version,
373
                    // no matter other crop parameters.
374
                    $crop = $resourceFile->getCrop();
375
                    if (!empty($crop)) {
376
                        $params['crop'] = $crop;
377
                    }
378
379
                    $fileName = $resourceNodeRepo->getFilename($resourceFile);
380
381
                    $response = $server->getImageResponse($fileName, $params);
382
383
                    $disposition = $response->headers->makeDisposition(
384
                        ResponseHeaderBag::DISPOSITION_INLINE,
385
                        basename($fileName)
386
                    );
387
                    $response->headers->set('Content-Disposition', $disposition);
388
389
                    return $response;
390
                }
391
392
                // Modify the HTML content before displaying it.
393
                if (str_contains($mimeType, 'html')) {
394
                    $content = $resourceNodeRepo->getResourceNodeFileContent($resourceNode);
395
396
                    $response = new Response();
397
                    $disposition = $response->headers->makeDisposition(
398
                        ResponseHeaderBag::DISPOSITION_INLINE,
399
                        $fileName
400
                    );
401
                    $response->headers->set('Content-Disposition', $disposition);
402
                    $response->headers->set('Content-Type', 'text/html');
403
404
                    // @todo move into a function/class
405
                    if ('true' === $this->getSettingsManager()->getSetting('editor.translate_html')) {
406
                        $user = $this->getUser();
407
                        if (null !== $user) {
408
                            // Overwrite user_json, otherwise it will be loaded by the TwigListener.php
409
                            $userJson = json_encode(['locale' => $user->getLocale()]);
410
                            $js = $this->renderView(
411
                                '@ChamiloCore/Layout/document.html.twig',
412
                                ['breadcrumb' => '', 'user_json' => $userJson]
413
                            );
414
                            // Insert inside the head tag.
415
                            $content = str_replace('</head>', $js.'</head>', $content);
416
                        }
417
                    }
418
                    if ('true' === $this->getSettingsManager()->getSetting('course.enable_bootstrap_in_documents_html')) {
419
                        // It adds the bootstrap and awesome css
420
                        $links = '<link href="'.api_get_path(WEB_PATH).'libs/bootstrap/bootstrap.min.css" rel="stylesheet">';
421
                        $links .= '<link href="'.api_get_path(WEB_PATH).'libs/bootstrap/font-awesome.min.css" rel="stylesheet">';
422
                        // Insert inside the head tag.
423
                        $content = str_replace('</head>', $links.'</head>', $content);
424
                    }
425
                    $response->setContent($content);
426
                    /*$contents = $this->renderView('@ChamiloCore/Resource/view_html.twig', [
427
                        'category' => '...',
428
                    ]);*/
429
430
                    return $response;
431
                }
432
433
                break;
434
        }
435
436
        $stream = $resourceNodeRepo->getResourceNodeFileStream($resourceNode);
437
438
        $response = new StreamedResponse(
439
            function () use ($stream): void {
440
                stream_copy_to_stream($stream, fopen('php://output', 'wb'));
441
            }
442
        );
443
444
        //Transliterator::transliterate($fileName)
445
        $disposition = $response->headers->makeDisposition(
446
            $forceDownload ? ResponseHeaderBag::DISPOSITION_ATTACHMENT : ResponseHeaderBag::DISPOSITION_INLINE,
447
            $fileName
448
        );
449
        $response->headers->set('Content-Disposition', $disposition);
450
        $response->headers->set('Content-Type', $mimeType ?: 'application/octet-stream');
451
452
        return $response;
453
    }
454
}
455