PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.6
Backup Migration v2.1.6
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 / smart-sort.php
backup-backup / includes / database Last commit date
export 3 weeks ago better-backup-v3.php 3 weeks ago better-backup.php 3 weeks ago better-restore.php 3 weeks ago even-better-restore-v3.php 3 weeks ago even-better-restore-v4.php 3 weeks ago interface-search-replace-repository.php 3 weeks ago manager.php 3 weeks ago search-replace-processor.php 3 weeks ago search-replace-repository.php 3 weeks ago search-replace-stack-based.php 3 weeks ago search-replace-v2.php 3 weeks ago search-replace.php 3 weeks ago smart-sort.php 3 weeks ago
smart-sort.php
1163 lines
1 <?php
2
3 /**
4 * Author: Mikołaj `iClyde` Chodorowski
5 * Contact: kontakt@iclyde.pl
6 * Package: Backup Migration – WP Plugin
7 * Version: 2.0
8 */
9
10 // Namespace
11 namespace BMI\Plugin\Database;
12
13 // Use
14 use BMI\Plugin\BMI_Logger AS Logger;
15 use BMI\Plugin\Progress\BMI_ZipProgress AS Progress;
16 use BMI\Plugin\Backup_Migration_Plugin as BMP;
17
18
19 // // Exit on direct access
20 if (!defined('ABSPATH')) exit;
21
22 /**
23 * Database sort method
24 * Usage: Create new object and use "sortUnsorted" function
25 */
26 class BMI_Database_Sorting {
27
28 /**
29 * Path to database files made with V2 engine
30 */
31 private $db_files_root;
32 private $output_file_stream;
33 private $name_of_finished_tables_dir = 'bmi_converted_completed_tables';
34 private $progress;
35 private $isCLI;
36 private $unwantedTables = [
37 'wfblockediplog',
38 'wfblocks7',
39 'wfcrawlers',
40 'wffilechanges',
41 'wffilemods',
42 'wfhits',
43 'wfhoover',
44 'wfissues',
45 'wfknownfilelist',
46 'wflivetraffichuman',
47 'wflocs',
48 'wflogins',
49 'wfnotifications',
50 'wfpendingissues',
51 'wfreversecache',
52 'wfsnipcache',
53 'wfstatus',
54 'wftrafficrate',
55 'actionscheduler_logs',
56 'slim_stats',
57 'woocommerce_sessions',
58 'yoast_indexable',
59 'slim_events',
60 'cerber_files',
61 'cerber_traffic',
62 'cerber_log',
63 'cerber_countries',
64 'cerber_blocks',
65 'cerber_acl'
66 ];
67
68 /**
69 * __construct - description
70 *
71 * @param {string} $db_root Path to database files
72 * @return void
73 */
74 function __construct($db_root, &$progress, $isCLI) {
75
76 /**
77 * Set path to the root directory
78 */
79 $this->db_files_root = $db_root;
80 $this->progress = $progress;
81 $this->isCLI = $isCLI;
82
83 }
84
85 /**
86 * sortUnsorted - Main function which dynamically sorts the tables (main function)
87 *
88 * @param {array} $processData = [] Data of process to be continued or empty array to start
89 * Requires keys [ 'index', 'insert', 'file', 'table_name', 'dir', 'unique', 'destinationFile' ]
90 * Optional key: convertionFinished
91 *
92 * @return array of [ 'index', 'insert', 'file', 'table_name', 'dir', 'unique', 'destinationFile' ]
93 */
94 public function sortUnsorted($processData = []) {
95
96 // Sort the SQL files into directories
97 $this->putDatabaseFilesInDirectories();
98
99 // Split files into partial files (query per batch)
100 $process = $this->splitFilesInSeparateDirectories($processData);
101
102 // Check if all tables finished and move them outside completed directory
103 if ($this->doesTablesFinished()) {
104
105 // Move them outside
106 $this->moveAllConvertedTables();
107
108 // Check if process finished
109 if ($this->isCLI || (is_array($process) && array_key_exists('completed', $process) && $process['completed'] == 'yes')) {
110 $process['convertionFinished'] = 'yes';
111 }
112
113 }
114
115 // Return process for that
116 return $process;
117
118 }
119
120 /**
121 * doesTablesFinished - Checks if there is any table that didn't finish
122 *
123 * @return {bool} true or false
124 */
125 private function doesTablesFinished() {
126
127 // Scan all files in root
128 $allFiles = scandir($this->db_files_root);
129 $files = array_diff($allFiles, array('.', '..', $this->name_of_finished_tables_dir));
130
131 // If no other directories here that means nothing to convert left
132 if (sizeof($files) <= 0) {
133
134 // Return true for that
135 return true;
136
137 } else {
138
139 // Otherwise it's false
140 return false;
141
142 }
143
144 }
145
146 /**
147 * putDatabaseFilesInDirectories - It puts all SQL files into table directories
148 * Does nothing if the root directory does not contain any SQL files
149 *
150 * @return void
151 */
152 private function putDatabaseFilesInDirectories() {
153
154 // Scan all files in root
155 $allFiles = scandir($this->db_files_root);
156 $files = array_diff($allFiles, array('.', '..'));
157
158 // For each found file make sure it's SQL and run merge function
159 foreach ($files as $index => $filename) {
160
161 // Exclude non .sql files
162 if (substr($filename, -4) != '.sql') continue;
163
164 // It may be directory with .sql ending, make sure it's not
165 $pathToFile = $this->db_files_root . DIRECTORY_SEPARATOR . $filename;
166 if (is_dir($pathToFile)) continue;
167
168 // Merge the file
169 $this->mergeFileWithDirectory($filename);
170
171 }
172
173 }
174
175 /**
176 * mergeFileWithDirectory - Merges the file into proper directory
177 *
178 * @param {string} $filename File name which is located in root
179 * @return bool true on success false on failed file move
180 */
181 private function mergeFileWithDirectory($filename) {
182
183 // Suffix size which stands for ending ".sql"
184 $suffix_size = 4;
185
186 // Match output for table name with different name than just table
187 // It may happen when our support used their tools for optimization
188 $match_output = [];
189 preg_match('/_(\d+)\_of\_(\d+)/', $filename, $match_output);
190
191 // If that's the case, add suffix size as we don't need that info here
192 if (sizeof($match_output) > 0) {
193 $suffix_size += strlen($match_output[0]);
194 }
195
196 // If we know the suffix size, get table name
197 $table_name = substr($filename, 0, -$suffix_size);
198
199 // Prepare future paths of the file
200 $dest_dir = $this->db_files_root . DIRECTORY_SEPARATOR . $table_name;
201 $source_path = $this->db_files_root . DIRECTORY_SEPARATOR . $filename;
202 $dest_path = $dest_dir . DIRECTORY_SEPARATOR . $filename;
203
204 // Check if the directory exists and if it's not make new one
205 if (!file_exists($dest_dir) && !is_dir($dest_dir)) {
206 @mkdir($dest_dir, 0755, true);
207 }
208
209 // Check if the files is not an duplicate and make sure both will be merged
210 $tries = 2;
211 while (file_exists($dest_path)) {
212
213 // If file exists already add number at the suffix and try again until unique name
214 $dest_path = substr($dest_path, 0, -4) . '_' . $tries . '.sql';
215 $tries++;
216
217 }
218
219 // If we're sure move the file to correct directory
220 if (rename($source_path, $dest_path)) {
221
222 // Return true on success
223 return true;
224
225 } else {
226
227 // Return false on fail
228 return false;
229
230 }
231
232 }
233
234 /**
235 * countAllFilesAndQueries - Counts all files inside directory and returns all query count
236 *
237 * @return {array} of total_queries, total_size, all_tables and all_files
238 */
239 public function countAllFilesAndQueries() {
240
241 // Scan all files in root
242 $allFiles = scandir($this->db_files_root);
243 $folders = array_diff($allFiles, array('.', '..', $this->name_of_finished_tables_dir));
244
245 // Tables
246 $tables = [];
247
248 // Files
249 $files = [];
250
251 // Total size
252 $total_size = 0;
253
254 // For each found file make sure it's SQL and run merge function
255 foreach ($folders as $index => $filename) {
256
257 // Path to that file/directory
258 $pathToFile = $this->db_files_root . DIRECTORY_SEPARATOR . $filename;
259
260 // Hanlde only dirs
261 if (is_dir($pathToFile)) {
262
263 // Add table information
264 $tables[$filename] = 0;
265
266 // Scan SQL files
267 $allSQLFiles = scandir($pathToFile);
268 $allSQLFilesParsed = array_diff($allSQLFiles, array('.', '..'));
269
270 // Go throught these files
271 foreach ($allSQLFilesParsed as $indexSQL => $filenameSQL) {
272
273 // Exclude non .sql files
274 if (substr($filenameSQL, -4) != '.sql') continue;
275
276 // Get size of that file
277 $size = filesize($pathToFile . DIRECTORY_SEPARATOR . $filenameSQL);
278
279 // Add size in total and table
280 $tables[$filename] += $size;
281 $total_size += $size;
282
283 // Add that file to all files
284 $files[] = $filenameSQL;
285
286 }
287
288 }
289
290 }
291
292 // Prepare stats variable
293 $stats = [
294 'total_queries' => (sizeof($files) * 5),
295 'total_size' => $total_size,
296 'all_tables' => $tables,
297 'all_files' => $files
298 ];
299
300 return $stats;
301
302 }
303
304 /**
305 * countTablesDuringProcess - Counts tables even during process
306 *
307 * @return array $done, $progress, $total
308 */
309 private function countTablesDuringProcess() {
310
311 $theFinalCount = 0;
312 $progressCount = 0;
313 $doneCount = 0;
314
315 // Scan all files in root (now everything should be directory)
316 $allFiles = scandir($this->db_files_root);
317 $files = array_diff($allFiles, array('.', '..', $this->name_of_finished_tables_dir));
318
319 foreach ($files as $index => $filename) {
320 if (is_dir($this->db_files_root . DIRECTORY_SEPARATOR . $filename)) {
321 $theFinalCount++;
322 $progressCount++;
323 }
324 }
325
326 $doneDir = $this->db_files_root . DIRECTORY_SEPARATOR . $this->name_of_finished_tables_dir;
327 if (file_exists($doneDir) && is_dir($doneDir)) {
328
329 $allDoneFiles = scandir($doneDir);
330 $doneFiles = array_diff($allDoneFiles, array('.', '..'));
331
332 foreach ($doneFiles as $index => $filename) {
333 if (is_dir($doneDir . DIRECTORY_SEPARATOR . $filename)) {
334 $theFinalCount++;
335 $doneCount++;
336 }
337 }
338
339 }
340
341 return [
342 'done' => $doneCount,
343 'in_progress' => $progressCount,
344 'total' => $theFinalCount
345 ];
346
347 }
348
349 /**
350 * logFinished - Shows progress of the process in the log file
351 *
352 * @return void
353 */
354 private function logFinished($dir, $filename) {
355
356 $counts = $this->countTablesDuringProcess();
357
358 $progress = ($counts['done'] + 1) . '/' . $counts['total'] . ' (' . number_format((($counts['done'] + 1) / $counts['total']) * 100, 2) . '%)';
359 $translated = __('Finished %progress%: %filename% table, total of %parts% parts.', 'backup-backup');
360
361 // Get part amount
362 $partsAll = scandir($dir);
363 $parts = sizeof(array_diff($partsAll, array('.', '..', $this->name_of_finished_tables_dir)));
364
365 $theLog = str_replace('%progress%', $progress, $translated);
366 $theLog = str_replace('%filename%', $filename, $theLog);
367 $theLog = str_replace('%parts%', $parts, $theLog);
368
369 $this->progress->log($theLog, 'SUCCESS');
370
371 $percentage = number_format((($counts['done'] + 1) / $counts['total']) * 100, 2);
372 $this->progress->progress((50 + ($percentage / 4)), 'INFO');
373
374 }
375
376 /**
377 * splitInDirectories - Splits file into parts inside proper directory
378 * @param {array} $processData Process data to be continued
379 *
380 * @return void
381 */
382 private function splitFilesInSeparateDirectories($processData = []) {
383
384 // Local process variable
385 $process = [];
386
387 // Scan all files in root (now everything should be directory)
388 $allFiles = scandir($this->db_files_root);
389 $files = array_diff($allFiles, array('.', '..', $this->name_of_finished_tables_dir));
390
391 // Loop all tables and split them one by one
392 foreach ($files as $index => $filename) {
393
394 // Prepate path to the SQL file which should be converted
395 $dir = $this->db_files_root . DIRECTORY_SEPARATOR . $filename;
396
397 // Always double check if it's directory otherwise we may get some unexpected errors
398 if (!is_dir($dir)) continue;
399
400 // Log the new file which will be processed
401 $destinationFile = $this->makeDestinationFilename($dir, $filename);
402 $destinationIndex = $destinationFile['index'];
403
404 // Log only if it's first batch
405 if ($destinationIndex == 1) {
406 $this->progress->log(str_replace('%s', $filename, __('Starting conversion of: %s table.', 'backup-backup')), 'INFO');
407 }
408
409 // Dynamically find file to shrink and split it
410 $process = $this->splitFileInTableDirectory($dir, $filename, $processData);
411
412 // If it's not CLI use batching
413 if (!$this->isCLI) {
414
415 // If process finished
416 if (is_array($process) && array_key_exists('completed', $process) && $process['completed'] == 'yes') {
417
418 // Show in the log file the progress
419 $this->logFinished($dir, $filename);
420
421 // Check if directory for completed tables exist and make it if it is not
422 $doneDirectory = $this->db_files_root . DIRECTORY_SEPARATOR . $this->name_of_finished_tables_dir;
423 if (!file_exists($doneDirectory) && !is_dir($doneDirectory)) {
424 @mkdir($doneDirectory, 0755, true);
425 }
426
427 // Move the table into completed directory
428 rename($dir, $doneDirectory . DIRECTORY_SEPARATOR . $filename);
429
430 // Return process even empty
431 return $process;
432
433 } else {
434
435 // If didn't finished that table return process data for next batch to finish
436 return $process;
437
438 }
439
440 } else {
441
442 // Show in the log file the progress
443 $this->logFinished($dir, $filename);
444
445 // Check if directory for completed tables exist and make it if it is not
446 $doneDirectory = $this->db_files_root . DIRECTORY_SEPARATOR . $this->name_of_finished_tables_dir;
447 if (!file_exists($doneDirectory) && !is_dir($doneDirectory)) {
448 @mkdir($doneDirectory, 0755, true);
449 }
450
451 // Move the table into completed directory
452 rename($dir, $doneDirectory . DIRECTORY_SEPARATOR . $filename);
453
454 }
455
456 }
457
458 }
459
460 /**
461 * splitFileInTableDirectory - Splits file into parts for easier progress read and performance
462 *
463 * @param {string} $dir Path to parent directory of table SQL files
464 * @param {string} $table_name Table name (current directory name)
465 * @param {array} $processData Process data to be continued
466 * @return void
467 */
468 private function splitFileInTableDirectory($dir, $table_name, $processData = []) {
469
470 // Prepare local var for process data
471 $process = [];
472
473 // Get all database files inside that directory (it can contain multiple files alrady)
474 $allFiles = scandir($dir);
475 $files = array_diff($allFiles, array('.', '..'));
476
477 // Loop throught all of them and apply splitting
478 foreach ($files as $index => $filename) {
479
480 // Just to triple check that parent is a valid directory
481 if (!is_dir($dir)) continue;
482
483 // Prepare exact path to the SQL file
484 $file = $dir . DIRECTORY_SEPARATOR . $filename;
485
486 // Run splitting function for that file
487 $process = $this->splitDatabaseFile($file, $dir, $table_name, $processData);
488
489 // Break each query if not CLI
490 if (!$this->isCLI) {
491 break;
492 }
493
494 }
495
496 // If CLI only rename files without return
497 if ($this->isCLI) {
498
499 // Rename splited files
500 $this->renameSplitedFiles($dir);
501
502 } else {
503
504 // Check if process for that table completed and rename if so
505 if (is_array($process) && array_key_exists('completed', $process) && $process['completed'] == 'yes') {
506
507 // Rename splited files
508 $this->renameSplitedFiles($dir);
509
510 // Return empty process data as we will iterate new file in next batch
511 return [
512 'index' => 0,
513 'insert' => 'no',
514 'completed' => 'yes'
515 ];
516
517 } else {
518
519 // Or just return process data for future use (next batch)
520 return $process;
521
522 }
523
524 }
525
526 }
527
528 /**
529 * logPartial - Logs partial progress to the output log
530 *
531 * @param {string} $dir Directory of the table
532 * @param {string} $table_name Name of the table
533 * @return void
534 */
535 private function logPartial($dir, $table_name) {
536
537 // Get part amount
538 $partsAll = scandir($dir);
539 $parts = sizeof(array_diff($partsAll, array('.', '..', $this->name_of_finished_tables_dir)));
540 $parts = $parts - 1;
541
542 // Prepare log string
543 $logString = __('Finished part %part% of %table% table.', 'backup-backup');
544 $logString = str_replace('%table%', $table_name, $logString);
545 $logString = str_replace('%part%', $parts, $logString);
546
547 // Log the string to output
548 $this->progress->log($logString, 'INFO');
549
550 }
551
552 /**
553 * splitDatabaseFile - Splits a SQL file into parts
554 *
555 * @param {string} $file Name of the SQL File
556 * @param {string} $dir Full path to the SQL file
557 * @param {string} $table_name Name of the table to which SQL file belong
558 * @param {array} $processData Process data to be continued
559 * @return void
560 */
561 private function splitDatabaseFile($file, $dir, $table_name, $processData = []) {
562
563 // Make destionation file and prepare output file stream
564 $destinationFile = $this->makeDestinationFilename($dir, $table_name);
565 $destinationFile = $destinationFile['file'];
566
567 // Local variables required for the process
568 $table_unique = 0;
569 $is_converted = false;
570 $query_started = false;
571 $custom_vars_started = false;
572 $values_started = false;
573 $insert_next = false;
574 $last_seek = 0;
575
576 // Resolve not finished process
577 if (!$this->isCLI && is_array($processData) && !empty($processData)) {
578
579 // Should be file path $file_path & $file (check if the file exist)
580 if (array_key_exists('file', $processData) && file_exists($processData['file'])) {
581
582 // Set existing file path
583 $file = $processData['file'];
584
585 // Should be int<make sure> of $last_seek
586 if (array_key_exists('index', $processData)) {
587 $last_seek = intval($processData['index']) + 1;
588 }
589
590 // Yes | No values {string} of $insert_next
591 if (array_key_exists('insert', $processData)) {
592 $ins = $processData['insert'];
593
594 if ($ins == 'yes') $insert_next = true;
595 else $insert_next = false;
596 }
597
598 // Table name of $table_name
599 if (array_key_exists('table_name', $processData)) {
600 $table_name = $processData['table_name'];
601 }
602
603 // Directory of that table $dir
604 if (array_key_exists('dir', $processData)) {
605 $dir = $processData['dir'];
606 }
607
608 // Unique for that table $table_unique
609 if (array_key_exists('unique', $processData)) {
610 $table_unique = $processData['unique'];
611 }
612
613 // Destination File (output) of $destinationFile
614 if (array_key_exists('destinationFile', $processData)) {
615 $destinationFile = $processData['destinationFile'];
616 }
617
618 }
619
620 }
621
622 // Open stream for output file
623 $this->output_file_stream = fopen($destinationFile, 'a+');
624
625 // Keep original filename
626 $file_path = $file;
627
628 // Open SplObject for seek-based reading
629 $file = new \SplFileObject($file);
630
631 // Go to last line to check how many lines are there
632 $file->seek($file->getSize());
633
634 // Calculate total lines in that file
635 $total_lines = $file->key() + 1;
636
637 for ($i = $last_seek; $i < $total_lines; ++$i) {
638
639 // Start seek frrom the loop index
640 $file->seek($i);
641
642 // Get line into string and trim it (it contains new line at the end)
643 $line = trim($file->current());
644
645 // Variable for that line, if it's a comment or not
646 $hasComment = false;
647
648 // Check this only if the size of line is below 24
649 // Otherwise it's not a comment line and we can ignore that check for better performance
650 if (strlen($line) < 24) {
651
652 // List of all our comments
653 $c_fs = "/* CONVERTED DB FILE */";
654 $q_s = "/* QUERY START */";
655 $q_e = "/* QUERY END */";
656 $cq_s = "/* CUSTOM VARS START */";
657 $cq_e = "/* CUSTOM VARS END */";
658 $v_s = "/* VALUES START */";
659 $v_e = "/* VALUES END */";
660
661 // If it's converted already ignore the file
662 if ($line === $c_fs) {
663
664 // Mark as converted (it output file be removed by this)
665 $is_converted = true;
666
667 // And break the loop, no need to continue
668 break;
669
670 }
671
672 // Check if it's a query start comment and adjust local variables for that for future know
673 if ($line === $q_s) {
674 $query_started = true;
675 $hasComment = true;
676 }
677
678 // Check if it's a custom variable section start comment and adjust local variables for that for future know
679 if ($line === $cq_s) {
680 $custom_vars_started = true;
681 $hasComment = true;
682 }
683
684 // Check if it's a values section start comment and adjust local variables for that for future know
685 if ($line === $v_s) {
686 $values_started = true;
687 $hasComment = true;
688 }
689
690 // If we are behind a heading insert this line into new output file
691 if ($insert_next && $hasComment) {
692
693 // Add new line as we trimed it before
694 $line .= "\n";
695
696 // Push this to output file
697 $this->insertIntoOutputFile($line, $table_name);
698
699 // If we already added this line continue to new line
700 continue;
701
702 }
703
704 // Handle query ending comment
705 if ($line === $q_e) {
706
707 // Adjust local variable for that file
708 $query_started = false;
709
710 // If it's below heading, add new output and generate new heading for output file
711 if ($insert_next) {
712
713 // Add double ending – it looks much better for our eyes :)
714 $line .= "\n\n";
715
716 // Add this line
717 $this->insertIntoOutputFile($line, $table_name);
718
719 // Close the file as we split per query, we will need new one for new query
720 if (is_resource($this->output_file_stream) || get_resource_type($this->output_file_stream) === 'file') {
721 fclose($this->output_file_stream);
722 }
723
724 // Check if it's end of the file add 4 lines offset for empty lines
725 // If it's not the end make new heading, otherwise remove new output file
726 if (($i + 4) < $total_lines) {
727
728 // Make new destination file
729 $destinationFile = $this->makeDestinationFilename($dir, $table_name);
730 $destinationFile = $destinationFile['file'];
731
732 // Check if we really need that table contents
733 if ($this->checkIfContainName($table_name, $this->unwantedTables)) {
734
735 $this->progress->log(str_replace('%s', $table_name, __('Cleaning up contents of %s table.', 'backup-backup')), 'INFO');
736
737 $insert_next = false;
738
739 if (!$this->isCLI) {
740
741 $file = null;
742
743 if (is_resource($this->output_file_stream) || get_resource_type($this->output_file_stream) === 'file') {
744 fclose($this->output_file_stream);
745 }
746
747 if (file_exists($destinationFile)) {
748 @unlink($destinationFile);
749 }
750
751 if (file_exists($file_path)) {
752 @unlink($file_path);
753 }
754
755 // Return empty for new file
756 return [
757 'index' => 0,
758 'insert' => 'no',
759 'completed' => 'yes'
760 ];
761
762 }
763
764 } else {
765
766 // Log partial progress
767 $this->logPartial($dir, $table_name);
768
769 // Open new stream for new output file
770 $this->output_file_stream = fopen($destinationFile, 'a+');
771
772 // Generate new heading
773 $heading = $this->generateNewHeading($table_unique, $table_name);
774
775 // Put the heading into new file
776 $this->insertIntoOutputFile($heading, $table_name);
777
778 // Technically only few bytes but with 200 tables it may be few MBs :)
779 // Always unset variables if you don't need them anymore
780 unset($heading);
781
782 }
783
784 if (!$this->isCLI) {
785
786 // Check if the output file is open and close it if it is
787 $file = null;
788 if (is_resource($this->output_file_stream) || get_resource_type($this->output_file_stream) === 'file') {
789 fclose($this->output_file_stream);
790 }
791
792 // Return latest insert query
793 return [
794 'index' => $i,
795 'insert' => ($insert_next == true ? 'yes' : 'no'),
796 'file' => $file_path,
797 'table_name' => $table_name,
798 'dir' => $dir,
799 'unique' => $table_unique,
800 'destinationFile' => $destinationFile
801 ];
802
803 }
804
805 } else {
806
807 // Log partial progress
808 $this->logPartial($dir, $table_name);
809
810 }
811
812 // Continue to new line as we handled this one already
813 continue;
814
815 }
816
817 }
818
819 // Handle Custom Variables ending comment
820 if ($line === $cq_e) {
821
822 // Adjust local variables for next loops
823 $custom_vars_started = false;
824
825 }
826
827 // Handle values ending
828 if ($line === $v_e) {
829
830 // Adjust local variables for next loops
831 $values_started = false;
832
833 // If it's after heading add this line into new output file
834 if ($insert_next) {
835
836 // Add new line as we trimed it before
837 $line .= "\n";
838
839 // Insert that line into new file
840 $this->insertIntoOutputFile($line, $table_name);
841
842 // As we added the line continue to new one
843 continue;
844
845 }
846
847 }
848
849 }
850
851 // Check if the line is not a comment
852 if (!$hasComment) {
853
854 // If the line should be a part of query handle that case
855 if ($query_started === true) {
856
857 // If it's after heading allow to add this line to new output file
858 if ($insert_next) {
859
860 // Add new line as we trimed it earlier
861 $line .= "\n";
862
863 // Insert the line into new file
864 $this->insertIntoOutputFile($line, $table_name);
865
866 // As we added that line continue to new one
867 continue;
868
869 }
870
871 }
872
873 // Handle custom variables
874 if ($custom_vars_started === true) {
875
876 // We are interested only into PRE TABLE NAME which contains unique timestamp
877 if (strpos($line, 'PRE_TABLE_NAME')) {
878
879 // Prepare variable for preg match output
880 $output = [];
881
882 // Run the preg match function to find the
883 preg_match('/\`(.*)\`/', $line, $output);
884
885 // Get the unique value from regex output
886 $unique = substr($output[1], 0, 10);
887
888 // Set the local variable to found unique for future use in the heading
889 $table_unique = $unique;
890
891 // As we went throught old heading let the script know that it can insert new lines in next loops
892 $insert_next = true;
893
894 // Prepare new heading for file which will be used in next loops
895 $heading = $this->generateNewHeading($unique, $table_name);
896
897 // Insert that heading
898 $this->insertIntoOutputFile($heading, $table_name);
899
900 }
901
902 }
903
904 // Handle casual variable line
905 if ($values_started === true) {
906
907 // If it can insert new output handle it
908 if ($insert_next) {
909
910 // Insert that line into new file
911 $this->insertIntoOutputFile($line, $table_name);
912
913 // Free up the memory as this line may be actually above few MBs.
914 unset($line);
915
916 // Continue to new line as we inserted this one already
917 continue;
918
919 }
920
921 }
922
923 }
924
925 // Free up the memory as this line may be actually above few MBs.
926 unset($line);
927
928 }
929
930 // $total_lines
931 // File size: $file->getSize() / 1024 / 1024
932
933 // Check if the output file is open and close it if it is
934 if (is_resource($this->output_file_stream) || get_resource_type($this->output_file_stream) === 'file') {
935 fclose($this->output_file_stream);
936 }
937
938 // If the file was already converted
939 if ($is_converted === false) {
940
941 // Empty the SplFileObject stream
942 $file = null;
943
944 // Remove new output file as there's nothing to do with it
945 @unlink($file_path);
946
947 } else {
948
949 // If the convert finished, remove source file as we now have splited files
950 @unlink($destinationFile);
951
952 }
953
954 // Empty SplFileObject stream
955 $file = null;
956
957 // Only if not CLI
958 if (!$this->isCLI) {
959
960 // Return empty process data as we will iterate new file in next batch
961 return [
962 'index' => 0,
963 'insert' => 'no',
964 'completed' => 'yes'
965 ];
966
967 }
968
969 }
970
971 /**
972 * makeHeading - Generates new heading (converted) for output file
973 *
974 * @param {string/int} $unique Unique timestamp generated during backup (all tables have to be part of it)
975 * @param {string} $table_name Table name of current file, required to generate the heading
976 * @return {string} New heading
977 */
978 private function generateNewHeading($unique, $table_name) {
979
980 $heading = '';
981
982 $heading .= "/* CONVERTED DB FILE */\n\n";
983 $heading .= "/* QUERY START */\n";
984 $heading .= "SET foreign_key_checks = 0;\n";
985 $heading .= "/* QUERY END */\n";
986 $heading .= "\n";
987 $heading .= "/* QUERY START */\n";
988 $heading .= "SET SQL_MODE = '';\n";
989 $heading .= "/* QUERY END */\n";
990 $heading .= "\n";
991 $heading .= "/* QUERY START */\n";
992 $heading .= "SET time_zone = '+00:00';\n";
993 $heading .= "/* QUERY END */\n";
994 $heading .= "\n";
995 $heading .= "/* QUERY START */\n";
996 $heading .= "SET NAMES 'utf8';\n";
997 $heading .= "/* QUERY END */\n";
998 $heading .= "\n";
999 $heading .= "/* CUSTOM VARS START */\n";
1000 $heading .= "/* REAL_TABLE_NAME: " . BMP::escapeSQLIDentifier($table_name) . "; */\n";
1001 $heading .= "/* PRE_TABLE_NAME: " . BMP::escapeSQLIDentifier($unique . "_" . $table_name) . "; */\n";
1002 $heading .= "/* CUSTOM VARS END */\n";
1003 $heading .= "\n";
1004
1005 return $heading;
1006
1007 }
1008
1009 /**
1010 * insertIntoOutputFile - Puts and output data into new (current) file
1011 *
1012 * @param {string} &$line Pointer to line, to save the memory
1013 * @param {string} $table_name Table name, may be useful for future use
1014 * @return void
1015 */
1016 private function insertIntoOutputFile(&$line, $table_name) {
1017
1018 if (is_resource($this->output_file_stream) || get_resource_type($this->output_file_stream) === 'file') {
1019 fwrite($this->output_file_stream, $line);
1020 }
1021
1022 }
1023
1024 /**
1025 * destinationFile - Makes unique destionation file name
1026 *
1027 * @param {string} $dir Full path to the SQL file
1028 * @param {string} $table_name Name of the table to which SQL file belong
1029 * @return {string} name of the destionation SQL file
1030 */
1031 private function makeDestinationFilename($dir, $table_name) {
1032
1033 // Start from file number one
1034 $i = 1;
1035
1036 // Make an assumption that it will be the first file
1037 $file = $table_name . '_' . $i . '.sql';
1038
1039 // Loop until it find unqiue name
1040 while (file_exists($dir . DIRECTORY_SEPARATOR . $file)) {
1041
1042 // Increment each iteration for new filename
1043 $i++;
1044
1045 // Set new name to check
1046 $file = $table_name . '_' . $i . '.sql';
1047
1048 }
1049
1050 // Return final file name
1051 return ['file' => $dir . DIRECTORY_SEPARATOR . $file, 'index' => $i];
1052
1053 }
1054
1055 /**
1056 *
1057 * renameSplitedFiles - Renames all files after splitting into sorted SQL files
1058 * It also gives better progress experience for the user
1059 *
1060 * @param {string} $directory Path to directory where the rename function should run
1061 * @return void
1062 */
1063 private function renameSplitedFiles($directory) {
1064
1065 // Scan all files after splitting
1066 $allFiles = scandir($directory);
1067 $files = array_diff($allFiles, array('.', '..'));
1068
1069 // Loop throught them and add proper number for each
1070 foreach ($files as $index => $filename) {
1071
1072 // Make new destination name
1073 $newName = substr($filename, 0, -4) . '_of_' . sizeof($files) . '.sql';
1074
1075 // Current file path
1076 $file = $directory . DIRECTORY_SEPARATOR . $filename;
1077
1078 // Final path (after rename)
1079 $finalPathWithName = $directory . DIRECTORY_SEPARATOR . $newName;
1080
1081 // Finally rename the file
1082 rename($file, $finalPathWithName);
1083
1084 }
1085
1086 }
1087
1088 /**
1089 * checkIfContainName - Checks if A (string) is part of B array
1090 *
1091 * @return bool
1092 */
1093 private function checkIfContainName($tableName, $unwantedTables) {
1094
1095 $found = false;
1096
1097 $tableName = str_replace('-', '', $tableName);
1098 $tableName = str_replace('_', '', $tableName);
1099 $tableName = strtolower($tableName);
1100
1101 for ($i = 0; $i < sizeof($unwantedTables); ++$i) {
1102
1103 $name = $unwantedTables[$i];
1104 $name = str_replace('-', '', $name);
1105 $name = str_replace('_', '', $name);
1106 $name = strtolower($name);
1107
1108 if (strpos($tableName, $name) !== false) {
1109 $found = true;
1110 break;
1111 }
1112
1113 }
1114
1115 return $found;
1116
1117 }
1118
1119 /**
1120 * moveAllConvertedTables - Moves all completed tables outside
1121 *
1122 * @return void
1123 */
1124 private function moveAllConvertedTables() {
1125
1126 // Finished directory path
1127 $finishedDBDir = $this->db_files_root . DIRECTORY_SEPARATOR . $this->name_of_finished_tables_dir;
1128
1129 // Check if it exists
1130 if (!file_exists($finishedDBDir) || !is_dir($finishedDBDir)) {
1131 return;
1132 }
1133
1134 // Scan all tables in finished directory
1135 $allFiles = scandir($finishedDBDir);
1136 $files = array_diff($allFiles, array('.', '..'));
1137
1138 // Loop throught them and move outside
1139 foreach ($files as $index => $filename) {
1140
1141 // Old path
1142 $old = $this->db_files_root . DIRECTORY_SEPARATOR . $this->name_of_finished_tables_dir . DIRECTORY_SEPARATOR . $filename;
1143
1144 // New path
1145 $new = $this->db_files_root . DIRECTORY_SEPARATOR . $filename;
1146
1147 // Move that directory to parent
1148 rename($old, $new);
1149
1150 }
1151
1152 // Unlink directory that won't be used as it's (and it should) be empty.
1153 if (file_exists($finishedDBDir) && is_dir($finishedDBDir)) {
1154 @rmdir($finishedDBDir);
1155 }
1156
1157 // Return void
1158 return;
1159
1160 }
1161
1162 }
1163