PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/phpBB/includes/acp/acp_database.php

http://github.com/phpbb/phpbb
PHP | 716 lines | 545 code | 114 blank | 57 comment | 68 complexity | 859b9f70daa14960ced48c4648c562c7 MD5 | raw file
Possible License(s): GPL-3.0, AGPL-1.0
  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. * @ignore
  15. */
  16. if (!defined('IN_PHPBB'))
  17. {
  18. exit;
  19. }
  20. class acp_database
  21. {
  22. protected $db_tools;
  23. protected $temp;
  24. public $u_action;
  25. public $page_title;
  26. function main($id, $mode)
  27. {
  28. global $cache, $db, $user, $template, $table_prefix, $request;
  29. global $phpbb_root_path, $phpbb_container, $phpbb_log, $table_prefix;
  30. $this->db_tools = $phpbb_container->get('dbal.tools');
  31. $this->temp = $phpbb_container->get('filesystem.temp');
  32. /** @var \phpbb\storage\storage $storage */
  33. $storage = $phpbb_container->get('storage.backup');
  34. $user->add_lang('acp/database');
  35. $this->tpl_name = 'acp_database';
  36. $this->page_title = 'ACP_DATABASE';
  37. $action = $request->variable('action', '');
  38. $form_key = 'acp_database';
  39. add_form_key($form_key);
  40. $template->assign_vars(array(
  41. 'MODE' => $mode
  42. ));
  43. switch ($mode)
  44. {
  45. case 'backup':
  46. $this->page_title = 'ACP_BACKUP';
  47. switch ($action)
  48. {
  49. case 'download':
  50. $type = $request->variable('type', '');
  51. $table = array_intersect($this->db_tools->sql_list_tables(), $request->variable('table', array('')));
  52. $format = $request->variable('method', '');
  53. if (!count($table))
  54. {
  55. trigger_error($user->lang['TABLE_SELECT_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING);
  56. }
  57. if (!check_form_key($form_key))
  58. {
  59. trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  60. }
  61. $store = true;
  62. $structure = false;
  63. $schema_data = false;
  64. if ($type == 'full' || $type == 'structure')
  65. {
  66. $structure = true;
  67. }
  68. if ($type == 'full' || $type == 'data')
  69. {
  70. $schema_data = true;
  71. }
  72. @set_time_limit(1200);
  73. @set_time_limit(0);
  74. $time = time();
  75. $filename = 'backup_' . $time . '_' . unique_id();
  76. try
  77. {
  78. /** @var phpbb\db\extractor\extractor_interface $extractor Database extractor */
  79. $extractor = $phpbb_container->get('dbal.extractor');
  80. $extractor->init_extractor($format, $filename, $time, false, $store);
  81. $extractor->write_start($table_prefix);
  82. foreach ($table as $table_name)
  83. {
  84. // Get the table structure
  85. if ($structure)
  86. {
  87. // Add table structure to the backup
  88. // This method also add a "drop the table if exists" after trying to write the table structure
  89. $extractor->write_table($table_name);
  90. }
  91. else
  92. {
  93. // Add command to empty table before write data on it
  94. switch ($db->get_sql_layer())
  95. {
  96. case 'sqlite3':
  97. $extractor->flush('DELETE FROM ' . $table_name . ";\n");
  98. break;
  99. case 'mssql_odbc':
  100. case 'mssqlnative':
  101. $extractor->flush('TRUNCATE TABLE ' . $table_name . "GO\n");
  102. break;
  103. case 'oracle':
  104. $extractor->flush('TRUNCATE TABLE ' . $table_name . "/\n");
  105. break;
  106. default:
  107. $extractor->flush('TRUNCATE TABLE ' . $table_name . ";\n");
  108. break;
  109. }
  110. }
  111. // Write schema data if it exists
  112. if ($schema_data)
  113. {
  114. $extractor->write_data($table_name);
  115. }
  116. }
  117. $extractor->write_end();
  118. }
  119. catch (\phpbb\exception\runtime_exception $e)
  120. {
  121. trigger_error($e->getMessage(), E_USER_ERROR);
  122. }
  123. try
  124. {
  125. if ($store)
  126. {
  127. // Get file name
  128. switch ($format)
  129. {
  130. case 'text':
  131. $ext = '.sql';
  132. break;
  133. case 'bzip2':
  134. $ext = '.sql.gz2';
  135. break;
  136. case 'gzip':
  137. $ext = '.sql.gz';
  138. break;
  139. }
  140. $file = $filename . $ext;
  141. // Copy to storage using streams
  142. $temp_dir = $this->temp->get_dir();
  143. $fp = fopen($temp_dir . '/' . $file, 'rb');
  144. if ($fp === false)
  145. {
  146. throw new \phpbb\exception\runtime_exception('CANNOT_OPEN_FILE');
  147. }
  148. $storage->write_stream($file, $fp);
  149. if (is_resource($fp))
  150. {
  151. fclose($fp);
  152. }
  153. // Remove file from tmp
  154. @unlink($temp_dir . '/' . $file);
  155. // Save to database
  156. $sql = "INSERT INTO " . $table_prefix . "backups (filename)
  157. VALUES ('$file');";
  158. $db->sql_query($sql);
  159. }
  160. }
  161. catch (\phpbb\exception\runtime_exception $e)
  162. {
  163. trigger_error($e->getMessage(), E_USER_ERROR);
  164. }
  165. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_BACKUP');
  166. trigger_error($user->lang['BACKUP_SUCCESS'] . adm_back_link($this->u_action));
  167. break;
  168. default:
  169. $tables = $this->db_tools->sql_list_tables();
  170. asort($tables);
  171. foreach ($tables as $table_name)
  172. {
  173. if (strlen($table_prefix) === 0 || stripos($table_name, $table_prefix) === 0)
  174. {
  175. $template->assign_block_vars('tables', array(
  176. 'TABLE' => $table_name
  177. ));
  178. }
  179. }
  180. unset($tables);
  181. $template->assign_vars(array(
  182. 'U_ACTION' => $this->u_action . '&amp;action=download'
  183. ));
  184. $available_methods = array('gzip' => 'zlib', 'bzip2' => 'bz2');
  185. foreach ($available_methods as $type => $module)
  186. {
  187. if (!@extension_loaded($module))
  188. {
  189. continue;
  190. }
  191. $template->assign_block_vars('methods', array(
  192. 'TYPE' => $type
  193. ));
  194. }
  195. $template->assign_block_vars('methods', array(
  196. 'TYPE' => 'text'
  197. ));
  198. break;
  199. }
  200. break;
  201. case 'restore':
  202. $this->page_title = 'ACP_RESTORE';
  203. switch ($action)
  204. {
  205. case 'submit':
  206. $delete = $request->variable('delete', '');
  207. $file = $request->variable('file', '');
  208. $backup_info = $this->get_backup_file($db, $file);
  209. if (empty($backup_info))
  210. {
  211. trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  212. }
  213. if ($delete)
  214. {
  215. if (confirm_box(true))
  216. {
  217. try
  218. {
  219. // Delete from storage
  220. $storage->delete($backup_info['file_name']);
  221. // Add log entry
  222. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_DELETE');
  223. // Remove from database
  224. $sql = "DELETE FROM " . $table_prefix . "backups
  225. WHERE filename = '" . $db->sql_escape($backup_info['file_name']) . "';";
  226. $db->sql_query($sql);
  227. }
  228. catch (\Exception $e)
  229. {
  230. trigger_error($user->lang['BACKUP_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING);
  231. }
  232. trigger_error($user->lang['BACKUP_DELETE'] . adm_back_link($this->u_action));
  233. }
  234. else
  235. {
  236. confirm_box(false, $user->lang['DELETE_SELECTED_BACKUP'], build_hidden_fields(array('delete' => $delete, 'file' => $file)));
  237. }
  238. }
  239. else if (confirm_box(true))
  240. {
  241. // Copy file to temp folder to decompress it
  242. $temp_file_name = $this->temp->get_dir() . '/' . $backup_info['file_name'];
  243. try
  244. {
  245. $stream = $storage->read_stream($backup_info['file_name']);
  246. $fp = fopen($temp_file_name, 'w+b');
  247. stream_copy_to_stream($stream, $fp);
  248. fclose($fp);
  249. fclose($stream);
  250. }
  251. catch (\phpbb\storage\exception\exception $e)
  252. {
  253. trigger_error($user->lang['RESTORE_DOWNLOAD_FAIL'] . adm_back_link($this->u_action));
  254. }
  255. switch ($backup_info['extension'])
  256. {
  257. case 'sql':
  258. $fp = fopen($temp_file_name, 'rb');
  259. $read = 'fread';
  260. $seek = 'fseek';
  261. $eof = 'feof';
  262. $close = 'fclose';
  263. $fgetd = 'fgetd';
  264. break;
  265. case 'sql.bz2':
  266. $fp = bzopen($temp_file_name, 'r');
  267. $read = 'bzread';
  268. $seek = '';
  269. $eof = 'feof';
  270. $close = 'bzclose';
  271. $fgetd = 'fgetd_seekless';
  272. break;
  273. case 'sql.gz':
  274. $fp = gzopen($temp_file_name, 'rb');
  275. $read = 'gzread';
  276. $seek = 'gzseek';
  277. $eof = 'gzeof';
  278. $close = 'gzclose';
  279. $fgetd = 'fgetd';
  280. break;
  281. default:
  282. @unlink($temp_file_name);
  283. trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  284. return;
  285. }
  286. switch ($db->get_sql_layer())
  287. {
  288. case 'mysqli':
  289. case 'sqlite3':
  290. while (($sql = $fgetd($fp, ";\n", $read, $seek, $eof)) !== false)
  291. {
  292. $db->sql_query($sql);
  293. }
  294. break;
  295. case 'postgres':
  296. $delim = ";\n";
  297. while (($sql = $fgetd($fp, $delim, $read, $seek, $eof)) !== false)
  298. {
  299. $query = trim($sql);
  300. if (substr($query, 0, 13) == 'CREATE DOMAIN')
  301. {
  302. list(, , $domain) = explode(' ', $query);
  303. $sql = "SELECT domain_name
  304. FROM information_schema.domains
  305. WHERE domain_name = '$domain';";
  306. $result = $db->sql_query($sql);
  307. if (!$db->sql_fetchrow($result))
  308. {
  309. $db->sql_query($query);
  310. }
  311. $db->sql_freeresult($result);
  312. }
  313. else
  314. {
  315. $db->sql_query($query);
  316. }
  317. if (substr($query, 0, 4) == 'COPY')
  318. {
  319. while (($sub = $fgetd($fp, "\n", $read, $seek, $eof)) !== '\.')
  320. {
  321. if ($sub === false)
  322. {
  323. trigger_error($user->lang['RESTORE_FAILURE'] . adm_back_link($this->u_action), E_USER_WARNING);
  324. }
  325. pg_put_line($db->get_db_connect_id(), $sub . "\n");
  326. }
  327. pg_put_line($db->get_db_connect_id(), "\\.\n");
  328. pg_end_copy($db->get_db_connect_id());
  329. }
  330. }
  331. break;
  332. case 'oracle':
  333. while (($sql = $fgetd($fp, "/\n", $read, $seek, $eof)) !== false)
  334. {
  335. $db->sql_query($sql);
  336. }
  337. break;
  338. case 'mssql_odbc':
  339. case 'mssqlnative':
  340. while (($sql = $fgetd($fp, "GO\n", $read, $seek, $eof)) !== false)
  341. {
  342. $db->sql_query($sql);
  343. }
  344. break;
  345. }
  346. $close($fp);
  347. @unlink($temp_file_name);
  348. // Purge the cache due to updated data
  349. $cache->purge();
  350. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_RESTORE');
  351. trigger_error($user->lang['RESTORE_SUCCESS'] . adm_back_link($this->u_action));
  352. break;
  353. }
  354. else
  355. {
  356. confirm_box(false, $user->lang['RESTORE_SELECTED_BACKUP'], build_hidden_fields(array('file' => $file)));
  357. }
  358. default:
  359. $backup_files = $this->get_file_list($db);
  360. if (!empty($backup_files))
  361. {
  362. krsort($backup_files);
  363. foreach ($backup_files as $name => $file)
  364. {
  365. $template->assign_block_vars('files', array(
  366. 'FILE' => sha1($file),
  367. 'NAME' => $user->format_date($name, 'd-m-Y H:i', true),
  368. 'SUPPORTED' => true,
  369. ));
  370. }
  371. }
  372. $template->assign_vars(array(
  373. 'U_ACTION' => $this->u_action . '&amp;action=submit'
  374. ));
  375. break;
  376. }
  377. break;
  378. }
  379. }
  380. /**
  381. * Get backup file from file hash
  382. *
  383. * @param \phpbb\db\driver\driver_interface $db Database driver
  384. * @param string $file_hash Hash of selected file
  385. *
  386. * @return array Backup file data or empty array if unable to find file
  387. */
  388. protected function get_backup_file($db, $file_hash)
  389. {
  390. $backup_data = [];
  391. $file_list = $this->get_file_list($db);
  392. $supported_extensions = $this->get_supported_extensions();
  393. foreach ($file_list as $file)
  394. {
  395. preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $file, $matches);
  396. if (sha1($file) === $file_hash && in_array($matches[2], $supported_extensions))
  397. {
  398. $backup_data = [
  399. 'file_name' => $file,
  400. 'extension' => $matches[2],
  401. ];
  402. break;
  403. }
  404. }
  405. return $backup_data;
  406. }
  407. /**
  408. * Get backup file list for directory
  409. *
  410. * @param \phpbb\db\driver\driver_interface $db Database driver
  411. *
  412. * @return array List of backup files in specified directory
  413. */
  414. protected function get_file_list($db)
  415. {
  416. $supported_extensions = $this->get_supported_extensions();
  417. $backup_files = [];
  418. $sql = 'SELECT filename
  419. FROM ' . BACKUPS_TABLE;
  420. $result = $db->sql_query($sql);
  421. while ($row = $db->sql_fetchrow($result))
  422. {
  423. if (preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $row['filename'], $matches))
  424. {
  425. if (in_array($matches[2], $supported_extensions))
  426. {
  427. $backup_files[(int) $matches[1]] = $row['filename'];
  428. }
  429. }
  430. }
  431. $db->sql_freeresult($result);
  432. return $backup_files;
  433. }
  434. /**
  435. * Get supported extensions for backup
  436. *
  437. * @return array List of supported extensions
  438. */
  439. protected function get_supported_extensions()
  440. {
  441. $extensions = ['sql'];
  442. $available_methods = ['sql.gz' => 'zlib', 'sql.bz2' => 'bz2'];
  443. foreach ($available_methods as $type => $module)
  444. {
  445. if (!@extension_loaded($module))
  446. {
  447. continue;
  448. }
  449. $extensions[] = $type;
  450. }
  451. return $extensions;
  452. }
  453. }
  454. // get how much space we allow for a chunk of data, very similar to phpMyAdmin's way of doing things ;-) (hey, we only do this for MySQL anyway :P)
  455. function get_usable_memory()
  456. {
  457. $val = trim(@ini_get('memory_limit'));
  458. if (preg_match('/(\\d+)([mkg]?)/i', $val, $regs))
  459. {
  460. $memory_limit = (int) $regs[1];
  461. switch ($regs[2])
  462. {
  463. case 'k':
  464. case 'K':
  465. $memory_limit *= 1024;
  466. break;
  467. case 'm':
  468. case 'M':
  469. $memory_limit *= 1048576;
  470. break;
  471. case 'g':
  472. case 'G':
  473. $memory_limit *= 1073741824;
  474. break;
  475. }
  476. // how much memory PHP requires at the start of export (it is really a little less)
  477. if ($memory_limit > 6100000)
  478. {
  479. $memory_limit -= 6100000;
  480. }
  481. // allow us to consume half of the total memory available
  482. $memory_limit /= 2;
  483. }
  484. else
  485. {
  486. // set the buffer to 1M if we have no clue how much memory PHP will give us :P
  487. $memory_limit = 1048576;
  488. }
  489. return $memory_limit;
  490. }
  491. function sanitize_data_mssql($text)
  492. {
  493. $data = preg_split('/[\n\t\r\b\f]/', $text);
  494. preg_match_all('/[\n\t\r\b\f]/', $text, $matches);
  495. $val = array();
  496. foreach ($data as $value)
  497. {
  498. if (strlen($value))
  499. {
  500. $val[] = "'" . $value . "'";
  501. }
  502. if (count($matches[0]))
  503. {
  504. $val[] = 'char(' . ord(array_shift($matches[0])) . ')';
  505. }
  506. }
  507. return implode('+', $val);
  508. }
  509. function sanitize_data_oracle($text)
  510. {
  511. // $data = preg_split('/[\0\n\t\r\b\f\'"\/\\\]/', $text);
  512. // preg_match_all('/[\0\n\t\r\b\f\'"\/\\\]/', $text, $matches);
  513. $data = preg_split('/[\0\b\f\'\/]/', $text);
  514. preg_match_all('/[\0\r\b\f\'\/]/', $text, $matches);
  515. $val = array();
  516. foreach ($data as $value)
  517. {
  518. if (strlen($value))
  519. {
  520. $val[] = "'" . $value . "'";
  521. }
  522. if (count($matches[0]))
  523. {
  524. $val[] = 'chr(' . ord(array_shift($matches[0])) . ')';
  525. }
  526. }
  527. return implode('||', $val);
  528. }
  529. function sanitize_data_generic($text)
  530. {
  531. $data = preg_split('/[\n\t\r\b\f]/', $text);
  532. preg_match_all('/[\n\t\r\b\f]/', $text, $matches);
  533. $val = array();
  534. foreach ($data as $value)
  535. {
  536. if (strlen($value))
  537. {
  538. $val[] = "'" . $value . "'";
  539. }
  540. if (count($matches[0]))
  541. {
  542. $val[] = "'" . array_shift($matches[0]) . "'";
  543. }
  544. }
  545. return implode('||', $val);
  546. }
  547. // modified from PHP.net
  548. function fgetd(&$fp, $delim, $read, $seek, $eof, $buffer = 8192)
  549. {
  550. $record = '';
  551. $delim_len = strlen($delim);
  552. while (!$eof($fp))
  553. {
  554. $pos = strpos($record, $delim);
  555. if ($pos === false)
  556. {
  557. $record .= $read($fp, $buffer);
  558. if ($eof($fp) && ($pos = strpos($record, $delim)) !== false)
  559. {
  560. $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
  561. return substr($record, 0, $pos);
  562. }
  563. }
  564. else
  565. {
  566. $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
  567. return substr($record, 0, $pos);
  568. }
  569. }
  570. return false;
  571. }
  572. function fgetd_seekless(&$fp, $delim, $read, $seek, $eof, $buffer = 8192)
  573. {
  574. static $array = array();
  575. static $record = '';
  576. if (!count($array))
  577. {
  578. while (!$eof($fp))
  579. {
  580. if (strpos($record, $delim) !== false)
  581. {
  582. $array = explode($delim, $record);
  583. $record = array_pop($array);
  584. break;
  585. }
  586. else
  587. {
  588. $record .= $read($fp, $buffer);
  589. }
  590. }
  591. if ($eof($fp) && strpos($record, $delim) !== false)
  592. {
  593. $array = explode($delim, $record);
  594. $record = array_pop($array);
  595. }
  596. }
  597. if (count($array))
  598. {
  599. return array_shift($array);
  600. }
  601. return false;
  602. }