PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.3.2
Backup Migration v1.3.2
2.1.6 2.1.5.2 trunk 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.7 1.4.8 1.4.9 1.4.9.1 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.5.1
backup-backup / includes / database / better-backup.php
backup-backup / includes / database Last commit date
better-backup-v3.php 2 years ago better-backup.php 2 years ago better-restore.php 2 years ago even-better-restore-v3.php 2 years ago even-better-restore-v4.php 2 years ago manager.php 2 years ago search-replace.php 2 years ago smart-sort.php 2 years ago
better-backup.php
425 lines
1 <?php
2
3 /**
4 * Author: Mikołaj `iClyde` Chodorowski
5 * Contact: kontakt@iclyde.pl
6 * Package: Backup Migration – WP Plugin
7 */
8
9 // Namespace
10 namespace BMI\Plugin\Database;
11
12 // Use
13 use BMI\Plugin\BMI_Logger AS Logger;
14 use BMI\Plugin\Progress\BMI_ZipProgress AS Progress;
15 use BMI\Plugin\Dashboard AS Dashboard;
16
17 // Exit on direct access
18 if (!defined('ABSPATH')) exit;
19
20 // echo "Memory usage at the beginning: " . (memory_get_usage() / 1024 / 1024) . " MB \n";
21 // function bmi_find_wordpress_base_path() {
22 //
23 // $dir = dirname(__FILE__);
24 // $previous = null;
25 //
26 // do {
27 //
28 // if (file_exists($dir . '/wp-config.php')) return $dir;
29 // if ($previous == $dir) break;
30 // $previous = $dir;
31 //
32 // } while ($dir = dirname($dir));
33 //
34 // return null;
35 //
36 // }
37 //
38 // define('BASE_PATH', bmi_find_wordpress_base_path() . '/');
39 // define('WP_USE_THEMES', false);
40 //
41 // // Use WP Globals and load WordPress
42 // global $wp, $wp_query, $wp_the_query, $wp_rewrite, $wp_did_header;
43 // require_once BASE_PATH . 'wp-load.php';
44 // echo "Memory usage after core load: " . (memory_get_usage() / 1024 / 1024) . " MB \n";
45 // ini_set('memory_limit', '2M');
46
47 /**
48 * Database exporting
49 * Main Class, requires $wpdb
50 */
51 class BMI_Database_Exporter {
52
53 /**
54 * Private local variables
55 */
56 private $total_tables = 0;
57 private $recipes = [];
58 private $tables_by_size = [];
59 public $total_queries = 0;
60 public $total_rows = 0;
61 public $total_size = 0;
62 public $files = [];
63
64 /**
65 * __construct - Initialization and logger resolver
66 *
67 * @return self
68 */
69 function __construct($storage, &$logger) {
70
71 /**
72 * WP Global Database variable
73 */
74 global $wpdb;
75 $this->wpdb = &$wpdb;
76
77 /**
78 * Logger of BMI core
79 */
80 $this->logger = &$logger;
81
82 /**
83 * Storage directory
84 */
85 // $this->storage = trailingslashit(__DIR__) . 'data';
86 $this->storage = $storage;
87
88 /**
89 * Percentage escape to replace
90 * This way we know what the randomized string is
91 */
92 $this->percentage = trim($this->wpdb->prepare('%s', '%'), "'");
93
94 /**
95 * Max rows to pass each query
96 */
97 $this->max_rows = BMI_DB_MAX_ROWS_PER_QUERY;
98
99 $this->table_prefix = time();
100 $this->init_start = microtime(true);
101 $this->logger->log("Memory usage after initialization: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB", 'INFO');
102
103 }
104
105 /**
106 * export - Export initializer
107 *
108 * @return filename/filenames
109 */
110 public function export() {
111
112 // Table names
113 $this->get_table_names_and_sizes();
114 $this->logger->log("Scan found $this->total_tables tables ($this->total_rows rows), estimated total size: $this->total_size MB.", 'INFO');
115 $this->logger->log("Memory usage after getting table names: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB ", 'INFO');
116
117 // Recipes
118 $this->logger->log("Getting table recipes...", 'INFO');
119 $this->table_recipes();
120 $this->logger->log("Table recipes have been exported.", 'INFO');
121 $this->logger->log("Memory usage after loading recipes: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB ", 'INFO');
122
123 // Save Recipes
124 $this->logger->log("Saving recipes...", 'INFO');
125 $this->save_recipes();
126 $this->logger->log("Recipes saved.", 'INFO');
127 $this->logger->log("Memory usage after recipe off-load: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB", 'INFO');
128
129 // Tables data
130 $this->logger->log("Exporting table data...", 'INFO');
131 $this->get_tables_data();
132 $this->logger->log("Table data exported.", 'INFO');
133 $this->logger->log("Memory usage after data export: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB", 'INFO');
134
135 $end = number_format(microtime(true) - $this->init_start, 4);
136 $this->logger->log("Entire process took: $end s", 'INFO');
137
138 }
139
140 /**
141 * get_table_names_and_sizes - Gets table names and sizes
142 *
143 * @return {array} associative array table_name => [size => its size in MB, rows => rows count]
144 */
145 private function get_table_names_and_sizes() {
146
147 $tables = $this->wpdb->get_results('SHOW TABLES');
148 $shouldExcludeTables = Dashboard\bmi_get_config('BACKUP:DATABASE:EXCLUDE');
149
150 $excludedTables = [];
151 if (defined('BMI_BACKUP_PRO') && BMI_BACKUP_PRO == 1) {
152 $excludedTables = Dashboard\bmi_get_config('BACKUP:DATABASE:EXCLUDE:LIST');
153 if (!is_array($excludedTables) || empty($excludedTables)) $excludedTables = [];
154 }
155
156 foreach ($tables as $table_index => $table_object) {
157 foreach ($table_object as $database_name => $table_name) {
158
159 if (in_array($table_name, $excludedTables) && $shouldExcludeTables) {
160 $str = __('Excluding %s table from backup (due to exclusion rules).', 'backup-backup');
161 $str = str_replace('%s', $table_name, $str);
162 $this->logger->log($str, 'INFO');
163
164 continue;
165 }
166
167 $query = "SELECT table_name AS `table`, round(((data_length + index_length) / 1024 / 1024), 2) AS `size`, ";
168 $query .= "(SELECT COUNT(*) FROM `$table_name`) AS `rows`";
169 $query .= "FROM information_schema.TABLES ";
170 $query .= "WHERE table_schema = %s AND table_name = %s";
171 $results = $this->wpdb->get_results($this->wpdb->prepare($query, DB_NAME, $table_name));
172
173 if (!is_object($results[0])) {
174 $this->logger->log("Could not get info about: $table_name (#01)", 'INFO');
175 continue;
176 }
177
178 $table_name_returned = trim($results[0]->table);
179 if ($table_name != $table_name_returned || strlen(trim($table_name)) <= 0) {
180 $this->logger->log("Could not get info about: $table_name (#02)", 'INFO');
181 continue;
182 }
183
184 $this->tables_by_size[$table_name_returned] = array(
185 'size' => floatval($results[0]->size),
186 'rows' => intval($results[0]->rows)
187 );
188
189 $this->total_size += floatval($results[0]->size);
190 $this->total_rows += intval($results[0]->rows);
191 $this->total_tables++;
192
193 }
194 }
195
196 return $this->tables_by_size;
197
198 }
199
200 /**
201 * table_recipes - Gets CREATION recipe of each table
202 *
203 * @return {array} - Creation recipes for each table_name => recipe
204 */
205 private function table_recipes() {
206
207 foreach ($this->tables_by_size as $table_name => $table_object) {
208
209 $query = "SHOW CREATE TABLE $table_name";
210 $result = $this->wpdb->get_results($query);
211 foreach ($result as $index => $result_object) {
212 foreach ($result_object as $column_name => $column_value) {
213
214 if ($column_value == $table_name) continue;
215 else {
216
217 $column_value = str_replace("`" . $table_name . "`", "`" . $this->table_prefix . '_' . $table_name . "`", $column_value);
218
219 $recipe = 'CREATE TABLE IF NOT EXISTS ';
220 $recipe .= substr($column_value, 13);
221
222 $this->recipes[$table_name] = $recipe;
223
224 }
225
226 }
227 }
228
229 }
230
231 return $this->recipes;
232
233 }
234
235 /**
236 * save_recipes - Save recipes and off-load the memory
237 *
238 * @return {void}
239 */
240 private function save_recipes() {
241
242 $time_prefix = $this->table_prefix;
243 foreach ($this->recipes as $table_name => $table_recipe) {
244
245 $this->total_queries += 4 + 3;
246 $recipe = "/* QUERY START */\n";
247 $recipe .= "SET foreign_key_checks = 0;\n";
248 $recipe .= "/* QUERY END */\n\n";
249
250 $recipe .= "/* QUERY START */\n";
251 $recipe .= "SET SQL_MODE = '';\n";
252 $recipe .= "/* QUERY END */\n\n";
253
254 $recipe .= "/* QUERY START */\n";
255 $recipe .= "SET time_zone = '+00:00';\n";
256 $recipe .= "/* QUERY END */\n\n";
257
258 $recipe .= "/* QUERY START */\n";
259 $recipe .= "SET NAMES 'utf8';\n";
260 $recipe .= "/* QUERY END */\n\n";
261
262 $recipe .= "/* CUSTOM VARS START */\n";
263 $recipe .= "/* REAL_TABLE_NAME: `$table_name`; */\n";
264 $recipe .= "/* PRE_TABLE_NAME: `$time_prefix" . "_" . "$table_name`; */\n";
265 $recipe .= "/* CUSTOM VARS END */\n\n";
266
267 $recipe .= "/* QUERY START */\n";
268 $recipe .= $table_recipe . ";\n";
269 $recipe .= "/* QUERY END */\n\n";
270
271 $this->total_rows++;
272 $location = $this->file_name($table_name);
273 $file = fopen($location, 'w');
274 fwrite($file, $recipe);
275
276 fclose($file);
277 unset($file);
278
279 $this->files[] = $location;
280 unset($location);
281
282 }
283
284 unset($this->recipes);
285
286 }
287
288 /**
289 * get_tables_data - Table data getter
290 *
291 * @return {int} Total rows count
292 */
293 private function get_tables_data() {
294
295 foreach ($this->tables_by_size as $table_name => $table_object) {
296
297 $start_time = microtime(true);
298 $this->logger->log("Getting data of table: " . $table_name . " (" . number_format ($table_object['size'], 2) . " MB)", 'STEP');
299 $rows = intval($table_object['rows']);
300
301 $this->wpdb->query("SET foreign_key_checks = 0;");
302
303 for ($i = 0; $i < $rows; $i += $this->max_rows) {
304
305 $query = $this->wpdb->prepare("SELECT * FROM `$table_name` LIMIT %d, $this->max_rows", $i);
306 $result = $this->wpdb->get_results($query);
307
308 $this->save_data($result, $table_name);
309 unset($result);
310
311 }
312
313 $this->wpdb->query("SET foreign_key_checks = 1;");
314
315 $this->logger->log("Table: " . $table_name . " cloned, operation took: " . number_format((microtime(true) - $start_time), 5) . " ms", 'INFO');
316 unset($start_time);
317
318 }
319
320 }
321
322 /**
323 * save_data - Saves table data/row as query
324 *
325 * @param {wpdb object} &$result Database query result
326 * @param {string} &$table_name Table name
327 * @return {void}
328 */
329 private function save_data(&$result, &$table_name) {
330
331 $columns_schema_added = false;
332 $file = fopen($this->file_name($table_name), 'a+');
333
334 $this->total_queries++;
335 $query = "/* QUERY START */\n";
336 $query .= "INSERT INTO `" . $this->table_prefix . "_" . $table_name . "` ";
337
338 foreach ($result as $index => $result_object) {
339
340 $data_in_order = array();
341 $format_in_order = array();
342 $columns_in_order = array();
343
344 foreach ($result_object as $column_name => $value) {
345
346 $data_in_order[] = $value;
347 $columns_in_order[] = "`$column_name`";
348
349 if (is_numeric($value)) {
350
351 if (is_float($value)) $format_in_order[] = '%f';
352 else $format_in_order[] = '%d';
353
354 } else $format_in_order[] = '%s';
355
356 }
357
358 if ($columns_schema_added === false) {
359
360 $query .= "(" . implode(', ', $columns_in_order) . ") VALUES ( \n";
361 $columns_schema_added = true;
362
363 } else {
364
365 $query = "), (\n";
366
367 }
368
369 $columns = sizeof($columns_in_order);
370 unset($columns_in_order);
371
372 $query .= "/* VALUES START */\n";
373 for ($i = 0; $i < $columns; ++$i) {
374
375 if ($format_in_order[$i] == '%f') {
376
377 $query .= floatval($data_in_order[$i]);
378
379 } elseif ($format_in_order[$i] == '%d') {
380
381 $query .= intval($data_in_order[$i]);
382
383 } else {
384
385 $query .= $this->wpdb->prepare("%s", $data_in_order[$i]);
386 $query = str_replace($this->percentage, '%', $query);
387
388 }
389
390 if ($i < ($columns - 1)) $query .= ",\n";
391 else $query .= "\n/* VALUES END */\n";
392
393 }
394
395 unset($data_in_order);
396 unset($format_in_order);
397 unset($columns_in_order);
398
399 fwrite($file, $query);
400
401 }
402
403 fwrite($file, ");\n/* QUERY END */\n\n");
404 fclose($file);
405 unset($file);
406
407 }
408
409 /**
410 * file_name - Replaces table name to file name friendly format
411 *
412 * @param {string} $table_name Table name
413 * @return {string} Friendly format for file
414 */
415 private function file_name($table_name) {
416
417 $friendly_name = preg_replace("/[^A-Za-z0-9_-]/", '', $table_name);
418 $friendly_name = trailingslashit($this->storage) . $friendly_name . '.sql';
419
420 return $friendly_name;
421
422 }
423
424 }
425