Passed
Push — master ( c032cd...29e9e9 )
by Julito
09:25
created

ResourceController   F

Complexity

Total Complexity 82

Size/Duplication

Total Lines 1224
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 1
Metric Value
eloc 580
dl 0
loc 1224
rs 2
c 6
b 0
f 1
wmc 82

20 Methods

Rating   Name   Duplication   Size   Complexity  
A uploadAction() 0 21 1
B setBreadCrumb() 0 72 8
A getParentResourceNode() 0 25 5
A deleteAction() 0 28 2
A deleteMassAction() 0 31 3
A previewAction() 0 36 3
D getGrid() 0 312 15
A getDocumentAction() 0 2 1
A newAction() 0 5 1
A indexAction() 0 30 1
A viewAction() 0 13 2
B editAction() 0 77 7
A diskSpaceAction() 0 23 1
A newFolderAction() 0 5 1
A infoAction() 0 39 3
A changeVisibilityAction() 0 40 5
A listAction() 0 23 1
B showFile() 0 66 9
A downloadAction() 0 72 5
B createResource() 0 152 8

How to fix   Complexity   

Complex Class

Complex classes like ResourceController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ResourceController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
namespace Chamilo\CoreBundle\Controller;
6
7
use APY\DataGridBundle\Grid\Action\MassAction;
8
use APY\DataGridBundle\Grid\Action\RowAction;
9
use APY\DataGridBundle\Grid\Column\Column;
10
use APY\DataGridBundle\Grid\Export\CSVExport;
11
use APY\DataGridBundle\Grid\Export\ExcelExport;
12
use APY\DataGridBundle\Grid\Grid;
13
use APY\DataGridBundle\Grid\Row;
14
use APY\DataGridBundle\Grid\Source\Entity;
15
use Chamilo\CoreBundle\Component\Utils\Glide;
16
use Chamilo\CoreBundle\Entity\Resource\AbstractResource;
17
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
18
use Chamilo\CoreBundle\Entity\Resource\ResourceNode;
19
use Chamilo\CoreBundle\Repository\IllustrationRepository;
20
use Chamilo\CoreBundle\Repository\ResourceRepository;
21
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
22
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
23
use Chamilo\CourseBundle\Controller\CourseControllerTrait;
24
use Chamilo\CourseBundle\Entity\CDocument;
25
use Chamilo\UserBundle\Entity\User;
26
use Doctrine\Common\Collections\ArrayCollection;
27
use Doctrine\Common\Collections\Criteria;
28
use Doctrine\ORM\QueryBuilder;
29
use FOS\CKEditorBundle\Form\Type\CKEditorType;
30
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
31
use Symfony\Component\HttpFoundation\File\UploadedFile;
32
use Symfony\Component\HttpFoundation\JsonResponse;
33
use Symfony\Component\HttpFoundation\RedirectResponse;
34
use Symfony\Component\HttpFoundation\Request;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
37
use Symfony\Component\HttpFoundation\StreamedResponse;
38
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
39
use Symfony\Component\Routing\Annotation\Route;
40
use Symfony\Component\Routing\Router;
41
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
42
use Vich\UploaderBundle\Util\Transliterator;
43
use ZipStream\Option\Archive;
44
use ZipStream\ZipStream;
45
46
/**
47
 * Class ResourceController.
48
 *
49
 * @todo improve/refactor $this->denyAccessUnlessGranted
50
 * @Route("/resources")
51
 *
52
 * @author Julio Montoya <[email protected]>.
53
 */
54
class ResourceController extends AbstractResourceController implements CourseControllerInterface
55
{
56
    use CourseControllerTrait;
57
58
    /**
59
     * @Route("/{tool}/{type}", name="chamilo_core_resource_index")
60
     *
61
     * Example: /document/files (See the 'tool' and the 'resource_type' DB tables.)
62
     * For the tool value check the Tool entity.
63
     * For the type value check the ResourceType entity.
64
     */
65
    public function indexAction(Request $request, Grid $grid): Response
66
    {
67
        $tool = $request->get('tool');
68
        $type = $request->get('type');
69
70
        $parentResourceNode = $this->getParentResourceNode($request);
71
        $repository = $this->getRepositoryFromRequest($request);
72
        $settings = $repository->getResourceSettings();
73
74
        $grid = $this->getGrid($request, $repository, $grid, $parentResourceNode->getId());
75
76
        $breadcrumb = $this->getBreadCrumb();
77
        $breadcrumb->addChild(
78
            $this->trans($tool),
79
            [
80
                'uri' => '#',
81
            ]
82
        );
83
84
        // The base resource node is the course.
85
        $id = $parentResourceNode->getId();
86
87
        return $grid->getGridResponse(
88
            '@ChamiloTheme/Resource/index.html.twig',
89
            [
90
                'tool' => $tool,
91
                'type' => $type,
92
                'id' => $id,
93
                'parent_resource_node' => $parentResourceNode,
94
                'resource_settings' => $settings,
95
            ]
96
        );
97
    }
98
99
    /**
100
     * @Route("/{tool}/{type}/{id}/list", name="chamilo_core_resource_list")
101
     *
102
     * If node has children show it
103
     */
104
    public function listAction(Request $request, Grid $grid): Response
105
    {
106
        $tool = $request->get('tool');
107
        $type = $request->get('type');
108
        $resourceNodeId = $request->get('id');
109
110
        $repository = $this->getRepositoryFromRequest($request);
111
        $settings = $repository->getResourceSettings();
112
113
        $grid = $this->getGrid($request, $repository, $grid, $resourceNodeId);
114
115
        $this->setBreadCrumb($request);
116
        $parentResourceNode = $this->getParentResourceNode($request);
117
118
        return $grid->getGridResponse(
119
            '@ChamiloTheme/Resource/index.html.twig',
120
            [
121
                'parent_id' => $resourceNodeId,
122
                'tool' => $tool,
123
                'type' => $type,
124
                'id' => $resourceNodeId,
125
                'parent_resource_node' => $parentResourceNode,
126
                'resource_settings' => $settings,
127
            ]
128
        );
129
    }
130
131
    public function getGrid(Request $request, ResourceRepository $repository, Grid $grid, $resourceNodeId): Grid
132
    {
133
        $class = $repository->getRepository()->getClassName();
134
135
        // The group 'resource' is set in the @GRID\Source annotation in the entity.
136
        $source = new Entity($class, 'resource');
137
        /** @var ResourceNode $parentNode */
138
        $parentNode = $repository->getResourceNodeRepository()->find($resourceNodeId);
139
140
        $this->denyAccessUnlessGranted(
141
            ResourceNodeVoter::VIEW,
142
            $parentNode,
143
            $this->trans('Unauthorised access to resource')
144
        );
145
146
        $settings = $repository->getResourceSettings();
147
148
        $course = $this->getCourse();
149
        $session = $this->getSession();
150
151
        $qb = $repository->getResources($this->getUser(), $parentNode, $course, $session, null);
152
153
        // 3. Set QueryBuilder to the source.
154
        $source->initQueryBuilder($qb);
155
        $grid->setSource($source);
156
157
        $resourceParams = $this->getResourceParams($request);
158
159
        $grid->setRouteUrl($this->generateUrl('chamilo_core_resource_list', $resourceParams));
160
161
        //$grid->hideFilters();
162
        //$grid->setLimits(20);
163
        //$grid->isReadyForRedirect();
164
        //$grid->setMaxResults(1);
165
        //$grid->setLimits(2);
166
        //$grid->setColumns($columns);
167
        $routeParams = $resourceParams;
168
        $routeParams['id'] = null;
169
170
        /** @var Column $titleColumn */
171
        $titleColumn = $repository->getTitleColumn($grid);
172
        $titleColumn->setSafe(false); // allows links in the title
173
174
        // Title link.
175
        $titleColumn->setTitle($this->trans('Name'));
176
177
        //$repository->formatGrid();
178
        /*if ($grid->hasColumn('filetype')) {
179
            $grid->getColumn('filetype')->setTitle($this->trans('Type'));
180
        }*/
181
182
        $titleColumn->manipulateRenderCell(
183
            function ($value, Row $row, $router) use ($routeParams) {
184
                /** @var Router $router */
185
                /** @var AbstractResource $entity */
186
                $entity = $row->getEntity();
187
                $resourceNode = $entity->getResourceNode();
188
                $id = $resourceNode->getId();
189
190
                $myParams = $routeParams;
191
                $myParams['id'] = $id;
192
                unset($myParams[0]);
193
194
                $icon = $resourceNode->getIcon().' &nbsp;';
195
                if ($resourceNode->hasResourceFile()) {
196
                    if ($resourceNode->isResourceFileAnImage()) {
197
                        $url = $router->generate(
198
                            'chamilo_core_resource_view',
199
                            $myParams
200
                        );
201
202
                        return $icon.'<a data-fancybox="gallery" href="'.$url.'">'.$value.'</a>';
203
                    }
204
205
                    if ($resourceNode->isResourceFileAVideo()) {
206
                        $url = $router->generate(
207
                            'chamilo_core_resource_view',
208
                            $myParams
209
                        );
210
211
                        return '
212
                        <video width="640" height="320" controls id="video'.$id.'" controls preload="metadata" style="display:none;">
213
                            <source src="'.$url.'" type="video/mp4">
214
                            Your browser doesn\'t support HTML5 video tag.
215
                        </video>
216
                        '.$icon.' <a data-fancybox="gallery"  data-width="640" data-height="360" href="#video'.$id.'">'.$value.'</a>';
217
                    }
218
219
                    $url = $router->generate(
220
                        'chamilo_core_resource_preview',
221
                        $myParams
222
                    );
223
224
                    return $icon.'<a data-fancybox="gallery" data-type="iframe" data-src="'.$url.'" href="javascript:;" >'.$value.'</a>';
225
                } else {
226
                    $url = $router->generate(
227
                        'chamilo_core_resource_list',
228
                        $myParams
229
                    );
230
231
                    return $icon.'<a href="'.$url.'">'.$value.'</a>';
232
                }
233
            }
234
        );
235
236
        if ($grid->hasColumn('filetype')) {
237
            $grid->getColumn('filetype')->manipulateRenderCell(
238
                function ($value, Row $row, $router) {
0 ignored issues
show
Unused Code introduced by
The parameter $router is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

238
                function ($value, Row $row, /** @scrutinizer ignore-unused */ $router) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
239
                    /** @var AbstractResource $entity */
240
                    $entity = $row->getEntity();
241
                    $resourceNode = $entity->getResourceNode();
242
243
                    if ($resourceNode->hasResourceFile()) {
244
                        $file = $resourceNode->getResourceFile();
245
246
                        return $file->getMimeType();
247
                    }
248
249
                    return $this->trans('Folder');
250
                }
251
            );
252
        }
253
254
        if ($grid->hasColumn('iid')) {
255
            $grid->setHiddenColumns(['iid']);
256
        }
257
258
        // Delete mass action.
259
        if ($this->isGranted(ResourceNodeVoter::DELETE, $parentNode)) {
260
            $deleteMassAction = new MassAction(
261
                'Delete',
262
                'ChamiloCoreBundle:Resource:deleteMass',
263
                true,
264
                $routeParams
265
            );
266
            $grid->addMassAction($deleteMassAction);
267
        }
268
269
        // Info action.
270
        $myRowAction = new RowAction(
271
            $this->trans('Info'),
272
            'chamilo_core_resource_info',
273
            false,
274
            '_self',
275
            [
276
                'class' => 'btn btn-secondary info_action',
277
                'icon' => 'fa-info-circle',
278
                'iframe' => false,
279
            ]
280
        );
281
282
        $setNodeParameters = function (RowAction $action, Row $row) use ($routeParams) {
283
            $id = $row->getEntity()->getResourceNode()->getId();
284
            $routeParams['id'] = $id;
285
            $action->setRouteParameters($routeParams);
286
            $attributes = $action->getAttributes();
287
            $attributes['data-action'] = $action->getRoute();
288
            $attributes['data-action-id'] = $action->getRoute().'_'.$id;
289
            $attributes['data-node-id'] = $id;
290
291
            $action->setAttributes($attributes);
292
293
            return $action;
294
        };
295
296
        $myRowAction->addManipulateRender($setNodeParameters);
297
        $grid->addRowAction($myRowAction);
298
299
        // Download action
300
        $myRowAction = new RowAction(
301
            $this->trans('Download'),
302
            'chamilo_core_resource_download',
303
            false,
304
            '_self',
305
            [
306
                'class' => 'btn btn-secondary download_action',
307
                'icon' => 'fa-download',
308
            ]
309
        );
310
311
        $setNodeDownloadParameters = function (RowAction $action, Row $row) use ($routeParams) {
312
            $id = $row->getEntity()->getResourceNode()->getId();
313
            $routeParams['id'] = $id;
314
            $action->setRouteParameters($routeParams);
315
            $attributes = $action->getAttributes();
316
            $action->setAttributes($attributes);
317
318
            return $action;
319
        };
320
        $myRowAction->addManipulateRender($setNodeDownloadParameters);
321
        $grid->addRowAction($myRowAction);
322
323
        // Set EDIT/DELETE
324
        $setNodeParameters = function (RowAction $action, Row $row) use ($routeParams) {
325
            $id = $row->getEntity()->getResourceNode()->getId();
326
327
            $allowedEdit = $this->isGranted(ResourceNodeVoter::EDIT, $row->getEntity()->getResourceNode());
328
329
            if (false === $allowedEdit) {
330
                return null;
331
            }
332
333
            $routeParams['id'] = $id;
334
335
            $action->setRouteParameters($routeParams);
336
            $attributes = $action->getAttributes();
337
            //$attributes['data-action'] = $action->getRoute();
338
            //$attributes['data-action-id'] = $action->getRoute().'_'.$id;
339
            //$attributes['data-node-id'] = $id;
340
            $action->setAttributes($attributes);
341
342
            return $action;
343
        };
344
345
        if ($this->isGranted(ResourceNodeVoter::EDIT, $parentNode)) {
346
            // Enable/Disable
347
            $myRowAction = new RowAction(
348
                '',
349
                'chamilo_core_resource_change_visibility',
350
                false,
351
                '_self'
352
            );
353
354
            $setVisibleParameters = function (RowAction $action, Row $row) use ($routeParams) {
355
                /** @var AbstractResource $resource */
356
                $resource = $row->getEntity();
357
                $allowedEdit = $this->isGranted(ResourceNodeVoter::EDIT, $resource->getResourceNode());
358
359
                if (false === $allowedEdit) {
360
                    return null;
361
                }
362
363
                $id = $resource->getResourceNode()->getId();
364
365
                $icon = 'fa-eye-slash';
366
                if ($this->hasCourse()) {
367
                    $link = $resource->getCourseSessionResourceLink($this->getCourse(), $this->getSession());
368
                } else {
369
                    $link = $resource->getFirstResourceLink();
370
                }
371
372
                if (null === $link) {
373
                    return null;
374
                }
375
                if (ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
376
                    $icon = 'fa-eye';
377
                }
378
                $routeParams['id'] = $id;
379
                $action->setRouteParameters($routeParams);
380
                $attributes = [
381
                    'class' => 'btn btn-secondary change_visibility',
382
                    'data-id' => $id,
383
                    'icon' => $icon,
384
                ];
385
                $action->setAttributes($attributes);
386
387
                return $action;
388
            };
389
390
            $myRowAction->addManipulateRender($setVisibleParameters);
391
            $grid->addRowAction($myRowAction);
392
393
            if ($settings->isAllowResourceEdit()) {
394
                // Edit action.
395
                $myRowAction = new RowAction(
396
                    $this->trans('Edit'),
397
                    'chamilo_core_resource_edit',
398
                    false,
399
                    '_self',
400
                    ['class' => 'btn btn-secondary', 'icon' => 'fa fa-pen']
401
                );
402
                $myRowAction->addManipulateRender($setNodeParameters);
403
                $grid->addRowAction($myRowAction);
404
            }
405
406
            // More action.
407
            /*$myRowAction = new RowAction(
408
                $this->trans('More'),
409
                'chamilo_core_resource_preview',
410
                false,
411
                '_self',
412
                ['class' => 'btn btn-secondary edit_resource', 'icon' => 'fa fa-ellipsis-h']
413
            );
414
415
            $myRowAction->addManipulateRender($setNodeParameters);
416
            $grid->addRowAction($myRowAction);*/
417
418
            // Delete action.
419
            $myRowAction = new RowAction(
420
                $this->trans('Delete'),
421
                'chamilo_core_resource_delete',
422
                true,
423
                '_self',
424
                [
425
                    'class' => 'btn btn-danger',
426
                    //'data_hidden' => true,
427
                ]
428
            );
429
            $myRowAction->addManipulateRender($setNodeParameters);
430
            $grid->addRowAction($myRowAction);
431
        }
432
433
        /*$grid->addExport(new CSVExport($this->trans('CSV export'), 'export', ['course' => $courseIdentifier]));
434
        $grid->addExport(
435
            new ExcelExport(
436
                $this->trans('Excel export'),
437
                'export',
438
                ['course' => $courseIdentifier]
439
            )
440
        );*/
441
442
        return $grid;
443
    }
444
445
    /**
446
     * @Route("/{tool}/{type}/{id}/new_folder", methods={"GET", "POST"}, name="chamilo_core_resource_new_folder")
447
     */
448
    public function newFolderAction(Request $request): Response
449
    {
450
        $this->setBreadCrumb($request);
451
452
        return $this->createResource($request, 'folder');
453
    }
454
455
    /**
456
     * @Route("/{tool}/{type}/{id}/new", methods={"GET", "POST"}, name="chamilo_core_resource_new")
457
     */
458
    public function newAction(Request $request): Response
459
    {
460
        $this->setBreadCrumb($request);
461
462
        return $this->createResource($request, 'file');
463
    }
464
465
    /**
466
     * @Route("/{tool}/{type}/{id}/disk_space", methods={"GET", "POST"}, name="chamilo_core_resource_disk_space")
467
     */
468
    public function diskSpaceAction(Request $request): Response
469
    {
470
        $this->setBreadCrumb($request);
471
472
        $nodeId = $request->get('id');
473
474
        $repository = $this->getRepositoryFromRequest($request);
475
476
        /** @var ResourceNode $resourceNode */
477
        $resourceNode = $repository->getResourceNodeRepository()->find($nodeId);
478
        $size = $repository->getResourceNodeRepository()->getSize($resourceNode, $repository->getResourceType());
479
480
        $this->denyAccessUnlessGranted(
481
            ResourceNodeVoter::VIEW,
482
            $resourceNode,
483
            $this->trans('Unauthorised access to resource')
484
        );
485
486
        return $this->render(
487
            '@ChamiloTheme/Resource/disk_space.html.twig',
488
            [
489
                'resourceNode' => $resourceNode,
490
                'size' => $size,
491
            ]
492
        );
493
    }
494
495
    /**
496
     * @Route("/{tool}/{type}/{id}/edit", methods={"GET", "POST"})
497
     */
498
    public function editAction(Request $request, IllustrationRepository $illustrationRepository): Response
499
    {
500
        $resourceNodeId = $request->get('id');
501
502
        $this->setBreadCrumb($request);
503
        $repository = $this->getRepositoryFromRequest($request);
504
        $resource = $repository->getResourceFromResourceNode($resourceNodeId);
505
        $resourceNode = $resource->getResourceNode();
506
507
        $this->denyAccessUnlessGranted(
508
            ResourceNodeVoter::EDIT,
509
            $resourceNode,
510
            $this->trans('Unauthorised access to resource')
511
        );
512
513
        $resourceNodeParentId = $resourceNode->getId();
514
515
        $routeParams = $this->getResourceParams($request);
516
        $routeParams['id'] = $resourceNodeParentId;
517
518
        $form = $repository->getForm($this->container->get('form.factory'), $resource);
519
520
        if ($resourceNode->hasEditableContent()) {
521
            $form->add(
522
                'content',
523
                CKEditorType::class,
524
                [
525
                    'mapped' => false,
526
                    'config' => [
527
                        'filebrowserImageBrowseRoute' => 'resources_filemanager',
528
                        'filebrowserImageBrowseRouteParameters' => $routeParams,
529
                    ],
530
                ]
531
            );
532
            $content = $repository->getResourceNodeFileContent($resourceNode);
533
            $form->get('content')->setData($content);
534
        }
535
536
        $form->handleRequest($request);
537
538
        if ($form->isSubmitted() && $form->isValid()) {
539
            /** @var AbstractResource $newResource */
540
            $newResource = $form->getData();
541
542
            if ($form->has('content')) {
543
                $data = $form->get('content')->getData();
544
                $repository->updateResourceFileContent($newResource, $data);
545
            }
546
547
            //$newResource->setTitle($form->get('title')->getData()); // already set in $form->getData()
548
            $repository->updateNodeForResource($newResource);
549
550
            if ($form->has('illustration')) {
551
                $illustration = $form->get('illustration')->getData();
552
                if ($illustration) {
553
                    $file = $illustrationRepository->addIllustration($newResource, $this->getUser(), $illustration);
554
                    $em = $illustrationRepository->getEntityManager();
555
                    $em->persist($file);
556
                    $em->flush();
557
                }
558
            }
559
560
            $this->addFlash('success', $this->trans('Updated'));
561
562
            //if ($newResource->getResourceNode()->hasResourceFile()) {
563
            $resourceNodeParentId = $newResource->getResourceNode()->getParent()->getId();
564
            //}
565
            $routeParams['id'] = $resourceNodeParentId;
566
567
            return $this->redirectToRoute('chamilo_core_resource_list', $routeParams);
568
        }
569
570
        return $this->render(
571
            '@ChamiloTheme/Resource/edit.html.twig',
572
            [
573
                'form' => $form->createView(),
574
                'parent' => $resourceNodeParentId,
575
            ]
576
        );
577
    }
578
579
    /**
580
     * Shows a resource information.
581
     *
582
     * @Route("/{tool}/{type}/{id}/info", methods={"GET"}, name="chamilo_core_resource_info")
583
     */
584
    public function infoAction(Request $request, IllustrationRepository $illustrationRepository): Response
585
    {
586
        $this->setBreadCrumb($request);
587
        $nodeId = $request->get('id');
588
589
        $repository = $this->getRepositoryFromRequest($request);
590
591
        /** @var AbstractResource $resource */
592
        $resource = $repository->getResourceFromResourceNode($nodeId);
593
594
        if (null === $resource) {
595
            throw new NotFoundHttpException();
596
        }
597
598
        $resourceNode = $resource->getResourceNode();
599
600
        if (null === $resourceNode) {
601
            throw new NotFoundHttpException();
602
        }
603
604
        $this->denyAccessUnlessGranted(
605
            ResourceNodeVoter::VIEW,
606
            $resourceNode,
607
            $this->trans('Unauthorised access to resource')
608
        );
609
610
        $tool = $request->get('tool');
611
        $type = $request->get('type');
612
613
        $illustration = $illustrationRepository->getIllustrationUrlFromNode($resource->getResourceNode());
614
615
        $params = [
616
            'resource' => $resource,
617
            'illustration' => $illustration,
618
            'tool' => $tool,
619
            'type' => $type,
620
        ];
621
622
        return $this->render('@ChamiloTheme/Resource/info.html.twig', $params);
623
    }
624
625
    /**
626
     * Preview a file. Mostly used when using a modal.
627
     *
628
     * @Route("/{tool}/{type}/{id}/preview", methods={"GET"}, name="chamilo_core_resource_preview")
629
     */
630
    public function previewAction(Request $request): Response
631
    {
632
        $this->setBreadCrumb($request);
633
        $nodeId = $request->get('id');
634
635
        $repository = $this->getRepositoryFromRequest($request);
636
637
        /** @var AbstractResource $resource */
638
        $resource = $repository->getResourceFromResourceNode($nodeId);
639
640
        if (null === $resource) {
641
            throw new NotFoundHttpException();
642
        }
643
644
        $resourceNode = $resource->getResourceNode();
645
646
        if (null === $resourceNode) {
647
            throw new NotFoundHttpException();
648
        }
649
650
        $this->denyAccessUnlessGranted(
651
            ResourceNodeVoter::VIEW,
652
            $resourceNode,
653
            $this->trans('Unauthorised access to resource')
654
        );
655
656
        $tool = $request->get('tool');
657
        $type = $request->get('type');
658
659
        $params = [
660
            'resource' => $resource,
661
            'tool' => $tool,
662
            'type' => $type,
663
        ];
664
665
        return $this->render('@ChamiloTheme/Resource/preview.html.twig', $params);
666
    }
667
668
    /**
669
     * @Route("/{tool}/{type}/{id}/change_visibility", name="chamilo_core_resource_change_visibility")
670
     */
671
    public function changeVisibilityAction(Request $request): Response
672
    {
673
        $id = $request->get('id');
674
675
        $repository = $this->getRepositoryFromRequest($request);
676
677
        /** @var AbstractResource $resource */
678
        $resource = $repository->getResourceFromResourceNode($id);
679
680
        if (null === $resource) {
681
            throw new NotFoundHttpException();
682
        }
683
684
        $resourceNode = $resource->getResourceNode();
685
686
        $this->denyAccessUnlessGranted(
687
            ResourceNodeVoter::EDIT,
688
            $resourceNode,
689
            $this->trans('Unauthorised access to resource')
690
        );
691
692
        /** @var ResourceLink $link */
693
        if ($this->hasCourse()) {
694
            $link = $resource->getCourseSessionResourceLink($this->getCourse(), $this->getSession());
695
        } else {
696
            $link = $resource->getFirstResourceLink();
697
        }
698
699
        $icon = 'fa-eye';
700
        // Use repository to change settings easily.
701
        if ($link && ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
702
            $repository->setVisibilityDraft($resource);
703
            $icon = 'fa-eye-slash';
704
        } else {
705
            $repository->setVisibilityPublished($resource);
706
        }
707
708
        $result = ['icon' => $icon];
709
710
        return new JsonResponse($result);
711
    }
712
713
    /**
714
     * @Route("/{tool}/{type}/{id}", name="chamilo_core_resource_delete")
715
     */
716
    public function deleteAction(Request $request): Response
717
    {
718
        $em = $this->getDoctrine()->getManager();
719
720
        $id = $request->get('id');
721
        $resourceNode = $this->getDoctrine()->getRepository('ChamiloCoreBundle:Resource\ResourceNode')->find($id);
722
        $parentId = $resourceNode->getParent()->getId();
723
724
        if (null === $resourceNode) {
725
            throw new NotFoundHttpException();
726
        }
727
728
        $this->denyAccessUnlessGranted(
729
            ResourceNodeVoter::DELETE,
730
            $resourceNode,
731
            $this->trans('Unauthorised access to resource')
732
        );
733
734
        $em->remove($resourceNode);
735
        $this->addFlash('success', $this->trans('Deleted'));
736
        $em->flush();
737
738
        $routeParams = $this->getResourceParams($request);
739
        $routeParams['id'] = $parentId;
740
741
        return $this->redirectToRoute(
742
            'chamilo_core_resource_list',
743
            $routeParams
744
        );
745
    }
746
747
    /**
748
     * @Route("/{tool}/{type}/{id}", methods={"DELETE"}, name="chamilo_core_resource_delete_mass")
749
     */
750
    public function deleteMassAction($primaryKeys, $allPrimaryKeys, Request $request): Response
751
    {
752
        $em = $this->getDoctrine()->getManager();
753
        $repo = $this->getRepositoryFromRequest($request);
754
755
        $parentId = 0;
756
        foreach ($primaryKeys as $id) {
757
            $resource = $repo->find($id);
758
            $resourceNode = $resource->getResourceNode();
759
760
            if (null === $resourceNode) {
761
                continue;
762
            }
763
764
            $this->denyAccessUnlessGranted(
765
                ResourceNodeVoter::DELETE,
766
                $resourceNode,
767
                $this->trans('Unauthorised access to resource')
768
            );
769
770
            $parentId = $resourceNode->getParent()->getId();
771
            $em->remove($resource);
772
        }
773
774
        $this->addFlash('success', $this->trans('Deleted'));
775
        $em->flush();
776
777
        $routeParams = $this->getResourceParams($request);
778
        $routeParams['id'] = $parentId;
779
780
        return $this->redirectToRoute('chamilo_core_resource_list', $routeParams);
781
    }
782
783
    /**
784
     * Shows the associated resource file.
785
     *
786
     * @Route("/{tool}/{type}/{id}/view", methods={"GET"}, name="chamilo_core_resource_view")
787
     */
788
    public function viewAction(Request $request, Glide $glide): Response
789
    {
790
        $id = $request->get('id');
791
        $filter = $request->get('filter');
792
        $mode = $request->get('mode');
793
        $em = $this->getDoctrine();
794
        $resourceNode = $em->getRepository('ChamiloCoreBundle:Resource\ResourceNode')->find($id);
795
796
        if (null === $resourceNode) {
797
            throw new FileNotFoundException('Resource not found');
798
        }
799
800
        return $this->showFile($request, $resourceNode, $mode, $glide, $filter);
801
    }
802
803
    /**
804
     * Gets a document when calling route resources_document_get_file.
805
1     *
806
     * @deprecated
807
     *
808
     * @throws \League\Flysystem\FileNotFoundException
809
     */
810
    public function getDocumentAction(Request $request, Glide $glide): Response
811
    {
812
        /*$file = $request->get('file');
813
        $mode = $request->get('mode');
814
815
        // see list of filters in config/services.yaml
816
        $filter = $request->get('filter');
817
        $mode = !empty($mode) ? $mode : 'show';
818
819
        $repository = $this->getRepository('document', 'files');
820
        $nodeRepository = $repository->getResourceNodeRepository();
821
822
        $title = basename($file);
823
        // @todo improve criteria to avoid giving the wrong file.
824
        $criteria = ['slug' => $title];
825
826
        $resourceNode = $nodeRepository->findOneBy($criteria);
827
828
        if (null === $resourceNode) {
829
            throw new NotFoundHttpException();
830
        }
831
832
        return $this->showFile($request, $resourceNode, $mode, $glide,$filter);*/
833
    }
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\Response. 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...
834
835
    /**
836
     * @Route("/{tool}/{type}/{id}/download", methods={"GET"}, name="chamilo_core_resource_download")
837
     */
838
    public function downloadAction(Request $request)
839
    {
840
        $resourceNodeId = (int) $request->get('id');
841
        $courseNode = $this->getCourse()->getResourceNode();
842
843
        $repo = $this->getRepositoryFromRequest($request);
844
845
        if (empty($resourceNodeId)) {
846
            $resourceNode = $courseNode;
847
        } else {
848
            $resourceNode = $repo->getResourceNodeRepository()->find($resourceNodeId);
849
        }
850
851
        if (null === $resourceNode) {
852
            throw new NotFoundHttpException();
853
        }
854
855
        $this->denyAccessUnlessGranted(
856
            ResourceNodeVoter::VIEW,
857
            $resourceNode,
858
            $this->trans('Unauthorised access to resource')
859
        );
860
861
        // If resource node has a file just download it. Don't download the children.
862
        if ($resourceNode->hasResourceFile()) {
863
            // Redirect to download single file.
864
            return $this->showFile($request, $resourceNode, 'download');
865
        }
866
867
        $zipName = $resourceNode->getSlug().'.zip';
868
        $rootNodePath = $resourceNode->getPathForDisplay();
869
870
        $resourceNodeRepo = $repo->getResourceNodeRepository();
871
872
        $criteria = Criteria::create()
873
            ->where(Criteria::expr()->neq('resourceFile', null)) // must have a file
874
           // ->andWhere(Criteria::expr()->eq('resourceType', $type))
875
        ;
876
877
        /** @var ArrayCollection|ResourceNode[] $children */
878
        /** @var QueryBuilder $children */
879
        $qb = $resourceNodeRepo->getChildrenQueryBuilder($resourceNode);
880
        $qb->addCriteria($criteria);
881
        $children = $qb->getQuery()->getResult();
882
883
        $response = new StreamedResponse(function () use ($rootNodePath, $zipName, $children, $repo) {
884
            // Define suitable options for ZipStream Archive.
885
            $options = new Archive();
886
            $options->setContentType('application/octet-stream');
887
            //initialise zipstream with output zip filename and options.
888
            $zip = new ZipStream($zipName, $options);
889
890
            /** @var ResourceNode $node */
891
            foreach ($children as $node) {
892
                //$resourceFile = $node->getResourceFile();
893
                //$systemName = $resourceFile->getFile()->getPathname();
894
                $stream = $repo->getResourceNodeFileStream($node);
895
                //error_log($node->getPathForDisplay());
896
                $fileToDisplay = str_replace($rootNodePath, '', $node->getPathForDisplay());
897
                $zip->addFileFromStream($fileToDisplay, $stream);
898
            }
899
            $zip->finish();
900
        });
901
902
        $disposition = $response->headers->makeDisposition(
903
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
904
            Transliterator::transliterate($zipName)
905
        );
906
        $response->headers->set('Content-Disposition', $disposition);
907
        $response->headers->set('Content-Type', 'application/octet-stream');
908
909
        return $response;
910
    }
911
912
    /**
913
     * Upload form.
914
     *
915
     * @Route("/{tool}/{type}/{id}/upload", name="chamilo_core_resource_upload", methods={"GET", "POST"},
916
     *                                      options={"expose"=true})
917
     */
918
    public function uploadAction(Request $request, $tool, $type, $id): Response
919
    {
920
        $repository = $this->getRepositoryFromRequest($request);
921
        $resourceNode = $repository->getResourceNodeRepository()->find($id);
922
923
        $this->denyAccessUnlessGranted(
924
            ResourceNodeVoter::EDIT,
925
            $resourceNode,
926
            $this->trans('Unauthorised access to resource')
927
        );
928
929
        $this->setBreadCrumb($request);
930
931
        $routeParams = $this->getResourceParams($request);
932
        $routeParams['tool'] = $tool;
933
        $routeParams['type'] = $type;
934
        $routeParams['id'] = $id;
935
936
        return $this->render(
937
            '@ChamiloTheme/Resource/upload.html.twig',
938
            $routeParams
939
        );
940
    }
941
942
    public function setBreadCrumb(Request $request)
943
    {
944
        $tool = $request->get('tool');
945
        $type = $request->get('type');
946
        $resourceNodeId = $request->get('id');
947
948
        $routeParams = $this->getResourceParams($request);
949
950
        if (!empty($resourceNodeId)) {
951
            $breadcrumb = $this->getBreadCrumb();
952
            $toolParams = $routeParams;
953
            $toolParams['id'] = null;
954
955
            // Root tool link
956
            $breadcrumb->addChild(
957
                $this->trans($tool),
958
                [
959
                    'uri' => $this->generateUrl(
960
                        'chamilo_core_resource_index',
961
                        $toolParams
962
                    ),
963
                ]
964
            );
965
966
            $repo = $this->getRepositoryFromRequest($request);
967
968
            /** @var AbstractResource $parent */
969
            $originalResource = $repo->findOneBy(['resourceNode' => $resourceNodeId]);
970
            if (null === $originalResource) {
971
                return;
972
            }
973
            $parent = $originalParent = $originalResource->getResourceNode();
974
975
            $parentList = [];
976
            while (null !== $parent) {
977
                if ($type !== $parent->getResourceType()->getName()) {
978
                    break;
979
                }
980
                $parent = $parent->getParent();
981
                if ($parent) {
982
                    $resource = $repo->findOneBy(['resourceNode' => $parent->getId()]);
983
                    if ($resource) {
984
                        $parentList[] = $resource;
985
                    }
986
                }
987
            }
988
989
            $parentList = array_reverse($parentList);
990
            /** @var AbstractResource $item */
991
            foreach ($parentList as $item) {
992
                $params = $routeParams;
993
                $params['id'] = $item->getResourceNode()->getId();
994
                $breadcrumb->addChild(
995
                    $item->getResourceName(),
996
                    [
997
                        'uri' => $this->generateUrl(
998
                            'chamilo_core_resource_list',
999
                            $params
1000
                        ),
1001
                    ]
1002
                );
1003
            }
1004
1005
            $params = $routeParams;
1006
            $params['id'] = $originalParent->getId();
1007
1008
            $breadcrumb->addChild(
1009
                $originalResource->getResourceName(),
1010
                [
1011
                    'uri' => $this->generateUrl(
1012
                        'chamilo_core_resource_list',
1013
                        $params
1014
                    ),
1015
                ]
1016
            );
1017
        }
1018
    }
1019
1020
    private function getParentResourceNode(Request $request): ResourceNode
1021
    {
1022
        $parentNodeId = $request->get('id');
1023
1024
        $parentResourceNode = null;
1025
1026
        if (empty($parentNodeId)) {
1027
            if ($this->hasCourse()) {
1028
                $parentResourceNode = $this->getCourse()->getResourceNode();
1029
            } else {
1030
                if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
1031
                    /** @var User $user */
1032
                    $parentResourceNode = $this->getUser()->getResourceNode();
1033
                }
1034
            }
1035
        } else {
1036
            $repo = $this->getDoctrine()->getRepository('ChamiloCoreBundle:Resource\ResourceNode');
1037
            $parentResourceNode = $repo->find($parentNodeId);
1038
        }
1039
1040
        if (null === $parentResourceNode) {
1041
            throw new AccessDeniedException();
1042
        }
1043
1044
        return $parentResourceNode;
1045
    }
1046
1047
    /**
1048
     * @param string $mode
1049
     * @param string $filter
1050
     *
1051
     * @return mixed|StreamedResponse
1052
     */
1053
    private function showFile(Request $request, ResourceNode $resourceNode, $mode = 'show', Glide $glide = null, $filter = '')
1054
    {
1055
        $this->denyAccessUnlessGranted(
1056
            ResourceNodeVoter::VIEW,
1057
            $resourceNode,
1058
            $this->trans('Unauthorised access to resource')
1059
        );
1060
1061
        $repo = $this->getRepositoryFromRequest($request);
1062
        $resourceFile = $resourceNode->getResourceFile();
1063
1064
        if (!$resourceFile) {
0 ignored issues
show
introduced by
$resourceFile is of type Chamilo\CoreBundle\Entity\Resource\ResourceFile, thus it always evaluated to true.
Loading history...
1065
            throw new NotFoundHttpException($this->trans('File not found for resource'));
1066
        }
1067
1068
        //$fileName = $resourceFile->getOriginalName();
1069
        $fileName = $resourceNode->getSlug();
1070
        $filePath = $resourceFile->getFile()->getPathname();
1071
        $mimeType = $resourceFile->getMimeType();
1072
1073
        switch ($mode) {
1074
            case 'download':
1075
                $forceDownload = true;
1076
1077
                break;
1078
            case 'show':
1079
            default:
1080
                $forceDownload = false;
1081
                // If it's an image then send it to Glide.
1082
                if (false !== strpos($mimeType, 'image')) {
1083
                    $server = $glide->getServer();
0 ignored issues
show
Bug introduced by
The method getServer() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1083
                    /** @scrutinizer ignore-call */ 
1084
                    $server = $glide->getServer();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1084
                    $params = $request->query->all();
1085
1086
                    // The filter overwrites the params from get
1087
                    if (!empty($filter)) {
1088
                        $params = $glide->getFilters()[$filter] ?? [];
1089
                    }
1090
1091
                    // The image was cropped manually by the user, so we force to render this version,
1092
                    // no matter other crop parameters.
1093
                    $crop = $resourceFile->getCrop();
1094
                    if (!empty($crop)) {
1095
                        $params['crop'] = $crop;
1096
                    }
1097
1098
                    return $server->getImageResponse($filePath, $params);
1099
                }
1100
1101
                break;
1102
        }
1103
1104
        $stream = $repo->getResourceNodeFileStream($resourceNode);
1105
1106
        //$stream = $this->fs->readStream($resourceNode);
1107
        $response = new StreamedResponse(function () use ($stream): void {
1108
            stream_copy_to_stream($stream, fopen('php://output', 'wb'));
0 ignored issues
show
Bug introduced by
It seems like fopen('php://output', 'wb') can also be of type false; however, parameter $dest of stream_copy_to_stream() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1108
            stream_copy_to_stream($stream, /** @scrutinizer ignore-type */ fopen('php://output', 'wb'));
Loading history...
Bug introduced by
It seems like $stream can also be of type string; however, parameter $source of stream_copy_to_stream() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1108
            stream_copy_to_stream(/** @scrutinizer ignore-type */ $stream, fopen('php://output', 'wb'));
Loading history...
1109
        });
1110
        $disposition = $response->headers->makeDisposition(
1111
            $forceDownload ? ResponseHeaderBag::DISPOSITION_ATTACHMENT : ResponseHeaderBag::DISPOSITION_INLINE,
1112
            $fileName
1113
            //Transliterator::transliterate($fileName)
1114
        );
1115
        $response->headers->set('Content-Disposition', $disposition);
1116
        $response->headers->set('Content-Type', $mimeType ?: 'application/octet-stream');
1117
1118
        return $response;
1119
    }
1120
1121
    /**
1122
     * @param string $fileType
1123
     *
1124
     * @return RedirectResponse|Response
1125
     */
1126
    private function createResource(Request $request, $fileType = 'file')
1127
    {
1128
        $resourceNodeParentId = $request->get('id');
1129
1130
        $repository = $this->getRepositoryFromRequest($request);
1131
1132
        // Default parent node is course.
1133
        $parentNode = $this->getParentResourceNode($request);
1134
        //var_dump($parentNode->getPath());
1135
1136
        $this->denyAccessUnlessGranted(
1137
            ResourceNodeVoter::CREATE,
1138
            $parentNode,
1139
            $this->trans('Unauthorised access to resource')
1140
        );
1141
1142
        $form = $repository->getForm($this->container->get('form.factory'), null);
1143
1144
        if ('file' === $fileType) {
1145
            $resourceParams = $this->getResourceParams($request);
1146
            $form->add(
1147
                'content',
1148
                CKEditorType::class,
1149
                [
1150
                    'mapped' => false,
1151
                    'config' => [
1152
                        'filebrowserImageBrowseRoute' => 'resources_filemanager',
1153
                        'filebrowserImageBrowseRouteParameters' => $resourceParams,
1154
                        'fullPage' => true,
1155
                    ],
1156
                ]
1157
            );
1158
        }
1159
1160
        $form->handleRequest($request);
1161
        if ($form->isSubmitted() && $form->isValid()) {
1162
            $em = $this->getDoctrine()->getManager();
1163
1164
            $course = $this->getCourse();
1165
            $session = $this->getSession();
1166
1167
            /** @var AbstractResource $newResource */
1168
            $newResource = $repository->saveResource($form, $course, $session, $fileType);
1169
1170
            $file = null;
1171
            if ('file' === $fileType && $newResource instanceof CDocument) {
1172
                $content = $form->get('content')->getViewData();
1173
                $newResource->setTitle($newResource->getTitle().'.html');
1174
                $fileName = $newResource->getTitle();
1175
1176
                $handle = tmpfile();
1177
                fwrite($handle, $content);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1177
                fwrite(/** @scrutinizer ignore-type */ $handle, $content);
Loading history...
1178
                $meta = stream_get_meta_data($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $stream of stream_get_meta_data() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1178
                $meta = stream_get_meta_data(/** @scrutinizer ignore-type */ $handle);
Loading history...
1179
                $file = new UploadedFile($meta['uri'], $fileName, 'text/html', null, true);
1180
                $em->persist($newResource);
1181
            }
1182
1183
            $resourceNode = $repository->createNodeForResource($newResource, $this->getUser(), $parentNode, $file);
1184
            $em->persist($resourceNode);
1185
1186
            $repository->addResourceNodeToCourse(
1187
                $resourceNode,
1188
                ResourceLink::VISIBILITY_PUBLISHED,
1189
                $course,
1190
                $session,
1191
                null
1192
            );
1193
1194
            $em->flush();
1195
1196
            // Loops all sharing options
1197
            /*foreach ($shareList as $share) {
1198
                $idList = [];
1199
                if (isset($share['search'])) {
1200
                    $idList = explode(',', $share['search']);
1201
                }
1202
1203
                $resourceRight = null;
1204
                if (isset($share['mask'])) {
1205
                    $resourceRight = new ResourceRight();
1206
                    $resourceRight
1207
                        ->setMask($share['mask'])
1208
                        ->setRole($share['role'])
1209
                    ;
1210
                }
1211
1212
                // Build links
1213
                switch ($share['sharing']) {
1214
                    case 'everyone':
1215
                        $repository->addResourceToEveryone(
1216
                            $resourceNode,
1217
                            $resourceRight
1218
                        );
1219
                        break;
1220
                    case 'course':
1221
                        $repository->addResourceToCourse(
1222
                            $resourceNode,
1223
                            $course,
1224
                            $resourceRight
1225
                        );
1226
                        break;
1227
                    case 'session':
1228
                        $repository->addResourceToSession(
1229
                            $resourceNode,
1230
                            $course,
1231
                            $session,
1232
                            $resourceRight
1233
                        );
1234
                        break;
1235
                    case 'user':
1236
                        // Only for me
1237
                        if (isset($share['only_me'])) {
1238
                            $repository->addResourceOnlyToMe($resourceNode);
1239
                        } else {
1240
                            // To other users
1241
                            $repository->addResourceToUserList($resourceNode, $idList);
1242
                        }
1243
                        break;
1244
                    case 'group':
1245
                        // @todo
1246
                        break;
1247
                }*/
1248
            //}
1249
            $em->flush();
1250
            $this->addFlash('success', $this->trans('Saved'));
1251
1252
            $params = $this->getResourceParams($request);
1253
            $params['id'] = $resourceNodeParentId;
1254
1255
            return $this->redirectToRoute(
1256
                'chamilo_core_resource_list',
1257
                $params
1258
            );
1259
        }
1260
1261
        switch ($fileType) {
1262
            case 'folder':
1263
                $template = '@ChamiloTheme/Resource/new_folder.html.twig';
1264
1265
                break;
1266
            case 'file':
1267
                $template = '@ChamiloTheme/Resource/new.html.twig';
1268
1269
                break;
1270
        }
1271
1272
        return $this->render(
1273
            $template,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $template does not seem to be defined for all execution paths leading up to this point.
Loading history...
1274
            [
1275
                'form' => $form->createView(),
1276
                'parent' => $resourceNodeParentId,
1277
                'file_type' => $fileType,
1278
            ]
1279
        );
1280
    }
1281
}
1282