Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.93% covered (warning)
75.93%
41 / 54
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SystemInformationController
75.93% covered (warning)
75.93%
41 / 54
50.00% covered (danger)
50.00%
1 / 2
12.69
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 index
75.47% covered (warning)
75.47%
40 / 53
0.00% covered (danger)
0.00%
0 / 1
11.48
1<?php
2
3/**
4 * The Administration System Information Controller
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2024-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2024-11-22
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Administration;
21
22use Elastic\Elasticsearch\Exception\ClientResponseException;
23use Elastic\Elasticsearch\Exception\ServerResponseException;
24use Elastic\Elasticsearch\Response\Elasticsearch;
25use phpMyFAQ\Administration\TranslationStatistics;
26use phpMyFAQ\Configuration\Storage\ConfigurationStorageSettingsResolver;
27use phpMyFAQ\Configuration\Storage\DatabaseConfigurationStore;
28use phpMyFAQ\Configuration\Storage\RedisConfigurationStore;
29use phpMyFAQ\Core\Exception;
30use phpMyFAQ\Database;
31use phpMyFAQ\Enums\PermissionType;
32use phpMyFAQ\System;
33use phpMyFAQ\Translation;
34use phpMyFAQ\Twig\Extensions\LanguageCodeTwigExtension;
35use Symfony\Component\HttpFoundation\Request;
36use Symfony\Component\HttpFoundation\Response;
37use Symfony\Component\Routing\Attribute\Route;
38use Twig\Extension\AttributeExtension;
39
40final class SystemInformationController extends AbstractAdministrationController
41{
42    public function __construct(
43        private readonly System $system,
44    ) {
45        parent::__construct();
46    }
47
48    /**
49     * @throws Exception
50     * @throws \Exception
51     */
52    #[Route(path: '/system', name: 'admin.system', methods: ['GET'])]
53    public function index(Request $request): Response
54    {
55        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);
56
57        $faqSystem = $this->system;
58
59        $esInformation = 'n/a';
60        if ((bool) $this->configuration->get(item: 'search.enableElasticsearch')) {
61            try {
62                $esFullInformation = $this->configuration->getElasticsearch()->info();
63                $esVersion = $esFullInformation instanceof Elasticsearch ? $esFullInformation['version'] : null;
64                $esInformation = is_array($esVersion) ? (string) ($esVersion['number'] ?? 'n/a') : 'n/a';
65            } catch (ClientResponseException|ServerResponseException $e) {
66                $this->configuration->getLogger()->error('Error while fetching Elasticsearch information', [$e->getMessage()]);
67            }
68        }
69
70        $openSearchInformation = 'n/a';
71        if ((bool) $this->configuration->get(item: 'search.enableOpenSearch')) {
72            $openSearchFullInformation = $this->configuration->getOpenSearch()->info();
73            $openSearchVersion = $openSearchFullInformation['version'] ?? null;
74            $openSearchInformation = is_array($openSearchVersion)
75                ? (string) ($openSearchVersion['number'] ?? 'n/a')
76                : 'n/a';
77        }
78
79        $redisInformation = 'n/a';
80        if (extension_loaded('redis')) {
81            try {
82                $databaseStore = new DatabaseConfigurationStore($this->configuration->getDb());
83                $settingsResolver = new ConfigurationStorageSettingsResolver($databaseStore);
84                $settings = $settingsResolver->resolve();
85
86                if ($settings->enabled) {
87                    $redisStore = new RedisConfigurationStore($settings);
88                    $redisInformation = $redisStore->getInstalledRedisVersion();
89                }
90            } catch (\Throwable $throwable) {
91                $redisInformation = 'n/a (' . $throwable->getMessage() . ')';
92            }
93        }
94
95        $translationInformation = new TranslationStatistics();
96        $translationStatistics = $translationInformation->getStatistics();
97
98        $this->addExtension(new AttributeExtension(LanguageCodeTwigExtension::class));
99        return $this->render('@admin/configuration/system.twig', [
100            ...$this->getHeader($request),
101            ...$this->getFooter(),
102            'adminHeaderSystemInfo' => Translation::get(key: 'ad_system_info'),
103            'systemInformation' => [
104                'phpMyFAQ Version' => $faqSystem->getVersion(),
105                'phpMyFAQ API Version' => $faqSystem->getApiVersion(),
106                'phpMyFAQ Plugin API Version' => $faqSystem->getPluginVersion(),
107                'phpMyFAQ MCP Server Version' => $faqSystem->getMcpServerVersion(),
108                'phpMyFAQ Installation Path' => dirname((string) $request->server->get('SCRIPT_FILENAME'), levels: 2),
109                'Web server software' => $request->server->get('SERVER_SOFTWARE'),
110                'Web server document root' => $request->server->get('DOCUMENT_ROOT'),
111                'Web server Interface' => strtoupper(PHP_SAPI),
112                'PHP Version' => PHP_VERSION,
113                'PHP Extensions' => implode(', ', get_loaded_extensions()),
114                'Database Driver' => Database::getType(),
115                'Database Server Version' => $this->configuration->getDb()->serverVersion(),
116                'Database Client Version' => $this->configuration->getDb()->clientVersion(),
117                'Elasticsearch Version' => $esInformation ?? 'n/a',
118                'OpenSearch Version' => $openSearchInformation ?? 'n/a',
119                'Redis Version' => $redisInformation,
120            ],
121            'translationInformation' => $translationStatistics,
122        ]);
123    }
124}