PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.1
Backup Migration v2.1.1
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
better-backup-v3.php 4 months ago better-backup.php 4 months ago better-restore.php 4 months ago even-better-restore-v3.php 4 months ago even-better-restore-v4.php 4 months ago interface-search-replace-repository.php 4 months ago manager.php 4 months ago search-replace-processor.php 4 months ago search-replace-repository.php 4 months ago search-replace-stack-based.php 4 months ago search-replace-v2.php 4 months ago search-replace.php 4 months ago smart-sort.php 4 months ago
search-replace.php
238 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 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier and where clause are safely escaped
151 $row_count = $wpdb->get_results($wpdb->prepare('SELECT COUNT(*) AS num FROM ' . BMP::escapeSQLIDentifier($table) . $whereStmt . ';'));
152 $row_count = $row_count[0]->num;
153 if ($row_count == 0) {
154 $report['currentPage'] = $report['currentPage'] + 1;
155 continue;
156 }
157
158 $page_size = BMI_MAX_SEARCH_REPLACE_PAGE;
159 $pages = ceil($row_count / $page_size);
160 $page = 0;
161
162 if ($report['totalPages'] === 0) {
163 $report['totalPages'] = $pages;
164 }
165
166 for ($page; $page < $pages; $page++) {
167
168 $report['currentPage'] = $report['currentPage'] + 1;
169
170 $current_row = 0;
171 $start = $page * $page_size;
172 $end = $start + $page_size;
173
174 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier and where clause are safely escaped
175 $data = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . BMP::escapeSQLIDentifier($table) . $whereStmt . ' LIMIT %d, %d;', $start, $page_size));
176 for ($i = 0; $i < sizeof($data); ++$i) {
177
178 $row = $data[$i];
179 $report['rows']++;
180 $current_row++;
181
182 $update_sql = [];
183 $where_sql = [];
184 $upd = false;
185
186 foreach ($columns as $column => $primary_key) {
187
188 if ($limitColumns != false) {
189 if (!in_array($column, $limitColumns)) continue;
190 }
191 if (in_array($column, $excluded_columns)) continue;
192 if (!in_array($column, $fieldsForWhereStmt)) continue;
193
194 $edited_data = $data_to_fix = $row->$column;
195 $edited_data = $this->recursive_unserialize_replace($search, $replace, $data_to_fix);
196
197 if ($edited_data != $data_to_fix) {
198 $report['change']++;
199 $update_sql[] = '' . BMP::escapeSQLIDentifier($column) . ' = "' . esc_sql($edited_data) . '"';
200 $upd = true;
201 $where_sql[] = '' . BMP::escapeSQLIDentifier($column) . ' = "' . esc_sql($data_to_fix) . '"';
202 }
203
204 }
205
206 if ($upd && !empty($where_sql)) {
207 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifiers and values are safely escaped via escapeSQLIDentifier() and esc_sql()
208 $wpdb->get_results($wpdb->prepare("UPDATE " . BMP::escapeSQLIDentifier($table) . " SET " . implode(', ', $update_sql) . " WHERE " . implode(' AND ', array_filter($where_sql)) . ";"));
209
210 unset($sql);
211
212 $report['updates']++;
213
214 if ($wpdb->last_error !== '') {
215 // error_log(strval($wpdb->last_query));
216 // error_log(strval($wpdb->last_result));
217 // error_log(strval($wpdb->last_error));
218 }
219 }
220
221 unset($row);
222
223 }
224
225 unset($data);
226 return $report;
227
228 }
229
230 }
231
232 }
233
234 return $report;
235 }
236
237 }
238