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 |