Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
20.41% |
30 / 147 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
database | |
20.41% |
30 / 147 |
|
33.33% |
2 / 6 |
1362.43 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
get_available_dbms | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
240 | |||
remove_comments | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
split_sql_file | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
2.01 | |||
validate_table_prefix | |
65.52% |
19 / 29 |
|
0.00% |
0 / 1 |
14.10 | |||
check_database_connection | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
506 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\install\helper; |
15 | |
16 | use phpbb\db\doctrine\connection_factory; |
17 | use phpbb\install\exception\invalid_dbms_exception; |
18 | use phpbb\filesystem\helper as filesystem_helper; |
19 | |
20 | /** |
21 | * Database related general functionality for installer |
22 | */ |
23 | class database |
24 | { |
25 | /** |
26 | * @var \phpbb\filesystem\filesystem_interface |
27 | */ |
28 | protected $filesystem; |
29 | |
30 | /** |
31 | * @var string |
32 | */ |
33 | protected $phpbb_root_path; |
34 | |
35 | /** |
36 | * @var array |
37 | */ |
38 | protected $supported_dbms = array( |
39 | // Note: php 5.5 alpha 2 deprecated mysql. |
40 | // Keep mysqli before mysql in this list. |
41 | 'mysqli' => array( |
42 | 'LABEL' => 'MySQL with MySQLi Extension', |
43 | 'SCHEMA' => 'mysql_41', |
44 | 'MODULE' => 'mysqli', |
45 | 'DOCTRINE' => ['pdo_mysql'], |
46 | 'DELIM' => ';', |
47 | 'DRIVER' => 'phpbb\db\driver\mysqli', |
48 | 'AVAILABLE' => true, |
49 | '2.0.x' => true, |
50 | ), |
51 | 'mssql_odbc' => array( |
52 | 'LABEL' => 'MS SQL Server [ ODBC ]', |
53 | 'SCHEMA' => 'mssql', |
54 | 'MODULE' => 'odbc', |
55 | 'DOCTRINE' => ['pdo_sqlsrv'], |
56 | 'DELIM' => ';', |
57 | 'DRIVER' => 'phpbb\db\driver\mssql_odbc', |
58 | 'AVAILABLE' => true, |
59 | '2.0.x' => true, |
60 | ), |
61 | 'mssqlnative' => array( |
62 | 'LABEL' => 'MS SQL Server 2005+ [ Native ]', |
63 | 'SCHEMA' => 'mssql', |
64 | 'MODULE' => 'sqlsrv', |
65 | 'DOCTRINE' => ['pdo_sqlsrv'], |
66 | 'DELIM' => ';', |
67 | 'DRIVER' => 'phpbb\db\driver\mssqlnative', |
68 | 'AVAILABLE' => true, |
69 | '2.0.x' => false, |
70 | ), |
71 | 'oracle' => array( |
72 | 'LABEL' => 'Oracle', |
73 | 'SCHEMA' => 'oracle', |
74 | 'MODULE' => 'oci8', |
75 | 'DELIM' => ';', |
76 | 'DRIVER' => 'phpbb\db\driver\oracle', |
77 | 'AVAILABLE' => true, |
78 | '2.0.x' => false, |
79 | ), |
80 | 'postgres' => array( |
81 | 'LABEL' => 'PostgreSQL 8.3+', |
82 | 'SCHEMA' => 'postgres', |
83 | 'MODULE' => 'pgsql', |
84 | 'DOCTRINE' => ['pdo_pgsql'], |
85 | 'DELIM' => ';', |
86 | 'DRIVER' => 'phpbb\db\driver\postgres', |
87 | 'AVAILABLE' => true, |
88 | '2.0.x' => true, |
89 | ), |
90 | 'sqlite3' => array( |
91 | 'LABEL' => 'SQLite3', |
92 | 'SCHEMA' => 'sqlite', |
93 | 'MODULE' => 'sqlite3', |
94 | 'DOCTRINE' => ['pdo_sqlite'], |
95 | 'DELIM' => ';', |
96 | 'DRIVER' => 'phpbb\db\driver\sqlite3', |
97 | 'AVAILABLE' => true, |
98 | '2.0.x' => false, |
99 | ), |
100 | ); |
101 | |
102 | /** |
103 | * Constructor |
104 | * |
105 | * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem interface |
106 | * @param string $phpbb_root_path Path to phpBB's root |
107 | */ |
108 | public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path) |
109 | { |
110 | $this->filesystem = $filesystem; |
111 | $this->phpbb_root_path = $phpbb_root_path; |
112 | } |
113 | |
114 | /** |
115 | * Returns an array of available DBMS supported by phpBB |
116 | * |
117 | * If a DBMS is specified it will only return data for that DBMS |
118 | * and will load its extension if necessary. |
119 | * |
120 | * @param mixed $dbms name of the DBMS that's info is required or false for all DBMS info |
121 | * @param bool $return_unavailable set it to true if you expect unavailable but supported DBMS |
122 | * returned as well |
123 | * @param bool $only_20x_options set it to true if you only want to recover 2.0.x options |
124 | * |
125 | * @return array Array of available and supported DBMS |
126 | */ |
127 | public function get_available_dbms($dbms = false, $return_unavailable = false, $only_20x_options = false) |
128 | { |
129 | $available_dbms = $this->supported_dbms; |
130 | |
131 | if ($dbms) |
132 | { |
133 | if (isset($this->supported_dbms[$dbms])) |
134 | { |
135 | $available_dbms = array($dbms => $this->supported_dbms[$dbms]); |
136 | } |
137 | else |
138 | { |
139 | return array(); |
140 | } |
141 | } |
142 | |
143 | $any_dbms_available = false; |
144 | foreach ($available_dbms as $db_name => $db_array) |
145 | { |
146 | if ($only_20x_options && !$db_array['2.0.x']) |
147 | { |
148 | if ($return_unavailable) |
149 | { |
150 | $available_dbms[$db_name]['AVAILABLE'] = false; |
151 | } |
152 | else |
153 | { |
154 | unset($available_dbms[$db_name]); |
155 | } |
156 | |
157 | continue; |
158 | } |
159 | |
160 | $dll = $db_array['MODULE']; |
161 | if (!@extension_loaded($dll)) |
162 | { |
163 | if ($return_unavailable) |
164 | { |
165 | $available_dbms[$db_name]['AVAILABLE'] = false; |
166 | } |
167 | else |
168 | { |
169 | unset($available_dbms[$db_name]); |
170 | } |
171 | |
172 | continue; |
173 | } |
174 | |
175 | if (array_key_exists('DOCTRINE', $db_array)) |
176 | { |
177 | $available = false; |
178 | foreach ($db_array['DOCTRINE'] as $dll) |
179 | { |
180 | if (@extension_loaded($dll)) |
181 | { |
182 | $available = true; |
183 | break; |
184 | } |
185 | } |
186 | |
187 | if (!$available) |
188 | { |
189 | if ($return_unavailable) |
190 | { |
191 | $available_dbms[$db_name]['AVAILABLE'] = false; |
192 | } |
193 | else |
194 | { |
195 | unset($available_dbms[$db_name]); |
196 | } |
197 | |
198 | continue; |
199 | } |
200 | } |
201 | |
202 | $any_dbms_available = true; |
203 | } |
204 | |
205 | if ($return_unavailable) |
206 | { |
207 | $available_dbms['ANY_DB_SUPPORT'] = $any_dbms_available; |
208 | } |
209 | |
210 | return $available_dbms; |
211 | } |
212 | |
213 | /** |
214 | * Removes "/* style" as well as "# style" comments from $input. |
215 | * |
216 | * @param string $sql_query Input string |
217 | * |
218 | * @return string Input string with comments removed |
219 | */ |
220 | public function remove_comments($sql_query) |
221 | { |
222 | // Remove /* */ comments (http://ostermiller.org/findcomment.html) |
223 | $sql_query = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $sql_query); |
224 | |
225 | // Remove # style comments |
226 | $sql_query = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql_query)); |
227 | |
228 | return $sql_query; |
229 | } |
230 | |
231 | /** |
232 | * split_sql_file() will split an uploaded sql file into single sql statements. |
233 | * |
234 | * Note: expects trim() to have already been run on $sql. |
235 | * |
236 | * @param string $sql SQL statements |
237 | * @param string $delimiter Delimiter between sql statements |
238 | * |
239 | * @return array Array of sql statements |
240 | */ |
241 | public function split_sql_file($sql, $delimiter) |
242 | { |
243 | $sql = str_replace("\r" , '', $sql); |
244 | $data = preg_split('/' . preg_quote($delimiter, '/') . '$/m', $sql); |
245 | |
246 | $data = array_map('trim', $data); |
247 | |
248 | // The empty case |
249 | $end_data = end($data); |
250 | |
251 | if (empty($end_data)) |
252 | { |
253 | unset($data[key($data)]); |
254 | } |
255 | |
256 | return $data; |
257 | } |
258 | |
259 | /** |
260 | * Validates table prefix |
261 | * |
262 | * @param string $dbms The selected dbms |
263 | * @param string $table_prefix The table prefix to validate |
264 | * |
265 | * @return bool|array true if table prefix is valid, array of errors otherwise |
266 | * |
267 | * @throws invalid_dbms_exception When $dbms is not a valid |
268 | */ |
269 | public function validate_table_prefix($dbms, $table_prefix) |
270 | { |
271 | $errors = array(); |
272 | |
273 | if (!preg_match('#^[a-zA-Z][a-zA-Z0-9_]*$#', $table_prefix)) |
274 | { |
275 | $errors[] = array( |
276 | 'title' => 'INST_ERR_DB_INVALID_PREFIX', |
277 | ); |
278 | } |
279 | |
280 | // Do dbms specific checks |
281 | $dbms_info = $this->get_available_dbms($dbms); |
282 | switch ($dbms_info[$dbms]['SCHEMA']) |
283 | { |
284 | case 'mysql_41': |
285 | $prefix_length = 36; |
286 | break; |
287 | case 'mssql': |
288 | $prefix_length = 90; |
289 | break; |
290 | case 'oracle': |
291 | $prefix_length = 6; |
292 | break; |
293 | case 'postgres': |
294 | $prefix_length = 36; |
295 | break; |
296 | case 'sqlite': |
297 | $prefix_length = 200; |
298 | break; |
299 | default: |
300 | throw new invalid_dbms_exception(); |
301 | break; |
302 | } |
303 | |
304 | // Check the prefix length to ensure that index names are not too long |
305 | if (strlen($table_prefix) > $prefix_length) |
306 | { |
307 | $errors[] = array( |
308 | 'title' => array('INST_ERR_PREFIX_TOO_LONG', $prefix_length), |
309 | ); |
310 | } |
311 | |
312 | return (empty($errors)) ? true : $errors; |
313 | } |
314 | |
315 | /** |
316 | * Check if the user provided database parameters are correct |
317 | * |
318 | * This function checks the database connection data and also checks for |
319 | * any other problems that could cause an error during the installation |
320 | * such as if there is any database table names conflicting. |
321 | * |
322 | * Note: The function assumes that $table_prefix has been already validated |
323 | * with validate_table_prefix(). |
324 | * |
325 | * @param string $dbms Selected database type |
326 | * @param string $dbhost Database host address |
327 | * @param int $dbport Database port number |
328 | * @param string $dbuser Database username |
329 | * @param string $dbpass Database password |
330 | * @param string $dbname Database name |
331 | * @param string $table_prefix Database table prefix |
332 | * |
333 | * @return array|bool Returns true if test is successful, array of errors otherwise |
334 | */ |
335 | public function check_database_connection($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix) |
336 | { |
337 | $dbms_info = $this->get_available_dbms($dbms); |
338 | $dbms_info = $dbms_info[$dbms]; |
339 | $errors = array(); |
340 | |
341 | // Instantiate it and set return on error true |
342 | /** @var \phpbb\db\driver\driver_interface $db */ |
343 | $db = new $dbms_info['DRIVER']; |
344 | $db->sql_return_on_error(true); |
345 | |
346 | // Check that we actually have a database name before going any further |
347 | if (!in_array($dbms_info['SCHEMA'], array('sqlite', 'oracle'), true) && $dbname === '') |
348 | { |
349 | $errors[] = array( |
350 | 'title' => 'INST_ERR_DB_NO_NAME', |
351 | ); |
352 | } |
353 | |
354 | // Make sure we don't have a daft user who thinks having the SQLite database in the forum directory is a good idea |
355 | if ($dbms_info['SCHEMA'] === 'sqlite' |
356 | && stripos(filesystem_helper::realpath($dbhost), filesystem_helper::realpath($this->phpbb_root_path) === 0)) |
357 | { |
358 | $errors[] = array( |
359 | 'title' =>'INST_ERR_DB_FORUM_PATH', |
360 | ); |
361 | } |
362 | |
363 | // Check if SQLite database is writable |
364 | if ($dbms_info['SCHEMA'] === 'sqlite' |
365 | && (($this->filesystem->exists($dbhost) && !$this->filesystem->is_writable($dbhost)) || !$this->filesystem->is_writable(pathinfo($dbhost, PATHINFO_DIRNAME)))) |
366 | { |
367 | $errors[] = array( |
368 | 'title' =>'INST_ERR_DB_NO_WRITABLE', |
369 | ); |
370 | } |
371 | |
372 | // Try to connect to db |
373 | if (is_array($db->sql_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, false, true))) |
374 | { |
375 | $db_error = $db->sql_error(); |
376 | $errors[] = array( |
377 | 'title' => 'INST_ERR_DB_CONNECT', |
378 | 'description' => ($db_error['message']) ? utf8_convert_message($db_error['message']) : 'INST_ERR_DB_NO_ERROR', |
379 | ); |
380 | } |
381 | else |
382 | { |
383 | // Check if there is any table name collisions |
384 | $temp_prefix = strtolower($table_prefix); |
385 | $table_ary = array( |
386 | $temp_prefix . 'attachments', |
387 | $temp_prefix . 'config', |
388 | $temp_prefix . 'sessions', |
389 | $temp_prefix . 'topics', |
390 | $temp_prefix . 'users', |
391 | ); |
392 | |
393 | $doctrine_db = connection_factory::get_connection_from_params($dbms, $dbhost, $dbuser, $dbpass, $dbname, $dbport); |
394 | $db_tools_factory = new \phpbb\db\tools\factory(); |
395 | $db_tools = $db_tools_factory->get($doctrine_db); |
396 | $tables = $db_tools->sql_list_tables(); |
397 | $tables = array_map('strtolower', $tables); |
398 | $table_intersect = array_intersect($tables, $table_ary); |
399 | |
400 | if (count($table_intersect)) |
401 | { |
402 | $errors[] = array( |
403 | 'title' => 'INST_ERR_PREFIX', |
404 | ); |
405 | } |
406 | |
407 | // Check if database version is supported |
408 | switch ($dbms) |
409 | { |
410 | case 'sqlite3': |
411 | if (version_compare($db->sql_server_info(true), '3.6.15', '<')) |
412 | { |
413 | $errors[] = array( |
414 | 'title' => 'INST_ERR_DB_NO_SQLITE3', |
415 | ); |
416 | } |
417 | break; |
418 | case 'oracle': |
419 | $sql = "SELECT * |
420 | FROM NLS_DATABASE_PARAMETERS |
421 | WHERE PARAMETER = 'NLS_RDBMS_VERSION' |
422 | OR PARAMETER = 'NLS_CHARACTERSET'"; |
423 | $result = $db->sql_query($sql); |
424 | |
425 | $stats = []; |
426 | while ($row = $db->sql_fetchrow($result)) |
427 | { |
428 | $stats[$row['parameter']] = $row['value']; |
429 | } |
430 | $db->sql_freeresult($result); |
431 | |
432 | if (version_compare($stats['NLS_RDBMS_VERSION'], '9.2', '<') && $stats['NLS_CHARACTERSET'] !== 'UTF8') |
433 | { |
434 | $errors[] = array( |
435 | 'title' => 'INST_ERR_DB_NO_ORACLE', |
436 | ); |
437 | } |
438 | break; |
439 | case 'postgres': |
440 | $sql = "SHOW server_encoding;"; |
441 | $result = $db->sql_query($sql); |
442 | $row = $db->sql_fetchrow($result); |
443 | $db->sql_freeresult($result); |
444 | |
445 | if ($row['server_encoding'] !== 'UNICODE' && $row['server_encoding'] !== 'UTF8') |
446 | { |
447 | $errors[] = array( |
448 | 'title' => 'INST_ERR_DB_NO_POSTGRES', |
449 | ); |
450 | } |
451 | break; |
452 | } |
453 | } |
454 | |
455 | return (empty($errors)) ? true : $errors; |
456 | } |
457 | } |