PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.3
Backup Migration v2.1.3
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 / search-replace.php
backup-backup / includes / database Last commit date
export 3 months ago better-backup-v3.php 3 months ago better-backup.php 3 months ago better-restore.php 3 months ago even-better-restore-v3.php 3 months ago even-better-restore-v4.php 3 months ago interface-search-replace-repository.php 3 months ago manager.php 3 months ago search-replace-processor.php 3 months ago search-replace-repository.php 3 months ago search-replace-stack-based.php 3 months ago search-replace-v2.php 3 months ago search-replace.php 3 months ago smart-sort.php 3 months ago
search-replace.php
239 lines
1 <?php
2
3 /**
4 * Modifications by: Mikołaj `iClyde` Chodorowski
5 * Modified by e-mail contact: kontakt@iclyde.pl
6 * Package: Safe Search and Replace on Database with Serialized Data v2.0.1
7 *
8 * Please contact above e-mail if you see any credits problem.
9 */
10
11 // Namespace
12 namespace BMI\Plugin\Database;
13
14 // Use
15 use BMI\Plugin\BMI_Logger AS Logger;
16 use BMI\Plugin\Backup_Migration_Plugin as BMP;
17 use BMI\Plugin\Progress\BMI_ZipProgress AS Progress;
18
19 // Exit on direct access
20 if (!defined('ABSPATH')) exit;
21
22 /**
23 * Database Search and Replace for Enginge v3
24 */
25 class BMI_Search_Replace_Engine {
26
27 function __construct($tables, $currentPage = 0, $totalPages = 0, $isStaging = false) {
28
29 $this->totalPages = $totalPages;
30 $this->currentPage = $currentPage;
31 $this->all_tables = $tables;
32 $this->isStaging = $isStaging;
33
34 }
35
36 public function perform($search, $replace, $limitColumns = false) {
37
38 return $this->search_replace($search, $replace, $this->all_tables, $limitColumns);
39
40 }
41
42 private function recursive_unserialize_replace($from = '', $to = '', $data = '', $serialised = false, $visited = []) {
43
44 if (is_array($data) || is_object($data)) {
45 if (in_array($data, $visited, true)) {
46 return $data;
47 }
48 $visited[] = $data;
49 }
50 try {
51
52 if (is_string($data) && is_serialized($data) && ($unserialized = @unserialize($data, ['allowed_classes' => ['stdClass']])) !== false) {
53
54 $data = $this->recursive_unserialize_replace($from, $to, $unserialized, true);
55
56 } else if (is_array($data)) {
57
58 $_tmp = [];
59 foreach ($data as $key => $value) {
60 $_tmp[$key] = $this->recursive_unserialize_replace($from, $to, $value, false, $visited);
61 }
62
63 $data = $_tmp;
64 unset($_tmp);
65 } else if (is_object($data) && !is_a($data, '__PHP_Incomplete_Class')) {
66 $tmp = $data;
67 $props = get_object_vars($data);
68 foreach ($props as $key => $value) {
69 $tmp->$key = $this->recursive_unserialize_replace($from, $to, $value, false, $visited);
70 }
71 $data = $tmp;
72 unset($tmp);
73 } else if (is_string($data) && (null !== ($_tmp = json_decode($data, true))) && is_array($_tmp)) {
74 foreach ($_tmp as $key => $value) {
75 $_tmp[$key] = $this->recursive_unserialize_replace($from, $to, $value, false, $visited);
76 }
77 $data = json_encode($_tmp);
78 unset($_tmp);
79 } else if (is_string($data)) {
80 $data = str_replace($from, $to, $data);
81 }
82
83 if ($serialised) return serialize($data);
84
85 }
86 catch (\Exception $error) {}
87 catch (\Throwable $error) {}
88
89 return $data;
90
91 }
92
93 private function search_replace($search = '', $replace = '', $tables = [], $limitColumns = false) {
94
95 global $wpdb;
96
97 $excluded_columns = ['id', 'ID'];
98 $report = ['tables' => 0, 'rows' => 0, 'change' => 0, 'updates' => 0, 'currentPage' => 0, 'totalPages' => 0];
99
100 if ($this->currentPage !== 0 && is_numeric($this->currentPage)) {
101 $report['currentPage'] = $this->currentPage;
102 }
103
104 if ($this->totalPages !== 0 && is_numeric($this->totalPages)) {
105 $report['totalPages'] = $this->totalPages;
106 }
107
108 if (is_array($tables) && !empty($tables)) {
109
110 $wpdb->show_errors();
111
112 foreach($tables as $table) {
113
114 $report['tables']++;
115 $columns = [];
116
117 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
118 $fields = $wpdb->get_results('DESCRIBE ' . BMP::escapeSQLIDentifier($table) . ';');
119 foreach ($fields as $index => $object) {
120 $columns[$object->Field] = $object->Key == 'PRI' ? true : false;
121 }
122
123 $fieldsForWhereStmt = [];
124
125 foreach ($fields as $index => $columnInfo) {
126 $type = strtolower($columnInfo->Type);
127 if (strpos($type, 'char') !== false || strpos($type, 'text') !== false) {
128 $column = mysqli_real_escape_string($wpdb->dbh, $columnInfo->Field);
129 if (!in_array($column, $fieldsForWhereStmt)) {
130 $fieldsForWhereStmt[] = $column;
131 }
132 }
133 }
134
135 $whereStmt = '';
136 $totalColumns = sizeof($fieldsForWhereStmt);
137 for ($i = 0; $i < $totalColumns; ++$i) {
138 $column = $fieldsForWhereStmt[$i];
139 if ($i == 0) $whereStmt .= ' WHERE ';
140 // if ($this->isStaging) {
141 $whereStmt .= '(' . BMP::escapeSQLIDentifier($column) . ' LIKE ' . '"%' . esc_sql($search) . '%"';
142 $whereStmt .= ' AND ' . BMP::escapeSQLIDentifier($column) . ' NOT LIKE ' . '"%' . esc_sql($replace) . '%")';
143 // } else {
144 // $whereStmt .= '`' . $column . '`' . ' LIKE ' . '"%' . mysqli_real_escape_string($wpdb->dbh, $search) . '%"';
145 // }
146 if ($i != $totalColumns - 1) $whereStmt .= ' OR ';
147 }
148
149 if ($whereStmt === '') continue;
150 $sql = 'SELECT COUNT(*) AS num FROM ' . BMP::escapeSQLIDentifier($table) . $whereStmt . ';';
151 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier and where clause are safely escaped
152 $row_count = $wpdb->get_results($sql);
153 $row_count = $row_count[0]->num;
154 if ($row_count == 0) {
155 $report['currentPage'] = $report['currentPage'] + 1;
156 continue;
157 }
158
159 $page_size = BMI_MAX_SEARCH_REPLACE_PAGE;
160 $pages = ceil($row_count / $page_size);
161 $page = 0;
162
163 if ($report['totalPages'] === 0) {
164 $report['totalPages'] = $pages;
165 }
166
167 for ($page; $page < $pages; $page++) {
168
169 $report['currentPage'] = $report['currentPage'] + 1;
170
171 $current_row = 0;
172 $start = $page * $page_size;
173 $end = $start + $page_size;
174
175 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier and where clause are safely escaped
176 $data = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . BMP::escapeSQLIDentifier($table) . $whereStmt . ' LIMIT %d, %d;', $start, $page_size));
177 for ($i = 0; $i < sizeof($data); ++$i) {
178
179 $row = $data[$i];
180 $report['rows']++;
181 $current_row++;
182
183 $update_sql = [];
184 $where_sql = [];
185 $upd = false;
186
187 foreach ($columns as $column => $primary_key) {
188
189 if ($limitColumns != false) {
190 if (!in_array($column, $limitColumns)) continue;
191 }
192 if (in_array($column, $excluded_columns)) continue;
193 if (!in_array($column, $fieldsForWhereStmt)) continue;
194
195 $edited_data = $data_to_fix = $row->$column;
196 $edited_data = $this->recursive_unserialize_replace($search, $replace, $data_to_fix);
197
198 if ($edited_data != $data_to_fix) {
199 $report['change']++;
200 $update_sql[] = '' . BMP::escapeSQLIDentifier($column) . ' = "' . esc_sql($edited_data) . '"';
201 $upd = true;
202 $where_sql[] = '' . BMP::escapeSQLIDentifier($column) . ' = "' . esc_sql($data_to_fix) . '"';
203 }
204
205 }
206
207 if ($upd && !empty($where_sql)) {
208 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifiers and values are safely escaped via escapeSQLIDentifier() and esc_sql()
209 $wpdb->get_results($wpdb->prepare("UPDATE " . BMP::escapeSQLIDentifier($table) . " SET " . implode(', ', $update_sql) . " WHERE " . implode(' AND ', array_filter($where_sql)) . ";"));
210
211 unset($sql);
212
213 $report['updates']++;
214
215 if ($wpdb->last_error !== '') {
216 // error_log(strval($wpdb->last_query));
217 // error_log(strval($wpdb->last_result));
218 // error_log(strval($wpdb->last_error));
219 }
220 }
221
222 unset($row);
223
224 }
225
226 unset($data);
227 return $report;
228
229 }
230
231 }
232
233 }
234
235 return $report;
236 }
237
238 }
239