Lines 90.59% 106 / 117
Methods 77.77% 7 / 9
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 setRootDir 100.00% 1 / 1 100.00% 1 / 1 1
 createAnonymousUser 0.00% 0 / 8 0.00% 0 / 1 2
 checkDirs 78.57% 11 / 14 0.00% 0 / 1 9.80
 createDatabaseFile 100.00% 44 / 44 100.00% 1 / 1 5
 escapeForSingleQuotedPhpString 100.00% 1 / 1 100.00% 1 / 1 1
 createLdapFile 100.00% 20 / 20 100.00% 1 / 1 1
 createElasticsearchFile 100.00% 14 / 14 100.00% 1 / 1 2
 createOpenSearchFile 100.00% 14 / 14 100.00% 1 / 1 2
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}