Passed
Pull Request — master (#7182)
by
unknown
10:46
created

LegalController::getExtraFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 8
rs 10
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\Legal;
10
use Chamilo\CoreBundle\Repository\LegalRepository;
11
use Doctrine\ORM\EntityManagerInterface;
12
use ExtraField;
13
use ExtraFieldValue;
14
use LegalManager;
15
use Symfony\Component\HttpFoundation\JsonResponse;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpFoundation\Response;
18
use Symfony\Component\Routing\Attribute\Route;
19
20
#[Route('/legal')]
21
class LegalController
22
{
23
    #[Route('/save', name: 'chamilo_core_legal_save', methods: ['POST'])]
24
    public function saveLegal(
25
        Request $request,
26
        EntityManagerInterface $entityManager,
27
        LegalRepository $legalRepository
28
    ): Response {
29
        $data = json_decode($request->getContent(), true);
30
31
        $languageId = (int) ($data['lang'] ?? 0);
32
        if ($languageId <= 0) {
33
            return new JsonResponse(['message' => 'Invalid language id'], Response::HTTP_BAD_REQUEST);
34
        }
35
36
        $changes = (string) ($data['changes'] ?? '');
37
        $sections = $data['sections'] ?? null;
38
39
        if (!is_array($sections)) {
40
            return new JsonResponse(['message' => 'Missing sections payload'], Response::HTTP_BAD_REQUEST);
41
        }
42
43
        // Normalize & hash incoming payload (idempotency).
44
        $normalize = static function ($v): string {
45
            $s = is_string($v) ? $v : '';
46
            $s = str_replace(["\r\n", "\r"], "\n", $s);
47
            $s = trim($s);
48
49
            return $s;
50
        };
51
52
        $incoming = [];
53
        for ($type = 0; $type <= 15; $type++) {
54
            $key = (string) $type;
55
            $incoming[$type] = $normalize($sections[$key] ?? ($sections[$type] ?? ''));
56
        }
57
        $incomingHash = hash('sha256', json_encode($incoming, JSON_UNESCAPED_UNICODE));
58
59
        $conn = $entityManager->getConnection();
60
        $conn->beginTransaction();
61
62
        try {
63
            // Generic concurrency control: lock the language row.
64
            // This prevents two concurrent saves for the same language.
65
            $conn->executeStatement(
66
                'SELECT id FROM language WHERE id = :id FOR UPDATE',
67
                ['id' => $languageId]
68
            );
69
70
            // Get latest version (inside the lock).
71
            $lastVersion = (int) $legalRepository->findLatestVersionByLanguage($languageId);
72
73
            if ($lastVersion > 0) {
74
                // Load last saved sections to compare.
75
                $rows = $conn->fetchAllAssociative(
76
                    'SELECT type, content
77
                 FROM legal
78
                 WHERE language_id = :lang AND version = :ver
79
                 ORDER BY type ASC',
80
                    ['lang' => $languageId, 'ver' => $lastVersion]
81
                );
82
83
                $existing = array_fill(0, 16, '');
84
                foreach ($rows as $r) {
85
                    $t = (int) ($r['type'] ?? -1);
86
                    if ($t >= 0 && $t <= 15) {
87
                        $existing[$t] = $normalize($r['content'] ?? '');
88
                    }
89
                }
90
                $existingHash = hash('sha256', json_encode($existing, JSON_UNESCAPED_UNICODE));
91
92
                // No changes => do NOT create a new version.
93
                if ($existingHash === $incomingHash) {
94
                    $conn->commit();
95
96
                    return new JsonResponse([
97
                        'message' => 'No changes detected',
98
                        'version' => $lastVersion,
99
                    ], Response::HTTP_OK);
100
                }
101
            }
102
103
            // Create new version only when changed.
104
            $newVersion = $lastVersion + 1;
105
            $timestamp = time();
106
107
            for ($type = 0; $type <= 15; $type++) {
108
                $content = $incoming[$type];
109
110
                $legal = new Legal();
111
                $legal->setLanguageId($languageId);
112
                $legal->setVersion($newVersion);
113
                $legal->setType($type);
114
                $legal->setDate($timestamp);
115
                $legal->setChanges($changes);
116
                $legal->setContent($content === '' ? null : $content);
117
118
                $entityManager->persist($legal);
119
            }
120
121
            $entityManager->flush();
122
            $conn->commit();
123
124
            return new JsonResponse([
125
                'message' => 'Terms saved successfully',
126
                'version' => $newVersion,
127
            ], Response::HTTP_OK);
128
        } catch (\Throwable $e) {
129
            $conn->rollBack();
130
            throw $e;
131
        }
132
    }
133
134
    #[Route('/extra-fields', name: 'chamilo_core_get_extra_fields')]
135
    public function getExtraFields(Request $request): JsonResponse
136
    {
137
        return new JsonResponse([
138
            ['type' => 0, 'title' => 'Terms and Conditions', 'subtitle' => ''],
139
            ['type' => 1, 'title' => 'Personal data collection', 'subtitle' => 'Why do we collect this data?'],
140
            // ...
141
            ['type' => 15, 'title' => 'Personal data profiling', 'subtitle' => 'For what purpose do we process personal data?'],
142
        ]);
143
    }
144
145
    /**
146
     * Checks if the extra field values have changed.
147
     *
148
     * This function compares the new values for extra fields against the old ones to determine
149
     * if there have been any changes. It is useful for triggering events or updates only when
150
     * actual changes to data occur.
151
     */
152
    private function hasExtraFieldsChanged(ExtraFieldValue $extraFieldValue, int $legalId, array $newValues): bool
0 ignored issues
show
Unused Code introduced by
The method hasExtraFieldsChanged() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
153
    {
154
        $oldValues = $extraFieldValue->getAllValuesByItem($legalId);
155
        $oldValues = array_column($oldValues, 'value', 'variable');
156
157
        foreach ($newValues as $key => $newValue) {
158
            if (isset($oldValues[$key]) && $newValue != $oldValues[$key]) {
159
                return true;
160
            }
161
            if (!isset($oldValues[$key])) {
162
                return true;
163
            }
164
        }
165
166
        return false;
167
    }
168
169
    /**
170
     * Updates the extra fields with new values for a specific item.
171
     */
172
    private function updateExtraFields(ExtraFieldValue $extraFieldValue, int $legalId, array $values): void
0 ignored issues
show
Unused Code introduced by
The method updateExtraFields() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
173
    {
174
        $values['item_id'] = $legalId;
175
        $extraFieldValue->saveFieldValues($values);
176
    }
177
178
    /**
179
     * Maps an integer representing a field type to its corresponding string value.
180
     */
181
    private function mapFieldType(int $type): string
0 ignored issues
show
Unused Code introduced by
The method mapFieldType() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
182
    {
183
        switch ($type) {
184
            case ExtraField::FIELD_TYPE_TEXT:
185
                return 'text';
186
187
            case ExtraField::FIELD_TYPE_TEXTAREA:
188
                return 'editor';
189
190
            case ExtraField::FIELD_TYPE_SELECT_MULTIPLE:
191
            case ExtraField::FIELD_TYPE_DATE:
192
            case ExtraField::FIELD_TYPE_DATETIME:
193
            case ExtraField::FIELD_TYPE_DOUBLE_SELECT:
194
            case ExtraField::FIELD_TYPE_RADIO:
195
                // Manage as needed
196
                break;
197
198
            case ExtraField::FIELD_TYPE_SELECT:
199
                return 'select';
200
        }
201
202
        return 'text';
203
    }
204
}
205