Passed
Push — master ( 262c23...1cc54c )
by Angel Fernando Quiroz
09:24
created

DownloadSelectedDocumentsAction::__invoke()   C

Complexity

Conditions 13
Paths 8

Size

Total Lines 87
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 48
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 87
rs 6.6166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Controller\Api;
8
9
use Chamilo\CoreBundle\Entity\ResourceNode;
10
use Chamilo\CoreBundle\Exception\NotAllowedException;
11
use Chamilo\CoreBundle\Helpers\CidReqHelper;
12
use Chamilo\CoreBundle\Helpers\ResourceFileHelper;
13
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
14
use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
15
use Chamilo\CoreBundle\Security\Authorization\Voter\GroupVoter;
16
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceFileVoter;
17
use Chamilo\CoreBundle\Security\Authorization\Voter\SessionVoter;
18
use Chamilo\CoreBundle\Traits\ControllerTrait;
19
use Chamilo\CourseBundle\Repository\CDocumentRepository;
20
use Exception;
21
use Symfony\Bundle\SecurityBundle\Security;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
25
use Symfony\Component\HttpFoundation\StreamedResponse;
26
use Symfony\Component\HttpKernel\KernelInterface;
27
use ZipStream\Option\Archive;
28
use ZipStream\ZipStream;
29
30
class DownloadSelectedDocumentsAction
31
{
32
    use ControllerTrait;
0 ignored issues
show
Bug introduced by
The trait Chamilo\CoreBundle\Traits\ControllerTrait requires the property $headers which is not provided by Chamilo\CoreBundle\Contr...SelectedDocumentsAction.
Loading history...
33
34
    public const CONTENT_TYPE = 'application/zip';
35
36
    public function __construct(
37
        private readonly KernelInterface $kernel,
38
        private readonly ResourceNodeRepository $resourceNodeRepository,
39
        private readonly CDocumentRepository $documentRepo,
40
        private readonly ResourceFileHelper $resourceFileHelper,
41
        private readonly Security $security,
42
        private readonly CidReqHelper $cidReqHelper,
43
    ) {}
44
45
    /**
46
     * @throws Exception
47
     */
48
    public function __invoke(Request $request): Response
49
    {
50
        ini_set('max_execution_time', '300');
51
        ini_set('memory_limit', '512M');
52
53
        $data = json_decode($request->getContent(), true);
54
        $documentIds = $data['ids'] ?? [];
55
56
        if (empty($documentIds)) {
57
            return new Response('No items selected.', Response::HTTP_BAD_REQUEST);
58
        }
59
60
        if ($this->security->isGranted('ROLE_ADMIN')) {
61
            $documents = $this->documentRepo->findBy(['iid' => $documentIds]);
62
        } else {
63
            $course = $this->cidReqHelper->getCourseEntity();
64
            $session = $this->cidReqHelper->getSessionEntity();
65
            $group = $this->cidReqHelper->getGroupEntity();
66
67
            if (!$course || !$this->security->isGranted(CourseVoter::VIEW, $course)) {
68
                throw new NotAllowedException("You're not allowed in this course");
69
            }
70
71
            if ($session && !$this->security->isGranted(SessionVoter::VIEW, $session)) {
72
                throw new NotAllowedException("You're not allowed in this session");
73
            }
74
75
            if ($group && !$this->security->isGranted(GroupVoter::VIEW, $group)) {
76
                throw new NotAllowedException("You're not allowed in this group");
77
            }
78
79
            $qb = $this->documentRepo->getResourcesByCourse($course, $session, $group);
80
            $qb->andWhere(
81
                $qb->expr()->in('resource.iid', $documentIds)
82
            );
83
84
            $documents = $qb->getQuery()->getResult();
85
        }
86
87
        if (empty($documents)) {
88
            return new Response('No documents found.', Response::HTTP_NOT_FOUND);
89
        }
90
91
        $zipName = 'selected_documents.zip';
92
93
        $response = new StreamedResponse(
94
            function () use ($documents, $zipName): void {
95
                // Creates a ZIP file containing the specified documents.
96
                $options = new Archive();
97
                $options->setSendHttpHeaders(false);
98
                $options->setContentType(self::CONTENT_TYPE);
99
100
                $zip = new ZipStream($zipName, $options);
101
102
                foreach ($documents as $document) {
103
                    $node = $document->getResourceNode();
104
105
                    if (!$node) {
106
                        error_log('ResourceNode not found for document ID: '.$document->getIid());
107
108
                        continue;
109
                    }
110
111
                    $this->addNodeToZip($zip, $node);
112
                }
113
114
                if (0 === count($zip->files)) {
115
                    $zip->addFile('.empty', '');
116
                }
117
118
                $zip->finish();
119
            },
120
            Response::HTTP_CREATED
121
        );
122
123
        // Convert the file name to ASCII using iconv
124
        $zipName = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $zipName);
125
126
        $disposition = $response->headers->makeDisposition(
127
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
128
            $zipName
129
        );
130
131
        $response->headers->set('Content-Disposition', $disposition);
132
        $response->headers->set('Content-Type', self::CONTENT_TYPE);
133
134
        return $response;
135
    }
136
137
    /**
138
     * Adds a resource node and its files or children to the ZIP archive.
139
     */
140
    private function addNodeToZip(ZipStream $zip, ResourceNode $node, string $currentPath = ''): void
141
    {
142
        if ($node->getChildren()->count() > 0) {
143
            $relativePath = $currentPath.$node->getTitle().'/';
144
145
            $zip->addFile($relativePath, '');
146
147
            foreach ($node->getChildren() as $childNode) {
148
                $this->addNodeToZip($zip, $childNode, $relativePath);
149
            }
150
151
            return;
152
        }
153
154
        $resourceFile = $this->resourceFileHelper->resolveResourceFileByAccessUrl($node);
155
156
        if ($resourceFile) {
157
            if (!$this->security->isGranted(ResourceFileVoter::DOWNLOAD, $resourceFile)) {
158
                return;
159
            }
160
161
            $fileName = $currentPath.$resourceFile->getOriginalName();
162
            $stream = $this->resourceNodeRepository->getResourceNodeFileStream($node, $resourceFile);
163
164
            $zip->addFileFromStream($fileName, $stream);
165
        }
166
    }
167
}
168