Passed
Push — master ( 4b65b7...050f6e )
by Julito
11:11
created

ResourceController::changeVisibilityAction()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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