Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.60% covered (success)
90.60%
106 / 117
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Setup
90.60% covered (success)
90.60%
106 / 117
77.78% covered (warning)
77.78%
7 / 9
23.44
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
 setRootDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createAnonymousUser
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 checkDirs
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
9.80
 createDatabaseFile
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
1 / 1
5
 escapeForSingleQuotedPhpString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createLdapFile
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 createElasticsearchFile
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 createOpenSearchFile
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * The phpMyFAQ instances setup class.
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 2012-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     2012-04-04
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Instance;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Core\Exception;
24use phpMyFAQ\User;
25
26/**
27 * Class Setup
28 *
29 * @package phpMyFAQ\Instance
30 */
31class Setup
32{
33    private string $rootDir;
34
35    /**
36     * Setup constructor.
37     */
38    public function __construct()
39    {
40        $this->setRootDir(PMF_SRC_DIR);
41    }
42
43    /**
44     * Sets the root directory of the phpMyFAQ instance.
45     */
46    public function setRootDir(string $rootDir): void
47    {
48        $this->rootDir = $rootDir;
49    }
50
51    /**
52     * Creates the anonymous default user.
53     *
54     * @throws Exception
55     */
56    public function createAnonymousUser(Configuration $configuration): void
57    {
58        $user = new User($configuration);
59        $user->createUser('anonymous', '', '', -1);
60        $user->setStatus('protected');
61
62        $anonymousData = [
63            'display_name' => 'Anonymous User',
64            'email' => '',
65        ];
66        $user->setUserData($anonymousData);
67    }
68
69    /**
70     * Checks basic folders and creates them if necessary.
71     *
72     * @param  string[] $dirs
73     * @return string[]
74     */
75    public function checkDirs(array $dirs): array
76    {
77        $failedDirs = [];
78
79        foreach ($dirs as $dir) {
80            if (false === is_dir($this->rootDir . $dir)) {
81                // If the folder does not exist, try to create it
82                if (false === mkdir($this->rootDir . $dir)) {
83                    // If the folder creation fails
84                    $failedDirs[] = 'Folder [' . $dir . '] could not be created.';
85                }
86
87                if (is_dir($this->rootDir . $dir) && false === chmod($this->rootDir . $dir, permissions: 0o775)) {
88                    $failedDirs[] = 'Folder [' . $dir . '] could not be given correct permissions (775).';
89                }
90            }
91
92            if (false === is_dir($this->rootDir . $dir)) {
93                continue;
94            }
95
96            if (false === is_writable($this->rootDir . $dir)) {
97                // The folder exists, check permissions
98                // If the folder exists but is not writeable
99                $failedDirs[] = 'Folder [' . $dir . '] exists but is not writable.';
100            }
101
102            if ([] === $failedDirs) {
103                // if no failed dirs exist
104                copy($this->rootDir . '/setup/index.html', $this->rootDir . $dir . '/index.html');
105            }
106        }
107
108        return $failedDirs;
109    }
110
111    /**
112     * Creates the file /content/core/config/database.php.
113     *
114     * @param  array<string, mixed> $data   Array with database credentials
115     * @param  string         $folder Folder
116     * @throws Exception
117     */
118    public function createDatabaseFile(array $data, string $folder = '/content/core/config'): int|bool
119    {
120        if (!file_exists($this->rootDir . $folder)) {
121            throw new Exception('File [' . $this->rootDir . $folder . '] does not exist.');
122        }
123
124        if (!is_writable($this->rootDir . $folder)) {
125            throw new Exception('File [' . $this->rootDir . $folder . '] is not writable.');
126        }
127
128        $schema = (string) ($data['dbSchema'] ?? '');
129        if ($schema !== '' && !preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $schema)) {
130            throw new Exception('Invalid database schema name.');
131        }
132
133        $dbServer = $this->escapeForSingleQuotedPhpString((string) ($data['dbServer'] ?? ''));
134        $dbPort = $this->escapeForSingleQuotedPhpString((string) ($data['dbPort'] ?? ''));
135        $dbUser = $this->escapeForSingleQuotedPhpString((string) ($data['dbUser'] ?? ''));
136        $dbPassword = $this->escapeForSingleQuotedPhpString((string) ($data['dbPassword'] ?? ''));
137        $dbDatabaseName = $this->escapeForSingleQuotedPhpString((string) ($data['dbDatabaseName'] ?? ''));
138        $dbPrefix = $this->escapeForSingleQuotedPhpString((string) ($data['dbPrefix'] ?? ''));
139        $dbType = $this->escapeForSingleQuotedPhpString((string) ($data['dbType'] ?? ''));
140        $dbSchema = $this->escapeForSingleQuotedPhpString($schema);
141
142        return file_put_contents(
143            $this->rootDir . $folder . '/database.php',
144            '<?php
145$DB[\'server\'] = \''
146            . $dbServer
147            . "';\n"
148            . "\$DB['port'] = '"
149            . $dbPort
150            . "';\n"
151            . "\$DB['user'] = '"
152            . $dbUser
153            . "';\n"
154            . "\$DB['password'] = '"
155            . $dbPassword
156            . "';\n"
157            . "\$DB['db'] = '"
158            . $dbDatabaseName
159            . "';\n"
160            . "\$DB['prefix'] = '"
161            . $dbPrefix
162            . "';\n"
163            . "\$DB['type'] = '"
164            . $dbType
165            . "';\n"
166            . "\$DB['schema'] = '"
167            . $dbSchema
168            . "';",
169            LOCK_EX,
170        );
171    }
172
173    private function escapeForSingleQuotedPhpString(string $value): string
174    {
175        return str_replace(['\\', "'"], ['\\\\', "\\'"], $value);
176    }
177
178    /**
179     * Creates the file /content/core/config/ldap.php.
180     *
181     * @param  array<string, mixed> $data   Array with LDAP credentials
182     * @param  string         $folder Folder
183     */
184    public function createLdapFile(array $data, string $folder = '/content/core/config'): int|bool
185    {
186        return file_put_contents(
187            $this->rootDir . $folder . '/config/ldap.php',
188            '<?php
189$PMF_LDAP[\'ldap_server\'] = \''
190            . (string) ($data['ldapServer'] ?? '')
191            . "';\n"
192            . "\$PMF_LDAP['ldap_port'] = '"
193            . (string) ($data['ldapPort'] ?? '')
194            . "';\n"
195            . "\$PMF_LDAP['ldap_user'] = '"
196            . (string) ($data['ldapUser'] ?? '')
197            . "';\n"
198            . "\$PMF_LDAP['ldap_password'] = '"
199            . (string) ($data['ldapPassword'] ?? '')
200            . "';\n"
201            . "\$PMF_LDAP['ldap_base'] = '"
202            . (string) ($data['ldapBase'] ?? '')
203            . "';",
204            LOCK_EX,
205        );
206    }
207
208    /**
209     * Creates the file /content/core/config/elasticsearch.php
210     *
211     * @param  array<string, mixed> $data   Array with Elasticsearch credentials
212     * @param  string         $folder Folder
213     */
214    public function createElasticsearchFile(array $data, string $folder = '/content/core/config'): int|bool
215    {
216        return file_put_contents(
217            $this->rootDir . $folder . '/config/elasticsearch.php',
218            '<?php
219$PMF_ES[\'hosts\'] = [\''
220            . implode("'], ['", array_map(
221                static fn(mixed $host): string => (string) $host,
222                is_array($data['hosts'] ?? null) ? $data['hosts'] : [],
223            ))
224            . "'];\n"
225            . "\$PMF_ES['index'] = '"
226            . (string) ($data['index'] ?? '')
227            . "';\n",
228            LOCK_EX,
229        );
230    }
231
232    /**
233     * Creates the file /content/core/config/opensearch.php
234     *
235     * @param  array<string, mixed> $data   Array with OpenSearch credentials
236     * @param  string         $folder Folder
237     */
238    public function createOpenSearchFile(array $data, string $folder = '/content/core/config'): int|bool
239    {
240        return file_put_contents(
241            $this->rootDir . $folder . '/config/opensearch.php',
242            '<?php
243$PMF_OS[\'hosts\'] = [\''
244            . implode("'], ['", array_map(
245                static fn(mixed $host): string => (string) $host,
246                is_array($data['hosts'] ?? null) ? $data['hosts'] : [],
247            ))
248            . "'];\n"
249            . "\$PMF_OS['index'] = '"
250            . (string) ($data['index'] ?? '')
251            . "';\n",
252            LOCK_EX,
253        );
254    }
255}