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 |