Файловый менеджер - Редактировать - /home/lexlmvtu/public_html/wp-content/plugins/backup-backup/includes/database/better-restore.php
Назад
<?php /** * Author: Mikołaj `iClyde` Chodorowski * Contact: kontakt@iclyde.pl * Package: Backup Migration – WP Plugin */ // Namespace namespace BMI\Plugin\Database; // Use use BMI\Plugin\BMI_Logger AS Logger; use BMI\Plugin\Backup_Migration_Plugin as BMP; use BMI\Plugin\Progress\BMI_ZipProgress AS Progress; // Exit on direct access if (!defined('ABSPATH')) exit; global $bmi_last_seek, $bmi_last_file; // register_shutdown_function(function () { // // echo $GLOBALS['bmi_last_file'] . ' - ' . $GLOBALS['bmi_last_seek']; // // }); /** * Database importing * Main Class, requires $wpdb */ class BMI_Database_Importer { /** * Private local variables */ private $files = []; /** * __construct - Initialization and logger resolver * * @return self */ function __construct($storage, $total_queries, $old_abspath, $old_domain, $new_domain, &$logger, $isCLI, $conversionStats) { /** * WP Global Database variable */ global $wpdb; $this->wpdb = &$wpdb; /** * Set isCLI variable */ $this->isCLI = $isCLI; /** * Logger of BMI core */ $this->logger = &$logger; /** * Storage directory */ // $this->storage = trailingslashit(__DIR__) . 'data'; $this->storage = $storage; $this->total_queries = intval($total_queries); // $this->total_queries = $this->conversionStats['total_queries']; // Conversion stats $this->conversionStats = $conversionStats; /** * Domain(s) configuration * ? - All cases that domain can be represented in the database */ $this->source_domain = untrailingslashit($old_domain); $this->new_domain = untrailingslashit($new_domain); // untrailingslashit(home_url()); $this->json_source_domain = trim(json_encode($this->source_domain), '"'); $this->json_new_domain = trim(json_encode($this->new_domain), '"'); $this->no_protocol_source_domain = ltrim(ltrim($this->source_domain, "https://"), "http://"); $this->no_protocol_new_domain = ltrim(ltrim($this->new_domain, "https://"), "http://"); $this->json_no_protocol_source_domain = ltrim(ltrim($this->json_source_domain, "https:\/\/"), "http:\/\/"); $this->json_no_protocol_new_domain = ltrim(ltrim($this->json_new_domain, "https:\/\/"), "http:\/\/"); $this->source_abs = untrailingslashit($old_abspath); $this->new_abs = untrailingslashit(ABSPATH); $this->json_source_abs = trim(json_encode($this->source_abs), '"'); $this->json_new_abs = trim(json_encode($this->new_abs), '"'); $this->xi = 0; $this->current_file = ''; $this->init_start = microtime(true); $this->last_finish = $this->init_start; $this->table_names_alter = []; @ini_set('memory_limit', '-1'); if ((class_exists('mysqli') || class_exists('\mysqli')) && false) { $this->owndb = true; $this->db = new \mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); if ($this->db->connect_error) $this->owndb = false; else $this->db->query('SET foreign_key_checks = 0;'); } else { $this->owndb = false; } set_error_handler(function($errno, $errstr, $errfile, $errline) { if (BMI_DEBUG) { error_log('BMI DEBUG ENABLED, HERE IS THE COMPLETE REPORT (ERROR HANDLER #4):'); error_log(print_r($errno, true)); error_log(print_r($errstr, true)); error_log(print_r($errfile, true)); error_log(print_r($errline, true)); } // The only notice we could get here is Instance does not exist, ignore it. if ($errno === '8' || $errno === 8 || $errno === E_NOTICE) { if ($errfile == __FILE__) { throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); return; } } // error_log($errno .' '. $errstr .' '. $errfile .':'. $errline); // throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); }); } /** * showFirstLogs - Shows logs for initialization * * @return @void */ public function showFirstLogs() { $this->logger->log("Initializing database import V2 engine...", 'INFO'); $this->logger->log("Initialized successfully, memory usage: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB", 'SUCCESS'); } /** * showFirstLogs - Shows logs after finish * * @return @void */ public function showEndLogs() { $from_start = number_format(microtime(true) - $this->init_start, 4); $this->logger->log("Total time to insert " . $this->xi . " queries took " . $from_start . " seconds.", 'INFO'); } /** * import - Import initializer * * @return {bool} true on success false on fail */ public function import() { $this->logger->log("Loading file list...", 'INFO'); $this->load_file_list(); $this->logger->log("File list loaded, memory usage: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB", 'SUCCESS'); $this->logger->log("Restoring tables using temporary name...", 'INFO'); $this->restore_tables(); // Mark those as finished $GLOBALS['bmi_last_file'] = false; $GLOBALS['bmi_last_seek'] = false; $this->showEndLogs(); } /** * restore_by_file - Allows to restore by one file * * @param {string} path to sql file * @param {int} last seek position of this file * @return {int/bool} last seek number or bool if completed */ public function restore_by_file($path, $last_seek = 0) { if (!file_exists($path)) { if (file_exists($path)) @unlink($path); $this->logger->log("Database file does not exist anymore (" . basename($path) . ")? – Omitting.", 'INFO'); return true; } $file = new \SplFileObject($path); $file->seek($file->getSize()); $total_lines = $file->key() + 1; $this->current_file = $path; $last_real_table_name = null; $custom_var_started = false; $values_started = false; $query_ended = false; $query = ''; $query_exist = false; $last_seek_start = $last_seek; for ($i = $last_seek; $i < $total_lines; ++$i) { // Set the pointer $GLOBALS['bmi_last_seek'] = $i; $file->seek($i); // Get the line $line = trim($file->current()); // Find our declaractions if ($line == ltrim("\/* QUERY START */", '\\')) $query_ended = false; elseif ($line == ltrim("\/* CUSTOM VARS START */", '\\')) $custom_var_started = true; elseif ($line == ltrim("\/* CUSTOM VARS END */", '\\')) $custom_var_started = false; elseif ($line == ltrim("\/* VALUES START */", '\\')) $values_started = true; elseif ($line == ltrim("\/* VALUES END */", '\\')) $values_started = false; elseif ($line == ltrim("\/* QUERY END */", '\\')) $query_ended = true; else { if ($custom_var_started === true) { $line = rtrim($line, "\n"); $the_variable = null; preg_match("/`(.*)`/i", $line, $the_variable); $the_variable = $the_variable[1]; if (strpos($line, 'REAL_TABLE_NAME') !== false) { $this->table_names_alter[$the_variable] = ''; $last_real_table_name = $the_variable; } else if (strpos($line, 'PRE_TABLE_NAME') !== false) { $this->table_names_alter[$last_real_table_name] = $the_variable; } } if (strlen($line) > 0 || $values_started === true) { $line = rtrim($line, "\n"); if ($values_started === true && $query_ended != true) { if ($this->source_domain != $this->new_domain) { $this->line_replace($line); } } $query .= $line; } if ($query_ended === true) { $this->run_query($query); $values_started = false; $query_ended = false; $query_exist = true; $query = ''; $last_seek = $i; if (($last_seek_start + 100) < $i) { break; } } } } if ($query_exist === false) { if (file_exists($path)) { gc_collect_cycles(); $file = null; @unlink($path); } return true; } else { $file = null; return $last_seek; } } /** * get_sql_files - Returns all SQL files included in the backup * * @return {array} List of SQL files */ public function get_sql_files($first_db = true) { if ($first_db == true) { $this->logger->log("Loading file list...", 'INFO'); } $this->load_file_list(); if ($first_db == true) { $this->logger->log("File list loaded, memory usage: " . number_format(memory_get_usage() / 1024 / 1024, 2) . " MB", 'SUCCESS'); } return $this->files; } /** * restore_tables - Line by line reader and query builder * It uses path provided in __construct to get the files * I.e. it will try to load all .sql files located in the folder * Important, those failes have to be generated with better-backup module * * @return {void} */ private function restore_tables() { foreach ($this->files as $file_index => $path) { $GLOBALS['bmi_last_file'] = $path; $this->current_file = $path; $import = 0; do { $import = $this->restore_by_file($path, $import); } while ($import !== true); } $this->alter_names(); $this->queries_ended(); } /** * queries_ended - Runs when all queries imported * * @return @void */ public function queries_ended() { if ($this->owndb == true) { $this->db->close(); } else { $foreign_keys = "SET foreign_key_checks = 1;"; $this->run_query($foreign_keys); } } /** * alter_names - Replaces temporary tables with final name * * @return {void} */ public function alter_names() { $this->logger->log(__('Switching tables to new ones and removing old ones.', 'backup-backup'), 'STEP'); foreach ($this->table_names_alter as $final_name => $tmp_name) { $sql = "DROP TABLE IF EXISTS `$final_name`;"; $this->run_query($sql, true); $sql = "ALTER TABLE `$tmp_name` RENAME TO `$final_name`;"; $this->run_query($sql, true); } $this->logger->log(__('Tables replaced successfully.', 'backup-backup'), 'SUCCESS'); } /** * replace_domain_string - Replaces the string recursively * It will find all possibilites to replace even the same string multiple times * * @param {string} &$string = '' String pointer declarated somewhere else to save memory * @return {void} - It modifies the root variable */ private function replace_domain_string(&$string = '', &$replaced = false) { $replacement = $this->is_replacable($string); if ($replacement !== false) { $string = str_replace($replacement[0], $replacement[1], $string); $replaced = true; if ($this->is_replacable($string) !== false) { $replaced = false; $this->replace_domain_string($string, $replaced); } } } /** * iterate_maybe_serial_string - It checks if the serialized thing was nested * And makes the serialization for each once again to get the right object * * @param {string} &$string String that may be serializable * @return {void} It modifies pointer */ private function iterate_maybe_serial_string(&$string) { // $serial = $this->is_valid_serialized($string, false); if (is_serialized($string)) { // $this->iterate_maybe_serial_string($string); $string = @unserialize($string, ['allowed_classes' => false]); $this->iterate_object($string); $string = @serialize($string); } else { $this->iterate_object($string); } } /** * is_replacable - Checks if the string is repecable, if it contains the old domain * * @param {string} &$string String that can include old domain name * @return {void} It modifies pointer */ private function is_replacable(&$string) { if (strpos($string, $this->source_domain) !== false) { return [$this->source_domain, $this->new_domain]; } if (strpos($string, $this->json_source_domain) !== false) { return [$this->json_source_domain, $this->json_new_domain]; } if (strpos($string, $this->no_protocol_source_domain) !== false) { return [$this->no_protocol_source_domain, $this->no_protocol_new_domain]; } if (strpos($string, $this->json_no_protocol_source_domain) !== false) { return [$this->json_no_protocol_source_domain, $this->json_no_protocol_new_domain]; } if (strpos($string, $this->source_abs) !== false) { return [$this->source_abs, $this->new_abs]; } if (strpos($string, $this->json_source_abs) !== false) { return [$this->json_source_abs, $this->json_new_abs]; } return false; } /** * check_key - Checks object key * It allows to modify object keys in case the domain was used there * E.g. some redirect plugins may store it in the database this way * * @param {string} $key Object key or normal string * @return {string/bool} String if it's replacable bool false if nothing to replace */ private function check_key($key) { if ($this->is_replacable($key) !== false) { $replaced = false; $this->replace_domain_string($key, $replaced); return $key; } else { return false; } } /** * logIterationError - Exception logged * * @param {string} $e Exception or Throwable * @return @void */ public function logIterationError($e) { $this->logger->log('Notice while importing: ' . $e->getMessage() . ' @ ' . $e->getLine() . ' (Code: ' . $e->getCode() . ')', 'WARN'); // error_log(print_r($e, true)); } /** * throwThisLine - Throws an exception for catch * * @return Throwable */ public function throwThisLine($e) { // $this->logIterationError($e); throw new \ErrorException($e->getMessage(), 0, $e->getCode(), $e->getFile(), $e->getLine()); } /** * @RECURSIVE MEMORY EATER * iterate_object - Recursive iterator of object/array * It checks every value and key and replaces it where needed * * @param {?object/?mixed} &$object Mixed type, it will detect correct object and return no changed if wrong * @return {void} It modifies object or does nothing on wrong source */ private function iterate_object(&$object) { if (is_string($object) && is_serialized($object)) { $this->iterate_maybe_serial_string($object); return; } if (!(is_object($object) || is_array($object))) { return; } try { foreach ($object as $key => $value) { $key_match = $this->check_key($key); if ($key_match !== false) { if (is_object($object)) { $object->$key_match = $object->$key; unset($object->$key); } elseif (is_array($object)) { $object[$key_match] = $object[$key]; unset($object[$key]); } $key = $key_match; } if (($value && !is_string($value)) && (is_object($value) || is_array($value))) { try { $success = $this->iterate_object($value); if ($success === false) return false; if (is_object($object)) $object->$key = $value; elseif (is_array($object)) $object[$key] = $value; } catch (\Exception $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } catch (\Throwable $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } } else { // $subserial = $this->is_valid_serialized($value, false); // $subserial['valid'] === true if (is_serialized($value)) { try { $success = $this->iterate_object($value); if ($success === false) return false; if (is_object($object)) @$object->$key = $value; elseif (is_array($object)) $object[$key] = $value; } catch (\Exception $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } catch (\Throwable $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } } else { if (is_array($value) || is_object($value)) { try { $success = $this->iterate_object($value); if ($success === false) return false; if (is_object($object)) @($object->$key = $value); elseif (is_array($object)) $object[$key] = $value; } catch (\Exception $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } catch (\Throwable $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } } else { // Final string to replace (if string / else ignore) if (gettype($value) === 'string') { $modified = false; $this->replace_domain_string($value, $modified); if ($modified) { try { if (is_object($object)) @($object->$key = $value); elseif (is_array($object)) $object[$key] = $value; } catch (\Exception $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } catch (\Throwable $e) { /* Errors? Probably here! */ $this->throwThisLine($e); return false; } } } } } } } } catch (\Exception $e) { $this->logIterationError($e); return false; } catch (\Throwable $e) { $this->logIterationError($e); return false; } catch (\ErrorException $e) { $this->logIterationError($e); return false; } } /** * line_replace - Value replacer before inserting to database * * @param {string} &$line = '' Pointer to file line which should be replaced if needed * @return {void} It modifies pointer */ private function line_replace(&$line = '') { // Remove wpdb prepare slashes to make sure the serialized data is correct $copy = $line; $line = stripcslashes($line); $line = str_replace('\0', '', $line); $serial = $this->is_valid_serialized($line); if ($serial['valid'] === true) { // Copy no needed here slashes will be added by wpdb unset($copy); // Iterate entire object replace keys and values $this->iterate_object($line); // Serialize it again as it was serialized before $line = serialize($line); // Make the string SQL friendly, as it was before if ($serial['quotes']) $line = $this->wpdb->prepare('%s', $line); if ($serial['comma']) $line .= ','; } else { // Make sure the line is exactly the same as in file $line = $copy; // Unset copy to free space unset($copy); // Find and replace domain $replaced = false; $this->replace_domain_string($line, $replaced); } } /** * run_query - Query runner, it runs the final query after built and replacements * * @param {string} &$query Pointer to small or huge query * @return {void} Returns nothing, and the &$query is not removed here * * Notice: Please unset($query) after this method otherwise we will lose memory */ private function run_query(&$query, $isAlter = false) { $this->xi++; if ($this->owndb === true) $this->db->query($query); else $this->wpdb->query($query); if ($this->xi % 5 == 0) { $from_start = number_format(microtime(true) - $this->init_start, 4); $filename = basename($this->current_file); $from_last = number_format(microtime(true) - $this->last_finish, 4); $this->last_finish = microtime(true); $queries_so_far = $this->xi; $queries_total = $this->total_queries; $queries_progress = number_format(($queries_so_far / $queries_total) * 100, 2); $halfProgress = true; if (isset($this->conversionStats['total_queries'])) { $halfProgress = false; } if (!$isAlter) { if ($halfProgress) { $this->logger->progress(50 + ($queries_progress / 2)); } else { $this->logger->progress(75 + ($queries_progress / 4)); } } $match_output = []; preg_match('/(.*)_(\d+)\_of\_(\d+)/', $filename, $match_output); // 1 (table name), 2 (part), 3 (total parts) $size = ""; if (file_exists($this->current_file)) { $size = ", size: " . BMP::humanSize(filesize($this->current_file)); } if (!$isAlter) { if (sizeof($match_output) >= 3) { $this->logger->log("Finished " . $queries_so_far . "/" . $queries_total . " (" . $queries_progress . "%)" . " for " . $match_output[1] . " table (part: " . $match_output[2] . "/" . $match_output[3] . $size . ").", 'INFO'); } else { $this->logger->log("Finished " . $queries_so_far . "/" . $queries_total . " (" . $queries_progress . "%)" . " for " . $filename . " table (part: dynamic)", 'INFO'); } } } } /** * load_file_list - Loads files to restore * * @return {array} list list of restorable files */ private function load_file_list() { $files = scandir($this->storage); foreach ($files as $index => $filename) { if (in_array($filename, ['.', '..'])) continue; $filePath = $this->storage . DIRECTORY_SEPARATOR . $filename; if (is_dir($filePath)) { $splitedFiles = scandir($filePath); foreach ($splitedFiles as $splitedIndex => $splitedFilename) { if (in_array($splitedFilename, ['.', '..', 'bmi_converted_completed_tables'])) continue; if (substr($splitedFilename, -4) != '.sql') continue; $this->files[] = $this->storage . DIRECTORY_SEPARATOR . $filename . DIRECTORY_SEPARATOR . $splitedFilename; } continue; } if (substr($filename, -4) != '.sql') continue; $this->files[] = $this->storage . DIRECTORY_SEPARATOR . $filename; } $this->sortFiles(); return $this->files; } private function sortFiles() { $files = $this->files; $tmp_files = []; for ($i = 0; $i < sizeof($files); ++$i) { $file = $files[$i]; $name = []; preg_match('/^(.*)\.sql$/', basename($file), $name); // 1 $order = []; $name = $name[1]; preg_match('/^(.*)\_((\d+)\_of\_(\d+))$/', $name, $order); // 1, 3, 4 $tablename = $name; if (sizeof($order) >= 3) { $tablename = $order[1]; } if (!isset($tmp_files[$tablename])) { $tmp_files[$tablename] = []; } $tmp_files[$tablename][] = $file; } ksort($tmp_files); $files = []; foreach ($tmp_files as $table => $sqlfiles) { usort($sqlfiles, function ($a, $b) { $nameA = []; preg_match('/^(.*)\.sql$/', basename($a), $nameA); // 1 $nameB = []; preg_match('/^(.*)\.sql$/', basename($b), $nameB); // 1 $nameA = $nameA[1]; $nameB = $nameB[1]; $orderA = []; preg_match('/^(.*)\_((\d+)\_of\_(\d+))$/', $nameA, $orderA); // 1, 3, 4 $orderB = []; preg_match('/^(.*)\_((\d+)\_of\_(\d+))$/', $nameB, $orderB); // 1, 3, 4 if (sizeof($orderA) >= 3 && sizeof($orderB) >= 3) { if (intval($orderA[3]) > intval($orderB[3])) { return 1; } else if (intval($orderA[3]) < intval($orderB[3])) { return -1; } else { return 0; } } else { return 1; } }); for ($i = 0; $i < sizeof($sqlfiles); ++$i) { $files[] = $sqlfiles[$i]; } } unset($tmp_files); $this->files = $files; return $this->files; } /** * is_valid_serialized - Checks if string is serialized and if it's valid serialization * * @param {mixed} $data = '' Maybe serialized string * @return {array} If the results is valid, if comma was removed, if quotes were removed */ private function is_valid_serialized(&$data = '', $sql = true) { $commaRemoved = false; $quotesRemoved = false; if ($sql) { $length = strlen($data); $is_last = $data[$length - 1] == "," ? false : true; if (!$is_last) { $commaRemoved = true; $data = substr($data, 0, -1); } } if (is_numeric($data)) { if ($commaRemoved) $data .= ','; return ['valid' => false, 'comma' => false, 'quotes' => false]; } if (!is_string($data)) { if ($commaRemoved) $data .= ','; return ['valid' => false, 'comma' => false, 'quotes' => false]; } if (!$data) { if ($commaRemoved) $data .= ','; return ['valid' => false, 'comma' => false, 'quotes' => false]; } if ($data[0] == "'") { $quotesRemoved = true; $data = trim($data, "'"); } if (is_serialized($data)) { $data = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $data); $unserialized = @unserialize($data, ['allowed_classes' => false]); } else { $unserialized = false; } $is_valid = ($data == 'b:0;' || ($unserialized !== false)) ? true : false; if (!$is_valid) { if ($quotesRemoved) { $data = $this->wpdb->prepare('%s', $data); } if ($commaRemoved) $data .= ','; } else { $data = $unserialized; unset($unserialized); } return ['valid' => $is_valid, 'comma' => $commaRemoved, 'quotes' => $quotesRemoved]; } }
| ver. 1.4 |
Github
|
.
| PHP 7.2.34 | Генерация страницы: 0.06 |
proxy
|
phpinfo
|
Настройка