PluginProbe ʕ •ᴥ•ʔ
UpdraftPlus: WP Backup & Migration Plugin / 1.24.10
UpdraftPlus: WP Backup & Migration Plugin v1.24.10
1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 1.9.64 1.11.12 1.4.8 1.11.15 1.4.9 1.11.17 1.5.16 1.11.18 1.5.20 1.11.2 1.5.21 1.11.20 1.5.22 1.11.23 1.5.5 1.11.24 1.5.6 1.11.25 1.5.7 1.11.26 1.5.8 1.11.27 1.5.9 1.11.28 1.6.1 1.11.3 1.6.17 1.11.4 1.6.2 1.11.5 1.6.46 1.11.8 1.7.0 1.11.9 1.7.1 1.12.0 1.7.18 1.12.1 1.7.20 1.12.12 1.7.3 1.12.13 1.7.34 1.12.15 1.7.35 1.12.17 1.7.39 1.12.2 1.7.40 1.12.20 1.7.41 1.12.23 1.8.1 1.12.24 1.8.11 1.12.25 1.8.12 1.12.28 1.8.13 1.12.29 1.8.2 1.12.30 1.8.5 1.12.32 1.8.8 1.12.34 1.9.0 1.12.35 1.9.13 1.12.37 1.9.15 1.12.39 1.9.17 1.12.4 1.12.40 1.12.6 1.13.1 1.13.11 1.13.12 1.13.15 1.13.16 1.13.2 1.13.3 1.13.4 1.13.5 1.13.6 1.13.7 1.13.8 1.13.9 1.14.10 1.14.11 1.14.12 1.14.13 1.14.2 1.14.3 1.14.4 1.14.5 1.14.7 1.14.9 1.15.0 1.15.2 1.15.3 1.15.5 1.15.6 1.15.7 1.16.0 1.16.10 1.16.11 1.16.12 1.16.13 1.16.14 1.16.15 1.16.16 1.16.17 1.16.20 1.16.21 1.16.22 1.16.23 1.16.24 1.16.25 1.16.26 1.16.28 1.16.29 1.16.32 1.16.34 1.16.35 1.16.36 1.16.37 1.16.4 1.16.40 1.16.41 1.16.42 1.16.43 1.16.44 1.16.45 1.16.46 1.16.47 1.16.48 1.16.49 1.16.5 1.16.50 1.16.51 1.16.53 1.16.55 1.16.56 1.16.59 1.16.6 1.16.60 1.16.61 1.16.62 1.16.63 1.16.64 1.16.65 1.16.66 1.16.67 1.16.68 1.16.69 1.16.7 1.16.8 1.16.9 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.14 1.2.15 1.2.16 1.2.17 1.2.19 1.2.2 1.2.20 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.33 1.2.35 1.2.36 1.2.38 1.2.39 1.2.4 1.2.40 1.2.41 1.2.42 1.2.43 1.2.44 1.2.45 1.2.46 1.2.5 1.2.7 1.2.8 1.2.9 1.22.1 1.22.10 1.22.11 1.22.12 1.22.14 1.22.15 1.22.16 1.22.17 1.22.18 1.22.19 1.22.20 1.22.21 1.22.22 1.22.23 1.22.24 1.22.3 1.22.4 1.22.5 1.22.6 1.22.7 1.22.8 1.22.9 1.23.1 1.23.10 1.23.11 1.23.12 1.23.13 1.23.15 1.23.16 1.23.2 1.23.3 1.23.4 1.23.5 1.23.6 1.23.7 1.23.8 1.23.9 1.24.1 1.24.10 1.24.11 1.24.12 1.24.2 trunk 1.24.3 0.7.4 1.24.4 0.7.7 1.24.5 0.8.28 1.24.6 0.8.29 1.24.7 0.8.30 1.24.8 0.8.31 1.24.9 0.8.32 1.25.1 0.8.33 1.25.2 0.8.36 1.25.3 0.8.37 1.25.5 0.8.50 1.25.6 0.8.51 1.25.7 0.9.1 1.25.8 0.9.10 1.25.9 0.9.11 1.26.1 0.9.12 1.26.2 0.9.2 1.3.10 0.9.20 1.3.12 0.9.21 1.3.14 0.9.22 1.3.15 1.0.10 1.3.17 1.0.11 1.3.18 1.0.12 1.3.19 1.0.15 1.3.2 1.0.16 1.3.20 1.0.18 1.3.22 1.0.20 1.3.23 1.0.3 1.3.24 1.0.4 1.3.25 1.0.5 1.3.3 1.0.6 1.3.4 1.0.7 1.3.6 1.0.8 1.3.7 1.0.9 1.3.8 1.1.0 1.3.9 1.1.10 1.4.0 1.1.11 1.4.10 1.1.12 1.4.11 1.1.13 1.4.12 1.1.14 1.4.13 1.1.15 1.4.14 1.1.16 1.4.15 1.1.17 1.4.2 1.1.2 1.4.27 1.1.3 1.4.28 1.1.5 1.4.29 1.1.6 1.4.30 1.1.8 1.4.4 1.1.9 1.4.48 1.10.1 1.4.5 1.10.3 1.4.6 1.11.1 1.4.7
updraftplus / includes / class-search-replace.php
updraftplus / includes Last commit date
Dropbox2 2 years ago Google 2 years ago blockui 1 year ago checkout-embed 1 year ago cloudfiles 2 years ago handlebars 2 years ago images 9 years ago jquery-ui.dialog.extended 1 year ago jquery.serializeJSON 5 years ago jstree 1 year ago labelauty 1 year ago pcloud 2 years ago tether 6 years ago tether-shepherd 7 years ago updraftclone 2 years ago S3.php 2 years ago S3compat.php 1 year ago cacert.pem 2 years ago class-backup-history.php 1 year ago class-commands.php 1 year ago class-database-utility.php 2 years ago class-filesystem-functions.php 1 year ago class-http-error-descriptions.php 2 years ago class-job-scheduler.php 3 years ago class-manipulation-functions.php 2 years ago class-partialfileservlet.php 5 years ago class-remote-send.php 2 years ago class-search-replace.php 3 years ago class-semaphore.php 3 years ago class-storage-methods-interface.php 2 years ago class-updraft-dashboard-news.php 2 years ago class-updraft-semaphore.php 4 years ago class-updraftcentral-updraftplus-commands.php 3 years ago class-updraftplus-encryption.php 2 years ago class-wpadmin-commands.php 1 year ago class-zip.php 2 years ago ftp.class.php 2 years ago get-cpanel-quota-usage.pl 12 years ago google-extensions.php 3 years ago jquery-ui.custom-v1.11.4-1-24-10.min.css 1 year ago jquery-ui.custom-v1.11.4-1-24-10.min.css.map 1 year ago jquery-ui.custom-v1.11.4.css 3 years ago jquery-ui.custom-v1.12.1-1-24-10.min.css 1 year ago jquery-ui.custom-v1.12.1-1-24-10.min.css.map 1 year ago jquery-ui.custom-v1.12.1.css 3 years ago migrator-lite.php 1 year ago updraft-admin-common-1-24-10.min.js 1 year ago updraft-admin-common.js 1 year ago updraft-restorer-skin-compatibility.php 6 years ago updraft-restorer-skin.php 3 years ago updraftcentral.php 2 years ago updraftplus-clone.php 2 years ago updraftplus-login.php 7 years ago updraftplus-notices.php 2 years ago updraftplus-tour.php 2 years ago updraftvault.php 3 years ago
class-search-replace.php
523 lines
1 <?php
2
3 if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
4
5 class UpdraftPlus_Search_Replace {
6
7 private $known_incomplete_classes = array();
8
9 private $columns = array();
10
11 private $current_row = 0;
12
13 private $use_wpdb = false;
14
15 private $use_mysqli = false;
16
17 private $wpdb_obj = null;
18
19 private $mysql_dbh = null;
20
21 protected $max_recursion = 0;
22
23 /**
24 * Constructor
25 */
26 public function __construct() {
27 add_action('updraftplus_restore_db_pre', array($this, 'updraftplus_restore_db_pre'));
28 $this->max_recursion = apply_filters('updraftplus_search_replace_max_recursion', 20);
29 }
30
31 /**
32 * This function is called via the filter updraftplus_restore_db_pre it sets up the search and replace database objects
33 *
34 * @return void
35 */
36 public function updraftplus_restore_db_pre() {
37 global $wpdb, $updraftplus_restorer;
38
39 $this->use_wpdb = $updraftplus_restorer->use_wpdb();
40 $this->wpdb_obj = $wpdb;
41
42 $mysql_dbh = false;
43 $use_mysqli = false;
44
45 if (!$this->use_wpdb) {
46 // We have our own extension which drops lots of the overhead on the query
47 $wpdb_obj = $updraftplus_restorer->get_db_object();
48 // Was that successful?
49 if (!$wpdb_obj->is_mysql || !$wpdb_obj->ready) {
50 $this->use_wpdb = true;
51 } else {
52 $this->wpdb_obj = $wpdb_obj;
53 $mysql_dbh = $wpdb_obj->updraftplus_get_database_handle();
54 $use_mysqli = $wpdb_obj->updraftplus_use_mysqli();
55 }
56 }
57
58 $this->mysql_dbh = $mysql_dbh;
59 $this->use_mysqli = $use_mysqli;
60 }
61
62 /**
63 * The engine
64 *
65 * @param string|array $search - a string or array of things to search for
66 * @param string|array $replace - a string or array of things to replace the search terms with
67 * @param array $tables - an array of tables
68 * @param integer $page_size - the page size
69 */
70 public function icit_srdb_replacer($search, $replace, $tables, $page_size) {
71
72 if (!is_array($tables)) return false;
73
74 global $wpdb, $updraftplus;
75
76 $report = array(
77 'tables' => 0,
78 'rows' => 0,
79 'change' => 0,
80 'updates' => 0,
81 'start' => microtime(true),
82 'end' => microtime(true),
83 'errors' => array(),
84 );
85
86 $page_size = (empty($page_size) || !is_numeric($page_size)) ? 5000 : $page_size;
87
88 foreach ($tables as $table => $stripped_table) {
89
90 $report['tables']++;
91
92 if ($search === $replace) {
93 $updraftplus->log("No search/replace required: would-be search and replacement are identical");
94 continue;
95 }
96
97 $this->columns = array();
98
99 $print_line = __('Search and replacing table:', 'updraftplus').' '.$table;
100
101 $updraftplus->check_db_connection($this->wpdb_obj, true);
102
103 // Get a list of columns in this table
104 $fields = $wpdb->get_results('DESCRIBE '.UpdraftPlus_Manipulation_Functions::backquote($table), ARRAY_A);
105
106 $prikey_field = false;
107 foreach ($fields as $column) {
108 $primary_key = ('PRI' == $column['Key']) ? true : false;
109 if ($primary_key) $prikey_field = $column['Field'];
110 if ('posts' == $stripped_table && 'guid' == $column['Field']) {
111 $updraftplus->log('Skipping search/replace on GUID column in posts table');
112 continue;
113 }
114 $this->columns[$column['Field']] = $primary_key;
115 }
116
117 // Count the number of rows we have in the table if large we'll split into blocks, This is a mod from Simon Wheatley
118
119 // InnoDB does not do count(*) quickly. You can use an index for more speed - see: http://www.cloudspace.com/blog/2009/08/06/fast-mysql-innodb-count-really-fast/
120
121 $where = '';
122 // Opportunity to use internal knowledge on tables which may be huge
123 if ('postmeta' == $stripped_table && ((is_array($search) && 0 === strpos($search[0], 'http')) || (is_string($search) && 0 === strpos($search, 'http')))) {
124 $where = " WHERE meta_value LIKE '%http%'";
125 }
126
127 $count_rows_sql = 'SELECT COUNT(*) FROM '.$table;
128 if ($prikey_field) $count_rows_sql .= " USE INDEX (PRIMARY)";
129 $count_rows_sql .= $where;
130
131 $row_countr = $wpdb->get_results($count_rows_sql, ARRAY_N);
132
133 // If that failed, try this
134 if (false !== $prikey_field && $wpdb->last_error) {
135 $row_countr = $wpdb->get_results("SELECT COUNT(*) FROM $table USE INDEX ($prikey_field)".$where, ARRAY_N);
136 if ($wpdb->last_error) $row_countr = $wpdb->get_results("SELECT COUNT(*) FROM $table", ARRAY_N);
137 }
138
139 $row_count = $row_countr[0][0];
140 $print_line .= ': '.sprintf(__('rows: %d', 'updraftplus'), $row_count);
141 $updraftplus->log($print_line, 'notice-restore', 'restoring-table-'.$table);
142 $updraftplus->log('Search and replacing table: '.$table.": rows: ".$row_count);
143
144 if (0 == $row_count) continue;
145
146 for ($on_row = 0; $on_row <= $row_count; $on_row = $on_row+$page_size) {
147
148 $this->current_row = 0;
149
150 if ($on_row>0) $updraftplus->log_e("Searching and replacing reached row: %d", $on_row);
151
152 // Grab the contents of the table
153 list($data, $page_size) = $this->fetch_sql_result($table, $on_row, $page_size, $where);
154 // $sql_line is calculated here only for the purpose of logging errors
155 // $where might contain a %, so don't place it inside the main parameter
156
157 $sql_line = sprintf('SELECT * FROM %s LIMIT %d, %d', $table.$where, $on_row, $on_row+$page_size);
158
159 // Our strategy here is to minimise memory usage if possible; to process one row at a time if we can, rather than reading everything into memory
160 if ($this->use_wpdb) {
161
162 if ($wpdb->last_error) {
163 $report['errors'][] = $this->print_error($sql_line);
164 } else {
165 foreach ($data as $row) {
166 $rowrep = $this->process_row($table, $row, $search, $replace, $stripped_table);
167 $report['rows']++;
168 $report['updates'] += $rowrep['updates'];
169 $report['change'] += $rowrep['change'];
170 foreach ($rowrep['errors'] as $err) $report['errors'][] = $err;
171 }
172 }
173 } else {
174 if (false === $data) {
175 $report['errors'][] = $this->print_error($sql_line);
176 } elseif (true !== $data && null !== $data) {
177 if ($this->use_mysqli) {
178 while ($row = mysqli_fetch_array($data)) {
179 $rowrep = $this->process_row($table, $row, $search, $replace, $stripped_table);
180 $report['rows']++;
181 $report['updates'] += $rowrep['updates'];
182 $report['change'] += $rowrep['change'];
183 foreach ($rowrep['errors'] as $err) $report['errors'][] = $err;
184 }
185 mysqli_free_result($data);
186 } else {
187 // phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- Ignore removed extension compatibility.
188 while ($row = mysql_fetch_array($data)) {
189 $rowrep = $this->process_row($table, $row, $search, $replace, $stripped_table);
190 $report['rows']++;
191 $report['updates'] += $rowrep['updates'];
192 $report['change'] += $rowrep['change'];
193 foreach ($rowrep['errors'] as $err) $report['errors'][] = $err;
194 }
195 @mysql_free_result($data); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- If an error occurs during mysql free result and it fails to free result, it will not impact anything at all. mysql_* function used in the scenario in which the mysqli extension doesn't exist.
196 }
197 }
198 }
199
200 }
201
202 }
203
204 $report['end'] = microtime(true);
205
206 return $report;
207 }
208
209 /**
210 * This function will get data from the passed in table ready to be search and replaced
211 *
212 * @param string $table - the table name
213 * @param integer $on_row - the row to start from
214 * @param integer $page_size - the page size
215 * @param string $where - the where condition
216 *
217 * @return array - an array of data or an array with a false value
218 */
219 private function fetch_sql_result($table, $on_row, $page_size, $where = '') {
220
221 $sql_line = sprintf('SELECT * FROM %s%s LIMIT %d, %d', $table, $where, $on_row, $page_size);
222
223 global $updraftplus;
224 $updraftplus->check_db_connection($this->wpdb_obj, true);
225
226 if ($this->use_wpdb) {
227 global $wpdb;
228 $data = $wpdb->get_results($sql_line, ARRAY_A);
229 if (!$wpdb->last_error) return array($data, $page_size);
230 } else {
231 if ($this->use_mysqli) {
232 $data = mysqli_query($this->mysql_dbh, $sql_line);
233 } else {
234 // phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- Ignore removed extension compatibility.
235 $data = mysql_query($sql_line, $this->mysql_dbh);
236 }
237 if (false !== $data) return array($data, $page_size);
238 }
239
240 if (5000 <= $page_size) return $this->fetch_sql_result($table, $on_row, 2000, $where);
241 if (2000 <= $page_size) return $this->fetch_sql_result($table, $on_row, 500, $where);
242
243 // At this point, $page_size should be 500; and that failed
244 return array(false, $page_size);
245
246 }
247
248 /**
249 * This function will process a single row from the database calling recursive_unserialize_replace to search and replace the data found in the search and replace arrays
250 *
251 * @param string $table - the current table we are working on
252 * @param array $row - the current row we are working on
253 * @param array $search - an array of things to search for
254 * @param array $replace - an array of things to replace the search terms with
255 * @param string $stripped_table - the stripped table
256 *
257 * @return array - returns an array report which includes changes made and any errors
258 */
259 private function process_row($table, $row, $search, $replace, $stripped_table) {
260
261 global $updraftplus, $wpdb, $updraftplus_restorer;
262
263 $report = array('change' => 0, 'errors' => array(), 'updates' => 0);
264
265 $this->current_row++;
266
267 $update_sql = array();
268 $where_sql = array();
269 $upd = false;
270
271 foreach ($this->columns as $column => $primary_key) {
272
273 // Don't search/replace these
274 if (('options' == $stripped_table && 'option_value' == $column && !empty($row['option_name']) && 'updraft_remotesites' == $row['option_name']) || ('sitemeta' == $stripped_table && 'meta_value' == $column && !empty($row['meta_key']) && 'updraftplus_options' == $row['meta_key'])) {
275 continue;
276 }
277
278 $edited_data = $data_to_fix = $row[$column];
279 $successful = false;
280
281 // We catch errors/exceptions so that they're not fatal. Once saw a fatal ("Cannot access empty property") on "if (is_a($value, '__PHP_Incomplete_Class')) {" (not clear what $value has to be to cause that).
282 try {
283 // Run a search replace on the data that'll respect the serialisation.
284 $edited_data = $this->recursive_unserialize_replace($search, $replace, $data_to_fix);
285 $successful = true;
286 } catch (Exception $e) {
287 $log_message = 'An Exception ('.get_class($e).') occurred during the recursive search/replace. Exception message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
288 $report['errors'][] = $log_message;
289 error_log($log_message);
290 $updraftplus->log($log_message);
291 $updraftplus->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'warning-restore');
292 // @codingStandardsIgnoreLine
293 } catch (Error $e) {
294 $log_message = 'A PHP Fatal error (recoverable, '.get_class($e).') occurred during the recursive search/replace. Exception message: Error message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
295 $report['errors'][] = $log_message;
296 error_log($log_message);
297 $updraftplus->log($log_message);
298 $updraftplus->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'warning-restore');
299 }
300
301 // Something was changed
302 if ($successful && $edited_data != $data_to_fix) {
303 $report['change']++;
304 $ed = $edited_data;
305 $wpdb->escape_by_ref($ed);
306 // Undo breakage introduced in WP 4.8.3 core
307 if (is_callable(array($wpdb, 'remove_placeholder_escape'))) $ed = $wpdb->remove_placeholder_escape($ed);
308 $update_sql[] = UpdraftPlus_Manipulation_Functions::backquote($column) . ' = "' . $ed . '"';
309 $upd = true;
310 }
311
312 if ($primary_key) {
313 $df = $data_to_fix;
314 $wpdb->escape_by_ref($df);
315 // Undo breakage introduced in WP 4.8.3 core
316 if (is_callable(array($wpdb, 'remove_placeholder_escape'))) $df = $wpdb->remove_placeholder_escape($df);
317 $where_sql[] = UpdraftPlus_Manipulation_Functions::backquote($column) . ' = "' . $df . '"';
318 }
319 }
320
321 if ($upd && !empty($where_sql)) {
322 $sql = 'UPDATE '.UpdraftPlus_Manipulation_Functions::backquote($table).' SET '.implode(', ', $update_sql).' WHERE '.implode(' AND ', array_filter($where_sql));
323 $result = $updraftplus_restorer->sql_exec($sql, 5, '', false);
324 if (false === $result || is_wp_error($result)) {
325 $last_error = $this->print_error($sql);
326 $report['errors'][] = $last_error;
327 } else {
328 $report['updates']++;
329 }
330
331 } elseif ($upd) {
332 $report['errors'][] = sprintf('"%s" has no primary key, manual change needed on row %s.', $table, $this->current_row);
333 $updraftplus->log(__('Error:', 'updraftplus').' '.sprintf(__('"%s" has no primary key, manual change needed on row %s.', 'updraftplus'), $table, $this->current_row), 'warning-restore');
334 }
335
336 return $report;
337
338 }
339
340 /**
341 * Inspect incomplete class object and make a note in the restoration log if it is a new class
342 *
343 * @param object $data Object expected to be of __PHP_Incomplete_Class_Name
344 */
345 private function unserialize_log_incomplete_class($data) {
346 global $updraftplus;
347
348 try {
349 $patch_object = new ArrayObject($data);
350 $class_name = $patch_object['__PHP_Incomplete_Class_Name'];
351 } catch (Exception $e) {
352 error_log('unserialize_log_incomplete_class: '.$e->getMessage());
353 // @codingStandardsIgnoreLine
354 } catch (Error $e) {
355 error_log('unserialize_log_incomplete_class: '.$e->getMessage());
356 }
357
358 // Check if this class is known
359 // Have to serialize incomplete class to find original class name
360 if (!in_array($class_name, $this->known_incomplete_classes)) {
361 $this->known_incomplete_classes[] = $class_name;
362 $updraftplus->log('Incomplete object detected in database: '.$class_name.'; Search and replace will be skipped for these entries');
363 }
364 }
365
366 /**
367 * Take a serialised array and unserialise it replacing elements as needed and
368 * unserialising any subordinate arrays and performing the replace on those too.
369 * N.B. $from and $to can be arrays - they get passed only to str_replace(), which can take an array
370 *
371 * @param string $from String we're looking to replace.
372 * @param string $to What we want it to be replaced with
373 * @param array $data Used to pass any subordinate arrays back to in.
374 * @param bool $serialised Does the array passed via $data need serialising.
375 * @param int $recursion_level Current recursion depth within the original data.
376 * @param array $visited_data Data that has been seen in previous recursion iterations.
377 *
378 * @return array The original array with all elements replaced as needed.
379 */
380 private function recursive_unserialize_replace($from = '', $to = '', $data = '', $serialised = false, $recursion_level = 0, $visited_data = array()) {
381
382 global $updraftplus;
383
384 static $error_count = 0;
385
386 // some unserialised data cannot be re-serialised eg. SimpleXMLElements
387 try {
388 $case_insensitive = false;
389
390 // If we've reached the maximum recursion level, short circuit
391 if (0 !== $this->max_recursion && $recursion_level >= $this->max_recursion) {
392 return $data;
393 }
394
395 if (is_array($data) || is_object($data)) {
396 // If we've seen this exact object or array before, short circuit
397 if (in_array($data, $visited_data, true)) {
398 return $data; // Avoid infinite recursions when there's a circular reference
399 }
400 // Add this data to the list of
401 $visited_data[] = $data;
402 }
403
404 if (is_array($from) && is_array($to)) {
405 $case_insensitive = preg_match('#^https?:#i', implode($from)) && preg_match('#^https?:#i', implode($to)) ? true : false;
406 } else {
407 $case_insensitive = preg_match('#^https?:#i', $from) && preg_match('#^https?:#i', $to) ? true : false;
408 }
409
410 // O:8:"DateTime":0:{} : see https://bugs.php.net/bug.php?id=62852
411 if (is_serialized($data) && false === strpos($data, 'O:8:"DateTime":0:{}') && false !== ($unserialized = @unserialize($data))) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
412 $data = $this->recursive_unserialize_replace($from, $to, $unserialized, true, $recursion_level + 1);
413 } elseif (is_array($data)) {
414 $_tmp = array();
415 foreach ($data as $key => $value) {
416 // Check that we aren't attempting search/replace on an incomplete class
417 // We assume that if $data is an __PHP_Incomplete_Class, it is extremely likely that the original did not contain the domain
418 if (is_a($value, '__PHP_Incomplete_Class')) {
419 // Check if this class is known
420 $this->unserialize_log_incomplete_class($value);
421
422 // return original data
423 $_tmp[$key] = $value;
424 } else {
425 $_tmp[$key] = $this->recursive_unserialize_replace($from, $to, $value, false, $recursion_level + 1, $visited_data);
426 }
427 }
428
429 $data = $_tmp;
430 unset($_tmp);
431 } elseif (is_object($data)) {
432 $_tmp = $data; // new $data_class();
433 // Check that we aren't attempting search/replace on an incomplete class
434 // We assume that if $data is an __PHP_Incomplete_Class, it is extremely likely that the original did not contain the domain
435 if (is_a($data, '__PHP_Incomplete_Class')) {
436 // Check if this class is known
437 $this->unserialize_log_incomplete_class($data);
438 } else {
439 $props = get_object_vars($data);
440 foreach ($props as $key => $value) {
441 $_tmp->$key = $this->recursive_unserialize_replace($from, $to, $value, false, $recursion_level + 1, $visited_data);
442 }
443 }
444 $data = $_tmp;
445 unset($_tmp);
446 } elseif (is_string($data) && (null !== ($_tmp = json_decode($data, true)))) {
447
448 if (is_array($_tmp)) {
449 foreach ($_tmp as $key => $value) {
450 // Check that we aren't attempting search/replace on an incomplete class
451 // We assume that if $data is an __PHP_Incomplete_Class, it is extremely likely that the original did not contain the domain
452 if (is_a($value, '__PHP_Incomplete_Class')) {
453 // Check if this class is known
454 $this->unserialize_log_incomplete_class($value);
455
456 // return original data
457 $_tmp[$key] = $value;
458 } else {
459 $_tmp[$key] = $this->recursive_unserialize_replace($from, $to, $value, false, $recursion_level + 1, $visited_data);
460 }
461 }
462
463 $data = json_encode($_tmp);
464 unset($_tmp);
465 }
466
467 } else {
468 if (is_string($data)) {
469 if ($case_insensitive) {
470 $data = str_ireplace($from, $to, $data);
471 } else {
472 $data = str_replace($from, $to, $data);
473 }
474 // Below is the wrong approach. In fact, in the problematic case, the resolution is an extra search/replace to undo unnecessary ones
475 // if (is_string($from)) {
476 // $data = str_replace($from, $to, $data);
477 // } else {
478 // # Array. We only want a maximum of one replacement to take place. This is only an issue in non-default setups, but in those situations, carrying out all the search/replaces can be wrong. This is also why the most specific URL should be done first.
479 // foreach ($from as $i => $f) {
480 // $ndata = str_replace($f, $to[$i], $data);
481 // if ($ndata != $data) {
482 // $data = $ndata;
483 // break;
484 // }
485 // }
486 // }
487 }
488 }
489
490 if ($serialised)
491 return serialize($data);
492
493 } catch (Exception $error) {
494 if (3 > $error_count) {
495 $log_message = 'PHP Fatal Exception error ('.get_class($error).') has occurred during recursive_unserialize_replace. Error Message: '.$error->getMessage().' (Code: '.$error->getCode().', line '.$error->getLine().' in '.$error->getFile().')';
496 $updraftplus->log($log_message, 'warning-restore');
497 $error_count++;
498 }
499 }
500
501 return $data;
502 }
503
504 /**
505 * This function will get the last database error and log it
506 *
507 * @param string $sql_line - the sql line that caused the error
508 *
509 * @return void
510 */
511 public function print_error($sql_line) {
512 global $wpdb, $updraftplus;
513 if ($this->use_wpdb) {
514 $last_error = $wpdb->last_error;
515 } else {
516 // phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- Ignore removed extension compatibility.
517 $last_error = ($this->use_mysqli) ? mysqli_error($this->mysql_dbh) : mysql_error($this->mysql_dbh);
518 }
519 $updraftplus->log(__('Error:', 'updraftplus')." ".$last_error." - ".__('the database query being run was:', 'updraftplus').' '.$sql_line, 'warning-restore');
520 return $last_error;
521 }
522 }
523