Lines 80.70% 46 / 57
Methods 71.42% 5 / 7
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 getPublicMetadata 100.00% 10 / 10 100.00% 1 / 1 1
 buildEnabledFeatures 100.00% 10 / 10 100.00% 1 / 1 1
 buildPublicLogoUrl 100.00% 1 / 1 100.00% 1 / 1 1
 buildThemeColors 47.36% 9 / 19 0.00% 0 / 1 4.31
 extractThemeVariables 93.33% 14 / 15 0.00% 0 / 1 3.00
 toBool 100.00% 1 / 1 100.00% 1 / 1 4
25final readonly class MetaService
26{
27    public function __construct(
28        private Configuration $configuration,
29        private OAuthDiscoveryService $oAuthDiscoveryService,
30    ) {
31    }
32
33    /**
34     * @return array{
35     *     version: string,
36     *     title: string,
37     *     language: string,
38     *     availableLanguages: array<array-key, string>,
39     *     enabledFeatures: array<string, bool>,
40     *     publicLogoUrl: string,
41     *     themeColors: array<string, array<string, string>>,
42     *     oauthDiscovery: array<string, bool|string|string[]>
43     * }
44     */
45    public function getPublicMetadata(): array
46    {
47        return [
48            'version' => $this->configuration->getVersion(),
49            'title' => $this->configuration->getTitle(),
50            'language' => $this->configuration->getLanguage()->getLanguage(),
51            'availableLanguages' => LanguageHelper::getAvailableLanguages(),
52            'enabledFeatures' => $this->buildEnabledFeatures(),
53            'publicLogoUrl' => $this->buildPublicLogoUrl(),
54            'themeColors' => $this->buildThemeColors(),
55            'oauthDiscovery' => $this->oAuthDiscoveryService->getMetaDiscovery(),
56        ];
57    }
58
59    /**
60     * @return array<string, bool>
61     */
62    private function buildEnabledFeatures(): array
63    {
64        return [
65            'api' => true,
66            'oauth2' => $this->toBool($this->configuration->get('oauth2.enable')),
67            'captcha' => $this->toBool($this->configuration->get('spam.enableCaptchaCode')),
68            'ldap' => $this->configuration->isLdapActive(),
69            'elasticsearch' => $this->toBool($this->configuration->get('search.enableElasticsearch')),
70            'opensearch' => $this->toBool($this->configuration->get('search.enableOpenSearch')),
71            'sso' => $this->toBool($this->configuration->get('security.ssoSupport')),
72            'signInWithMicrosoft' => $this->configuration->isSignInWithMicrosoftActive(),
73        ];
74    }
75
76    private function buildPublicLogoUrl(): string
77    {
78        return rtrim($this->configuration->getDefaultUrl(), characters: '/') . '/assets/images/logo-transparent.svg';
79    }
80
81    /**
82     * @return array<string, array<string, string>>
83     */
84    private function buildThemeColors(): array
85    {
86        $themeCssPath = (string) PMF_ROOT_DIR . '/assets/templates/default/theme.css';
87        if (!is_readable($themeCssPath)) {
88            return [
89                'light' => [],
90                'dark' => [],
91                'highContrast' => [],
92            ];
93        }
94
95        $themeCss = file_get_contents($themeCssPath);
96        if ($themeCss === false) {
97            return [
98                'light' => [],
99                'dark' => [],
100                'highContrast' => [],
101            ];
102        }
103
104        return [
105            'light' => $this->extractThemeVariables($themeCss, ":root,\n[data-bs-theme='light']"),
106            'dark' => $this->extractThemeVariables($themeCss, "[data-bs-theme='dark']"),
107            'highContrast' => $this->extractThemeVariables($themeCss, "[data-bs-theme='high-contrast']"),
108        ];
109    }
110
111    /**
112     * @return array<string, string>
113     */
114    private function extractThemeVariables(string $themeCss, string $selector): array
115    {
116        $pattern = sprintf('/%s\s*\{(?P<body>.*?)^\}/ms', preg_quote($selector, delimiter: '/'));
117        $matches = [];
118        if (preg_match($pattern, $themeCss, $matches) !== 1) {
119            return [];
120        }
121
122        $variableMatches = [];
123        preg_match_all(
124            '/(?P<name>--[A-Za-z0-9\-]+)\s*:\s*(?P<value>[^;]+);/',
125            (string) $matches['body'],
126            $variableMatches,
127            PREG_SET_ORDER,
128        );
129
130        $variables = [];
131        foreach ($variableMatches as $variableMatch) {
132            $variables[$variableMatch['name']] = trim($variableMatch['value']);
133        }
134
135        return $variables;
136    }
137
138    private function toBool(mixed $value): bool
139    {
140        return $value === true || $value === 1 || $value === '1' || $value === 'true';
141    }
142}