Passed
Pull Request — master (#7330)
by
unknown
08:51
created

InstallDbGuardSubscriber   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 115
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 61
c 1
b 0
f 0
dl 0
loc 115
rs 10
wmc 25

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 1 1
A redirectToInstaller() 0 4 1
A detectSettingsTable() 0 10 2
A getSubscribedEvents() 0 3 1
D onKernelRequest() 0 85 20
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\EventSubscriber;
8
9
use Doctrine\DBAL\Connection;
10
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
11
use Symfony\Component\HttpFoundation\RedirectResponse;
12
use Symfony\Component\HttpKernel\Event\RequestEvent;
13
use Symfony\Component\HttpKernel\KernelEvents;
14
15
final class InstallDbGuardSubscriber implements EventSubscriberInterface
16
{
17
    private const VERSION_KEY = 'chamilo_database_version';
18
19
    public function __construct(private readonly Connection $connection) {}
20
21
    public static function getSubscribedEvents(): array
22
    {
23
        return [KernelEvents::REQUEST => ['onKernelRequest', 2048]];
24
    }
25
26
    public function onKernelRequest(RequestEvent $event): void
27
    {
28
        if (!$event->isMainRequest() || PHP_SAPI === 'cli') {
29
            return;
30
        }
31
32
        $request = $event->getRequest();
33
        $path = $request->getPathInfo();
34
35
        if (
36
            str_starts_with($path, '/main/install/')
37
            || str_starts_with($path, '/_wdt')
38
            || str_starts_with($path, '/_profiler')
39
            || str_starts_with($path, '/build/')
40
            || str_starts_with($path, '/bundles/')
41
            || str_starts_with($path, '/favicon')
42
        ) {
43
            return;
44
        }
45
46
        $installedFlag = (string) (
47
            $request->server->get('APP_INSTALLED')
48
            ?? $_ENV['APP_INSTALLED']
49
            ?? getenv('APP_INSTALLED')
50
            ?? '0'
51
        );
52
        $installedFlag = trim($installedFlag, " \t\n\r\0\x0B'\"");
53
54
        if ($installedFlag !== '1') {
55
            $this->redirectToInstaller($event);
56
            return;
57
        }
58
59
        // Cache "healthy" per PHP-FPM worker
60
        static $isHealthy = false;
61
        if ($isHealthy) {
62
            return;
63
        }
64
65
        // Cache which settings table to use per PHP-FPM worker
66
        static $settingsTable = null;
67
68
        try {
69
            // If DB is down/unreachable, this will fail quickly
70
            $this->connection->executeQuery($this->connection->getDatabasePlatform()->getDummySelectSQL());
71
72
            if ($settingsTable === null) {
73
                $settingsTable = $this->detectSettingsTable();
74
                if ($settingsTable === null) {
75
                    $this->redirectToInstaller($event);
76
                    return;
77
                }
78
            }
79
80
            // Cheap proof: at least 1 row
81
            $hasRow = $this->connection->fetchOne("SELECT 1 FROM {$settingsTable} LIMIT 1");
82
            if ($hasRow === false || $hasRow === null) {
83
                $this->redirectToInstaller($event);
84
                return;
85
            }
86
87
            // Read DB version (try common column names; only runs until first success)
88
            $version = null;
89
            foreach (['selected_value', 'value', 'c_value'] as $col) {
90
                try {
91
                    $version = $this->connection->fetchOne(
92
                        "SELECT {$col} FROM {$settingsTable} WHERE variable = :var LIMIT 1",
93
                        ['var' => self::VERSION_KEY]
94
                    );
95
                    if (!empty($version)) {
96
                        break;
97
                    }
98
                } catch (\Throwable) {
99
                    // Try next column
100
                }
101
            }
102
103
            if (empty($version)) {
104
                $this->redirectToInstaller($event);
105
                return;
106
            }
107
108
            $isHealthy = true;
109
        } catch (\Throwable) {
110
            $this->redirectToInstaller($event);
111
        }
112
    }
113
114
    private function detectSettingsTable(): ?string
115
    {
116
        // Fallback to settings
117
        try {
118
            $this->connection->fetchOne('SELECT 1 FROM settings LIMIT 1');
119
            return 'settings';
120
        } catch (\Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
121
        }
122
123
        return null;
124
    }
125
126
    private function redirectToInstaller(RequestEvent $event): void
127
    {
128
        $event->setResponse(new RedirectResponse('/main/install/index.php', 302));
129
        $event->stopPropagation();
130
    }
131
}
132