Passed
Push — master ( 0935e9...e918a0 )
by
unknown
19:43 queued 10:06
created

normalizeResourceLinks()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
nc 4
nop 1
dl 0
loc 22
rs 9.8666
c 1
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/* For licensing terms, see /license.txt */
5
6
namespace Chamilo\CoreBundle\Controller\Api;
7
8
use Chamilo\CoreBundle\Entity\Course;
9
use Chamilo\CoreBundle\Entity\Session;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CGlossary;
12
use Chamilo\CourseBundle\Repository\CGlossaryRepository;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Mpdf\Mpdf;
15
use Symfony\Component\HttpFoundation\File\UploadedFile;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpKernel\KernelInterface;
18
use Symfony\Contracts\Translation\TranslatorInterface;
19
20
final class ExportGlossaryToDocumentsAction
21
{
22
    public function __invoke(
23
        Request $request,
24
        CGlossaryRepository $repo,
25
        EntityManagerInterface $em,
26
        KernelInterface $kernel,
27
        TranslatorInterface $translator
28
    ): string {
29
        $data = json_decode((string) $request->getContent(), true) ?: [];
30
31
        $parentResourceNodeId = (int) ($data['parentResourceNodeId'] ?? 0);
32
33
        // The frontend may send resourceLinkList as a JSON string or as an array.
34
        $resourceLinkListRaw = $data['resourceLinkList'] ?? [];
35
        if (is_string($resourceLinkListRaw)) {
36
            $resourceLinkListRaw = json_decode($resourceLinkListRaw, true) ?: [];
37
        }
38
39
        $resourceLinkList = $this->normalizeResourceLinks($resourceLinkListRaw);
40
41
        // Resolve context from resource links.
42
        $cid = (int) ($resourceLinkList[0]['cid'] ?? 0);
43
        $sid = (int) ($resourceLinkList[0]['sid'] ?? 0);
44
45
        $course = null;
46
        $session = null;
47
48
        if ($cid > 0) {
49
            $course = $em->getRepository(Course::class)->find($cid);
50
        }
51
        if ($sid > 0) {
52
            $session = $em->getRepository(Session::class)->find($sid);
53
        }
54
55
        // Important: export only glossary items for the current course/session context.
56
        if ($course) {
57
            $qb = $repo->getResourcesByCourse($course, $session, null, null, true, true);
58
59
            // The alias used by getResourcesByCourse() is "resource" (as seen in GetGlossaryCollectionController).
60
            $qb->orderBy('resource.title', 'ASC');
61
62
            /** @var CGlossary[] $glossaryItems */
63
            $glossaryItems = $qb->getQuery()->getResult();
64
        } else {
65
            // Fallback to keep behavior resilient if cid is missing.
66
            /** @var CGlossary[] $glossaryItems */
67
            $glossaryItems = $repo->findAll();
68
        }
69
70
        $exportPath = $kernel->getCacheDir();
71
        $pdfFilePath = $this->generatePdfFile($glossaryItems, $exportPath, $translator);
72
73
        if (!empty($pdfFilePath) && file_exists($pdfFilePath)) {
74
            $fileName = basename($pdfFilePath);
75
76
            // Important: mark as "test" because this file is generated on the server (not uploaded by HTTP).
77
            $uploadFile = new UploadedFile(
78
                $pdfFilePath,
79
                $fileName,
80
                'application/pdf',
81
                null,
82
                true
83
            );
84
85
            $document = new CDocument();
86
            $document->setTitle($fileName);
87
            $document->setUploadFile($uploadFile);
88
            $document->setFiletype('file');
89
90
            if ($parentResourceNodeId > 0) {
91
                $document->setParentResourceNode($parentResourceNodeId);
92
            }
93
94
            if (!empty($resourceLinkList)) {
95
                $document->setResourceLinkArray($resourceLinkList);
96
            }
97
98
            // Save the CDocument entity to the database
99
            $em->persist($document);
100
            $em->flush();
101
102
            @unlink($pdfFilePath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

102
            /** @scrutinizer ignore-unhandled */ @unlink($pdfFilePath);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
103
        }
104
105
        return $pdfFilePath;
106
    }
107
108
    /**
109
     * @param mixed $links
110
     * @return array<int, array{cid:int,sid:int,gid:int,visibility:int}>
111
     */
112
    private function normalizeResourceLinks(mixed $links): array
113
    {
114
        if (!is_array($links)) {
115
            return [];
116
        }
117
118
        $normalized = [];
119
120
        foreach ($links as $link) {
121
            if (!is_array($link)) {
122
                continue;
123
            }
124
125
            $normalized[] = [
126
                'cid' => (int) ($link['cid'] ?? 0),
127
                'sid' => (int) ($link['sid'] ?? 0),
128
                'gid' => (int) ($link['gid'] ?? 0),
129
                'visibility' => (int) ($link['visibility'] ?? 0),
130
            ];
131
        }
132
133
        return $normalized;
134
    }
135
136
    /**
137
     * @param CGlossary[] $glossaryItems
138
     */
139
    private function generatePdfFile(array $glossaryItems, string $exportPath, TranslatorInterface $translator): string
140
    {
141
        $date = date('Y-m-d');
142
        $suffix = bin2hex(random_bytes(4));
143
        $pdfFileName = 'glossary_'.$date.'_'.$suffix.'.pdf';
144
        $pdfFilePath = rtrim($exportPath, '/').'/'.$pdfFileName;
145
146
        $mpdf = new Mpdf();
147
148
        $html = '<h1>'.$translator->trans('Glossary').'</h1>';
149
        $html .= '<table border="1" cellpadding="6" cellspacing="0" width="100%">';
150
        $html .= '<tr><th>'.$translator->trans('Term').'</th><th>'.$translator->trans('Term definition').'</th></tr>';
151
152
        foreach ($glossaryItems as $item) {
153
            $term = htmlspecialchars((string) $item->getTitle(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
154
            $def = htmlspecialchars((string) ($item->getDescription() ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
155
156
            $html .= '<tr>';
157
            $html .= '<td>'.$term.'</td>';
158
            $html .= '<td>'.$def.'</td>';
159
            $html .= '</tr>';
160
        }
161
        $html .= '</table>';
162
163
        $mpdf->WriteHTML($html);
164
165
        $mpdf->Output($pdfFilePath, 'F');
166
167
        return $pdfFilePath;
168
    }
169
}
170