· 9 years ago · Jul 02, 2017, 10:10 AM
1<?php
2//bold("<br><br>Shuttle Dumper included");
3/**
4 * Abstract dump file: provides common interface for writing
5 * data to dump files.
6 */
7abstract class Shuttle_Dump_File {
8 /**
9 * File Handle
10 */
11 protected $fh;
12
13 /**
14 * Location of the dump file on the disk
15 */
16 protected $file_location;
17
18 abstract function write($string);
19 abstract function end();
20
21 static function create($filename) {
22 if (self::is_gzip($filename)) {
23 return new Shuttle_Dump_File_Gzip($filename);
24 }
25 return new Shuttle_Dump_File_Plaintext($filename);
26 }
27 function __construct($file) {
28 $this->file_location = $file;
29 $this->fh = $this->open();
30
31 if (!$this->fh) {
32 throw new Shuttle_Exception("Couldn't create gz file");
33 }
34 }
35
36 public static function is_gzip($filename) {
37 return preg_match('~gz$~i', $filename);
38 }
39}
40
41/**
42 * Plain text implementation. Uses standard file functions in PHP.
43 */
44class Shuttle_Dump_File_Plaintext extends Shuttle_Dump_File {
45 function open() {
46 return fopen($this->file_location, 'w');
47 }
48 function write($string) {
49 return fwrite($this->fh, $string);
50 }
51 function end() {
52 return fclose($this->fh);
53 }
54}
55
56/**
57 * Gzip implementation. Uses gz* functions.
58 */
59class Shuttle_Dump_File_Gzip extends Shuttle_Dump_File {
60 function open() {
61 return gzopen($this->file_location, 'wb9');
62 }
63 function write($string) {
64 return gzwrite($this->fh, $string);
65 }
66 function end() {
67 return gzclose($this->fh);
68 }
69}
70
71/**
72 * MySQL insert statement builder.
73 */
74class Shuttle_Insert_Statement {
75 private $rows = array();
76 private $length = 0;
77 private $table;
78
79 function __construct($table) {
80 $this->table = $table;
81 }
82
83 function reset() {
84 $this->rows = array();
85 $this->length = 0;
86 }
87
88 function add_row($row) {
89 $row = '(' . implode(",", $row) . ')';
90 $this->rows[] = $row;
91 $this->length += strlen($row);
92 }
93
94 function get_sql() {
95 if (empty($this->rows)) {
96 return false;
97 }
98
99 return 'INSERT INTO `' . $this->table . '` VALUES ' .
100 implode(",\n", $this->rows) . '; ';
101 }
102
103 function get_length() {
104 return $this->length;
105 }
106}
107
108/**
109 * Main facade
110 */
111abstract class Shuttle_Dumper {
112 /**
113 * Maximum length of single insert statement
114 */
115 const INSERT_THRESHOLD = 838860;
116
117 /**
118 * @var Shuttle_DBConn
119 */
120 public $db;
121
122 /**
123 * @var Shuttle_Dump_File
124 */
125 public $dump_file;
126
127 /**
128 * End of line style used in the dump
129 */
130 public $eol = "\r\n";
131
132 /**
133 * Specificed tables to include
134 */
135 public $include_tables;
136
137 /**
138 * Specified tables to exclude
139 */
140 public $exclude_tables = array();
141
142 /**
143 * Factory method for dumper on current hosts's configuration.
144 */
145 static function create($db_options) {
146 $db = Shuttle_DBConn::create($db_options);
147
148 $db->connect();
149
150 if (self::has_shell_access()
151 && self::is_shell_command_available('mysqldump')
152 && self::is_shell_command_available('gzip')
153 ) {
154 $dumper = new Shuttle_Dumper_ShellCommand($db);
155 } else {
156 $dumper = new Shuttle_Dumper_Native($db);
157 }
158
159 if (isset($db_options['include_tables'])) {
160 $dumper->include_tables = $db_options['include_tables'];
161 }
162 if (isset($db_options['exclude_tables'])) {
163 $dumper->exclude_tables = $db_options['exclude_tables'];
164 }
165
166 return $dumper;
167 }
168
169 function __construct(Shuttle_DBConn $db) {
170 $this->db = $db;
171 }
172
173 public static function has_shell_access() {
174 if (!is_callable('shell_exec')) {
175 return false;
176 }
177 $disabled_functions = ini_get('disable_functions');
178 return stripos($disabled_functions, 'shell_exec') === false;
179 }
180
181 public static function is_shell_command_available($command) {
182 if (preg_match('~win~i', PHP_OS)) {
183 /*
184 On Windows, the `where` command checks for availabilty in PATH. According
185 to the manual(`where /?`), there is quiet mode:
186 ....
187 /Q Returns only the exit code, without displaying the list
188 of matched files. (Quiet mode)
189 ....
190 */
191 $output = array();
192 exec('where /Q ' . $command, $output, $return_val);
193
194 if (intval($return_val) === 1) {
195 return false;
196 } else {
197 return true;
198 }
199
200 } else {
201 $last_line = exec('which ' . $command);
202 $last_line = trim($last_line);
203
204 // Whenever there is at least one line in the output,
205 // it should be the path to the executable
206 if (empty($last_line)) {
207 return false;
208 } else {
209 return true;
210 }
211 }
212
213 }
214
215 /**
216 * Create an export file from the tables with that prefix.
217 * @param string $export_file_location the file to put the dump to.
218 * Note that whenever the file has .gz extension the dump will be comporessed with gzip
219 * @param string $table_prefix Allow to export only tables with particular prefix
220 * @return void
221 */
222 abstract public function dump($export_file_location, $table_prefix='');
223
224 protected function get_tables($table_prefix) {
225 if (!empty($this->include_tables)) {
226 return $this->include_tables;
227 }
228
229 // $tables will only include the tables and not views.
230 // TODO - Handle views also, edits to be made in function 'get_create_table_sql' line 336
231 $tables = $this->db->fetch_numeric('
232 SHOW FULL TABLES WHERE Table_Type = "BASE TABLE" AND Tables_in_'.$this->db->name.' LIKE "' . $this->db->escape_like($table_prefix) . '%"
233 ');
234
235 $tables_list = array();
236 foreach ($tables as $table_row) {
237 $table_name = $table_row[0];
238 if (!in_array($table_name, $this->exclude_tables)) {
239 $tables_list[] = $table_name;
240 }
241 }
242 return $tables_list;
243 }
244}
245
246class Shuttle_Dumper_ShellCommand extends Shuttle_Dumper {
247 function dump($export_file_location, $table_prefix='') {
248 $command = 'mysqldump -h ' . escapeshellarg($this->db->host) .
249 ' -u ' . escapeshellarg($this->db->username) .
250 ' --password=' . escapeshellarg($this->db->password) .
251 ' ' . escapeshellarg($this->db->name);
252
253 $include_all_tables = empty($table_prefix) &&
254 empty($this->include_tables) &&
255 empty($this->exclude_tables);
256
257 if (!$include_all_tables) {
258 $tables = $this->get_tables($table_prefix);
259 $command .= ' ' . implode(' ', array_map('escapeshellarg', $tables));
260 }
261
262 $error_file = tempnam(sys_get_temp_dir(), 'err');
263
264 $command .= ' 2> ' . escapeshellarg($error_file);
265
266 if (Shuttle_Dump_File::is_gzip($export_file_location)) {
267 $command .= ' | gzip';
268 }
269
270 $command .= ' > ' . escapeshellarg($export_file_location);
271
272 exec($command, $output, $return_val);
273
274 if ($return_val !== 0) {
275 $error_text = file_get_contents($error_file);
276 unlink($error_file);
277 throw new Shuttle_Exception('Couldn\'t export database: ' . $error_text);
278 }
279
280 unlink($error_file);
281 }
282}
283
284class Shuttle_Dumper_Native extends Shuttle_Dumper {
285 public function dump($export_file_location, $table_prefix='') {
286 $eol = $this->eol;
287
288 $this->dump_file = Shuttle_Dump_File::create($export_file_location);
289
290 $this->dump_file->write("-- Generation time: " . date('r') . $eol);
291 $this->dump_file->write("-- Host: " . $this->db->host . $eol);
292 $this->dump_file->write("-- DB name: " . $this->db->name . $eol);
293 $this->dump_file->write("/*!40030 SET NAMES UTF8 */;$eol");
294
295 $this->dump_file->write("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;$eol");
296 $this->dump_file->write("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;$eol");
297 $this->dump_file->write("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;$eol");
298 $this->dump_file->write("/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;$eol");
299 $this->dump_file->write("/*!40103 SET TIME_ZONE='+00:00' */;$eol");
300 $this->dump_file->write("/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;$eol");
301 $this->dump_file->write("/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;$eol");
302 $this->dump_file->write("/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;$eol");
303 $this->dump_file->write("/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;$eol$eol");
304
305
306 $tables = $this->get_tables($table_prefix);
307 foreach ($tables as $table) {
308 $this->dump_table($table);
309 }
310
311 $this->dump_file->write("$eol$eol");
312 $this->dump_file->write("/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;$eol");
313 $this->dump_file->write("/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;$eol");
314 $this->dump_file->write("/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;$eol");
315 $this->dump_file->write("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;$eol");
316 $this->dump_file->write("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;$eol");
317 $this->dump_file->write("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;$eol");
318 $this->dump_file->write("/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;$eol$eol");
319
320 unset($this->dump_file);
321 }
322
323 protected function dump_table($table) {
324 $eol = $this->eol;
325
326 $this->dump_file->write("DROP TABLE IF EXISTS `$table`;$eol");
327
328 $create_table_sql = $this->get_create_table_sql($table);
329 $this->dump_file->write($create_table_sql . $eol . $eol);
330
331 $data = $this->db->query("SELECT * FROM `$table`");
332
333 $insert = new Shuttle_Insert_Statement($table);
334
335 while ($row = $this->db->fetch_row($data)) {
336 $row_values = array();
337 foreach ($row as $value) {
338 $row_values[] = $this->db->escape($value);
339 }
340 $insert->add_row( $row_values );
341
342 if ($insert->get_length() > self::INSERT_THRESHOLD) {
343 // The insert got too big: write the SQL and create
344 // new insert statement
345 $this->dump_file->write($insert->get_sql() . $eol);
346 $insert->reset();
347 }
348 }
349
350 $sql = $insert->get_sql();
351 if ($sql) {
352 $this->dump_file->write($insert->get_sql() . $eol);
353 }
354 $this->dump_file->write($eol . $eol);
355 }
356
357 public function get_create_table_sql($table) {
358 $create_table_sql = $this->db->fetch('SHOW CREATE TABLE `' . $table . '`');
359 return $create_table_sql[0]['Create Table'] . ';';
360 }
361}
362
363class Shuttle_DBConn {
364 public $host;
365 public $username;
366 public $password;
367 public $name;
368
369 protected $connection;
370
371 function __construct($options) {
372 $this->host = $options['host'];
373 if (empty($this->host)) {
374 $this->host = '127.0.0.1';
375 }
376 $this->username = $options['username'];
377 $this->password = $options['password'];
378 $this->name = $options['db_name'];
379 }
380
381 static function create($options) {
382 if (class_exists('mysqli')) {
383 $class_name = "Shuttle_DBConn_Mysqli";
384 } else {
385 $class_name = "Shuttle_DBConn_Mysql";
386 }
387
388 return new $class_name($options);
389 }
390}
391
392class Shuttle_DBConn_Mysql extends Shuttle_DBConn {
393 function connect() {
394 $this->connection = @mysql_connect($this->host, $this->username, $this->password);
395 if (!$this->connection) {
396 throw new Shuttle_Exception("Couldn't connect to the database: " . mysql_error());
397 }
398
399 $select_db_res = mysql_select_db($this->name, $this->connection);
400 if (!$select_db_res) {
401 throw new Shuttle_Exception("Couldn't select database: " . mysql_error($this->connection));
402 }
403
404 return true;
405 }
406
407 function query($q) {
408 if (!$this->connection) {
409 $this->connect();
410 }
411 $res = mysql_query($q);
412 if (!$res) {
413 throw new Shuttle_Exception("SQL error: " . mysql_error($this->connection));
414 }
415 return $res;
416 }
417
418 function fetch_numeric($query) {
419 return $this->fetch($query, MYSQL_NUM);
420 }
421
422 function fetch($query, $result_type=MYSQL_ASSOC) {
423 $result = $this->query($query, $this->connection);
424 $return = array();
425 while ( $row = mysql_fetch_array($result, $result_type) ) {
426 $return[] = $row;
427 }
428 return $return;
429 }
430
431 function escape($value) {
432 if (is_null($value)) {
433 return "NULL";
434 }
435 return "'" . mysql_real_escape_string($value) . "'";
436 }
437
438 function escape_like($search) {
439 return str_replace(array('_', '%'), array('\_', '\%'), $search);
440 }
441
442 function get_var($sql) {
443 $result = $this->query($sql);
444 $row = mysql_fetch_array($result);
445 return $row[0];
446 }
447
448 function fetch_row($data) {
449 return mysql_fetch_assoc($data);
450 }
451}
452
453
454class Shuttle_DBConn_Mysqli extends Shuttle_DBConn {
455 function connect() {
456 $this->connection = @new MySQLi($this->host, $this->username, $this->password, $this->name);
457
458 if ($this->connection->connect_error) {
459 throw new Shuttle_Exception("Couldn't connect to the database: " . $this->connection->connect_error);
460 }
461
462 return true;
463 }
464
465 function query($q) {
466 if (!$this->connection) {
467 $this->connect();
468 }
469 $res = $this->connection->query($q);
470
471 if (!$res) {
472 throw new Shuttle_Exception("SQL error: " . $this->connection->error);
473 }
474
475 return $res;
476 }
477
478 function fetch_numeric($query) {
479 return $this->fetch($query, MYSQLI_NUM);
480 }
481
482 function fetch($query, $result_type=MYSQLI_ASSOC) {
483 $result = $this->query($query, $this->connection);
484 $return = array();
485 while ( $row = $result->fetch_array($result_type) ) {
486 $return[] = $row;
487 }
488 return $return;
489 }
490
491 function escape($value) {
492 if (is_null($value)) {
493 return "NULL";
494 }
495 return "'" . $this->connection->real_escape_string($value) . "'";
496 }
497
498 function escape_like($search) {
499 return str_replace(array('_', '%'), array('\_', '\%'), $search);
500 }
501
502 function get_var($sql) {
503 $result = $this->query($sql);
504 $row = $result->fetch_array($result, MYSQLI_NUM);
505 return $row[0];
506 }
507
508 function fetch_row($data) {
509 return $data->fetch_array(MYSQLI_ASSOC);
510 }
511}
512
513class Shuttle_Exception extends Exception {};