/** * Recursively search and replace * @param mixed $data - **by reference** - string or array which should have find/replace applied * @param array $search array with text values to find * @param array $replace array with text values to replace $search values with * @param array $regex_search array with regular expressions to look for * @param array $regex_replace array with text value to replace $regex_search values with * @return int number of replacements made */ function ns_recursive_search_replace(&$data, $search, $replace, $regex_search = array(), $regex_replace = array(), $case_sensitive = false) { $is_serialized = is_serialized($data); $string_replacements_made = $regex_replacements_made = 0; // unserialize if need be if ($is_serialized) { $data = unserialize($data); } // run through replacements for strings, arrays - other types are unsupported to vaoid if (is_array($data)) { foreach ($data as $key => $value) { ns_recursive_search_replace($data[$key], $search, $replace, $regex_search, $regex_replace, $case_sensitive); } } elseif (is_string($data)) { // simple string replacment - most of the time this is all that is needed $replace_func = $case_sensitive ? 'str_replace' : 'str_ireplace'; $data = $replace_func($search, $replace, $data, $string_replacements_made); // advanced regex replacement - this will be skipped most of the time if (!empty($regex_search) && !empty($regex_replace)) { $data = preg_replace($regex_search, $regex_replace, $data, -1, $regex_replacements_made); } } // reserialize if need be if ($is_serialized) { $data = serialize($data); } // return count of replacements made return $string_replacements_made + $regex_replacements_made; }
function clone_tables() { $this->dlog('ENTER ns_cloner::clone_tables'); // Setup replacements for standard url/name substitution + character encoding issues $search = array($this->source_upload_dir_relative, $this->source_upload_url, $this->source_subd, $this->source_prefix . "user_roles"); $replace = array($this->target_upload_dir_relative, $this->target_upload_url, $this->target_subd, $this->target_prefix . "user_roles"); $search = apply_filters('ns_cloner_search_items', $search, $this); $replace = apply_filters('ns_cloner_replace_items', $replace, $this); $regex_search = apply_filters('ns_cloner_regex_search_items', array(), $this); $regex_replace = apply_filters('ns_cloner_regex_replace_items', array(), $this); $this->dlog(array("String search targets:", $search)); $this->dlog(array("String search replacements:", $replace)); $this->dlog(array("Regex search targets:", $regex_search)); $this->dlog(array("Regex search replacements:", $regex_replace)); // Sort and filter replacements to intelligently avoid compounding replacement issues - more details in function comments in lib/ns-utils.php if (apply_filters('ns_do_search_replace_validation', true)) { ns_set_search_replace_sequence($search, $replace, $regex_search, $regex_replace, NS_CLONER_LOG_FILE_DETAILED); $search = apply_filters('ns_cloner_search_items_after_sequence', $search, $this); $replace = apply_filters('ns_cloner_replace_items_after_sequence', $replace, $this); $regex_search = apply_filters('ns_cloner_regex_search_items_after_sequence', $regex_search, $this); $regex_replace = apply_filters('ns_cloner_regex_replace_items_after_sequence', $regex_replace, $this); $this->dlog(array("String search targets after sequence:", $search)); $this->dlog(array("String search replacements after sequence:", $replace)); $this->dlog(array("Regex search targets after sequence:", $regex_search)); $this->dlog(array("Regex search replacements after sequence:", $regex_replace)); } // Fetch source tables and start cloning $tables = $this->get_site_tables($this->source_db, $this->source_prefix); $count_tables_cloned = $count_replacements_made = 0; if (is_array($tables) && count($tables) > 0) { foreach ($tables as $source_table) { // if it's a non-prefixed table (root/main), prepend the prefix on, otherwise do replacement if (strpos($source_table, $this->source_prefix) === false) { $target_table = $this->target_prefix . $source_table; } else { $target_table = str_replace($this->source_prefix, $this->target_prefix, $source_table); } $quoted_source_table = ns_sql_backquote($source_table); $quoted_target_table = ns_sql_backquote($target_table); $structure_query = "SHOW CREATE TABLE " . $quoted_source_table; $structure = $this->source_db->get_var($structure_query, 1, 0); $this->handle_any_db_errors($this->source_db, $query); // If table references another table not yet created, save it for the end $reference_exists = preg_match_all("/REFERENCES `{$this->source_prefix}([^`]+?)/", $structure, $reference_matches); if ($reference_exists) { foreach ($reference_matches[1] as &$referenced_table) { $current_pos = array_search($source_table, $tables); $completed_tables = array_slice($tables, 0, $current_pos); if (!in_array($referenced_table, $completed_tables)) { unset($tables[$currrent_pos]); array_push($tables, $source_table); $this->dlog("Moving table <b>{$source_table}</b> to end of cloning queue due to dependent constraint"); continue 2; } } } // Log which table this is (and don't copy a table to itself if for some reason prefix didn't change) $this->dlog_break(); if ($source_table == $target_table) { $this->dlog("Source table: <b>{$source_table}</b> and Target table: <b>{$target_table} are the same! SKIPPING!!!</b>"); continue; } else { $this->dlog("Cloning source table: <b>{$source_table}</b> to Target table: <b>{$target_table}</b>"); } $this->dlog_break(); // Drop the target table if it already exists to avoid conflicts if (apply_filters('ns_cloner_do_drop_target_table', true, $target_table, $this)) { $query = "DROP TABLE IF EXISTS " . $quoted_target_table; $this->target_db->query($query); $this->handle_any_db_errors($this->target_db, $query); } // Create cloned table structure (and rename any constraints to prevent errors) $query = str_replace($quoted_source_table, $quoted_target_table, $structure); $query = preg_replace("/REFERENCES `{$this->source_prefix}/", "REFERENCES `{$this->target_prefix}", $query); $query = preg_replace("/CONSTRAINT `.+?`/", "CONSTRAINT", $query); $this->target_db->query(apply_filters('ns_cloner_create_table_query', $query, $this)); $this->handle_any_db_errors($this->target_db, $query); // Get table contents $query = "SELECT * FROM " . $quoted_source_table; $contents = $this->source_db->get_results($query, ARRAY_A); $this->handle_any_db_errors($this->source_db, $query); $this->dlog("Number of rows: " . count($contents)); $row_counter = 0; $rows_to_insert = array(); foreach ($contents as $row) { $row_counter++; $insert_this_row = true; // set flag to skip any junk rows which shouldn't/needn't be copied // we can't use 'continue' here because if this is the last row in a batch insert that query still needs to happen if (isset($row['option_name']) && preg_match('/(_transient_rss_|_transient_(timeout_)?feed_)/', $row['option_name']) || isset($row['meta_key']) && preg_match('/(_edit_lock|_edit_last)/', $row['meta_key']) || !apply_filters('ns_cloner_do_copy_row', true, $row, $source_table)) { $insert_this_row = false; } // only spend resources on replacements if this row is going to be inserted if ($insert_this_row) { // make sure target title option doesn't get lost/replaced if (preg_match('/options$/', $target_table) && isset($row['option_name']) && $row['option_name'] == 'blogname' && !empty($this->target_title)) { $row['option_value'] = $this->target_title; } // perform replacements foreach ($row as $field => $value) { $row_count_replacements_made = ns_recursive_search_replace($value, $search, $replace, $regex_search, $regex_replace, isset($this->request['case_sensitive'])); $row[$field] = apply_filters('ns_cloner_field_value', $value, $field, $row, $this); $count_replacements_made += $row_count_replacements_made; } $row = apply_filters('ns_cloner_insert_values', $row, $target_table); } // one by one insertion is less efficient - only do if explicitly set in code elsewhere via filter if (apply_filters('ns_cloner_single_insert', false, $this, $target_table)) { if ($insert_this_row) { $format = apply_filters('ns_cloner_insert_format', null, $target_table); $this->target_db->insert($target_table, $row, $format); $this->handle_any_db_errors($this->target_db, "INSERT INTO {$target_table} via wpdb --> " . print_r($row, true)); do_action('ns_cloner_after_insert', $rows, $target_table); } } else { if ($insert_this_row) { array_push($rows_to_insert, $row); } if ($row_counter % 100 === 0 || $row_counter === count($contents)) { // avoid trying to insert with no values if (empty($rows_to_insert)) { continue; } // we are go to insert, so create query and execute $column_names = array_keys($row); $query = "INSERT INTO {$quoted_target_table} (" . implode(",", ns_sql_backquote($column_names)) . ") VALUES "; foreach ($rows_to_insert as $row_to_insert) { $values = array_map('ns_sql_quote', $row_to_insert); $query .= "(" . implode(",", $values) . "),"; } $rows_to_insert = array(); $query_with_ending = substr($query, 0, -1) . ';'; $this->target_db->query($query_with_ending); $this->handle_any_db_errors($this->target_db, $query_with_ending); do_action('ns_cloner_after_insert_batch', $rows_to_insert, $target_table); } } } // end rows loop $count_tables_cloned++; } // end tables loop $this->report[__('Tables cloned', 'ns-cloner')] = $count_tables_cloned; $this->report[__('Replacements made', 'ns-cloner')] = $count_replacements_made; $this->dlog('Cloned: <b>' . $count_tables_cloned . '</b> tables!'); $this->dlog('Replaced: <b>' . $count_replacements_made . '</b> occurences of search strings!'); } else { $this->dlog('No tables found for cloning'); } }