Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
90.60% |
106 / 117 |
|
77.78% |
7 / 9 |
CRAP | |
0.00% |
0 / 1 |
| Setup | |
90.60% |
106 / 117 |
|
77.78% |
7 / 9 |
23.44 | |
0.00% |
0 / 1 |
| __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 | |||
| 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 | |
| 18 | declare(strict_types=1); |
| 19 | |
| 20 | namespace phpMyFAQ\Instance; |
| 21 | |
| 22 | use phpMyFAQ\Configuration; |
| 23 | use phpMyFAQ\Core\Exception; |
| 24 | use phpMyFAQ\User; |
| 25 | |
| 26 | /** |
| 27 | * Class Setup |
| 28 | * |
| 29 | * @package phpMyFAQ\Instance |
| 30 | */ |
| 31 | class 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 | } |