Passed
Push — master ( ed7a00...f76ccf )
by
unknown
18:58 queued 09:26
created

LegalController::hasExtraFieldsChanged()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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