| 1 |
<?php |
| 2 |
/** |
| 3 |
* Attachment db renamer class. |
| 4 |
*/ |
| 5 |
|
| 6 |
/** |
| 7 |
* URL Replacer for WordPress |
| 8 |
* |
| 9 |
* A standalone class to replace URLs in WordPress database, |
| 10 |
* including handling image size variations and scaled images. |
| 11 |
* |
| 12 |
* @since 4.0.0 |
| 13 |
*/ |
| 14 |
class Optml_Attachment_Db_Renamer { |
| 15 |
/** |
| 16 |
* Tables to skip during replacement |
| 17 |
* |
| 18 |
* @var array |
| 19 |
*/ |
| 20 |
private $skip_tables = [ 'users', 'terms', 'term_relationships', 'term_taxonomy' ]; |
| 21 |
|
| 22 |
/** |
| 23 |
* Columns to skip during replacement |
| 24 |
* |
| 25 |
* @var array |
| 26 |
*/ |
| 27 |
private $skip_columns = [ 'user_pass' ]; |
| 28 |
|
| 29 |
/** |
| 30 |
* Handle image size variations |
| 31 |
* |
| 32 |
* @var bool |
| 33 |
*/ |
| 34 |
private $handle_image_sizes = false; |
| 35 |
|
| 36 |
/** |
| 37 |
* Constructor |
| 38 |
*/ |
| 39 |
public function __construct( $skip_sizes = false ) { |
| 40 |
$this->handle_image_sizes = ! $skip_sizes; |
| 41 |
} |
| 42 |
|
| 43 |
/** |
| 44 |
* Replace URLs in the WordPress database |
| 45 |
* |
| 46 |
* @param string $old_url The base URL to search for (e.g., http://domain.com/wp-content/uploads/2025/03/image.jpg). |
| 47 |
* @param string $new_url The base URL to replace with (e.g., http://domain.com/wp-content/uploads/2025/03/new-name.jpg). |
| 48 |
* |
| 49 |
* @return int Number of replacements made |
| 50 |
*/ |
| 51 |
public function replace( $old_url, $new_url ) { |
| 52 |
if ( $old_url === $new_url ) { |
| 53 |
return 0; |
| 54 |
} |
| 55 |
|
| 56 |
if ( empty( $old_url ) || empty( $new_url ) ) { |
| 57 |
return 0; |
| 58 |
} |
| 59 |
|
| 60 |
if ( ! is_string( $old_url ) || ! is_string( $new_url ) ) { |
| 61 |
return 0; |
| 62 |
} |
| 63 |
|
| 64 |
$tables = $this->get_tables(); |
| 65 |
$total_replacements = 0; |
| 66 |
|
| 67 |
foreach ( $tables as $table ) { |
| 68 |
if ( in_array( $table, $this->skip_tables, true ) ) { |
| 69 |
continue; |
| 70 |
} |
| 71 |
|
| 72 |
list($primary_keys, $columns) = $this->get_columns( $table ); |
| 73 |
|
| 74 |
// Skip tables with no primary keys |
| 75 |
if ( empty( $primary_keys ) ) { |
| 76 |
continue; |
| 77 |
} |
| 78 |
|
| 79 |
foreach ( $columns as $column ) { |
| 80 |
if ( in_array( $column, $this->skip_columns, true ) ) { |
| 81 |
continue; |
| 82 |
} |
| 83 |
|
| 84 |
$replacements = $this->process_column( $table, $column, $primary_keys, $old_url, $new_url ); |
| 85 |
$total_replacements += $replacements; |
| 86 |
} |
| 87 |
} |
| 88 |
|
| 89 |
return $total_replacements; |
| 90 |
} |
| 91 |
|
| 92 |
/** |
| 93 |
* Get WordPress tables |
| 94 |
* |
| 95 |
* @return array Table names |
| 96 |
*/ |
| 97 |
private function get_tables() { |
| 98 |
global $wpdb; |
| 99 |
|
| 100 |
return array_values( $wpdb->tables() ); |
| 101 |
} |
| 102 |
|
| 103 |
/** |
| 104 |
* Get columns for a table |
| 105 |
* |
| 106 |
* @param string $table Table name. |
| 107 |
* |
| 108 |
* @return array Array containing primary keys and text columns |
| 109 |
*/ |
| 110 |
private function get_columns( $table ) { |
| 111 |
global $wpdb; |
| 112 |
|
| 113 |
$primary_keys = []; |
| 114 |
$text_columns = []; |
| 115 |
|
| 116 |
// Get table information |
| 117 |
$results = $wpdb->get_results( $wpdb->prepare( 'DESCRIBE %i', $table ) ); |
| 118 |
|
| 119 |
if ( ! empty( $results ) ) { |
| 120 |
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase |
| 121 |
foreach ( $results as $col ) { |
| 122 |
if ( 'PRI' === $col->Key ) { |
| 123 |
$primary_keys[] = $col->Field; |
| 124 |
} |
| 125 |
if ( $this->is_text_col( $col->Type ) ) { |
| 126 |
$text_columns[] = $col->Field; |
| 127 |
} |
| 128 |
} |
| 129 |
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase |
| 130 |
} |
| 131 |
|
| 132 |
return [ $primary_keys, $text_columns ]; |
| 133 |
} |
| 134 |
|
| 135 |
/** |
| 136 |
* Check if column is text type |
| 137 |
* |
| 138 |
* @param string $type Column type. |
| 139 |
* |
| 140 |
* @return bool True if text column |
| 141 |
*/ |
| 142 |
private function is_text_col( $type ) { |
| 143 |
foreach ( [ 'text', 'varchar', 'longtext', 'mediumtext', 'char' ] as $token ) { |
| 144 |
if ( false !== stripos( $type, $token ) ) { |
| 145 |
return true; |
| 146 |
} |
| 147 |
} |
| 148 |
return false; |
| 149 |
} |
| 150 |
|
| 151 |
/** |
| 152 |
* Process a single column for replacements |
| 153 |
* |
| 154 |
* @param string $table Table name. |
| 155 |
* @param string $column Column name. |
| 156 |
* @param array $primary_keys Primary keys. |
| 157 |
* @param string $old_url Old URL. |
| 158 |
* @param string $new_url New URL. |
| 159 |
* |
| 160 |
* @return int Number of replacements |
| 161 |
*/ |
| 162 |
private function process_column( $table, $column, $primary_keys, $old_url, $new_url ) { |
| 163 |
global $wpdb; |
| 164 |
|
| 165 |
$count = 0; |
| 166 |
|
| 167 |
// Check for serialized data |
| 168 |
$has_serialized = $wpdb->get_var( |
| 169 |
$wpdb->prepare( |
| 170 |
"SELECT COUNT(%i) FROM %i WHERE %i REGEXP '^[aiO]:[1-9]' LIMIT 1", |
| 171 |
$column, |
| 172 |
$table, |
| 173 |
$column |
| 174 |
) |
| 175 |
); |
| 176 |
|
| 177 |
// Process with PHP if serialized data is found |
| 178 |
if ( $has_serialized ) { |
| 179 |
$count = $this->php_handle_column( $table, $column, $primary_keys, $old_url, $new_url ); |
| 180 |
} else { |
| 181 |
// Use direct SQL replacement for non-serialized data |
| 182 |
$count = $this->sql_handle_column( $table, $column, $old_url, $new_url ); |
| 183 |
} |
| 184 |
|
| 185 |
return $count; |
| 186 |
} |
| 187 |
|
| 188 |
/** |
| 189 |
* Handle column using SQL replacement |
| 190 |
* |
| 191 |
* @param string $table Table name. |
| 192 |
* @param string $column Column name. |
| 193 |
* @param string $old_url Old URL. |
| 194 |
* @param string $new_url New URL. |
| 195 |
* |
| 196 |
* @return int Number of replacements |
| 197 |
*/ |
| 198 |
private function sql_handle_column( $table, $column, $old_url, $new_url ) { |
| 199 |
global $wpdb; |
| 200 |
$count = 0; |
| 201 |
|
| 202 |
// Get the filename components |
| 203 |
$old_path_parts = parse_url( $old_url ); |
| 204 |
if ( ! isset( $old_path_parts['path'] ) ) { |
| 205 |
return 0; |
| 206 |
} |
| 207 |
|
| 208 |
$old_path = $old_path_parts['path']; |
| 209 |
$old_file_info = pathinfo( $old_path ); |
| 210 |
|
| 211 |
$old_base = $old_file_info['filename']; |
| 212 |
$old_dir = dirname( $old_path ); |
| 213 |
$old_domain = isset( $old_path_parts['host'] ) ? 'http' . ( isset( $old_path_parts['scheme'] ) && $old_path_parts['scheme'] === 'https' ? 's' : '' ) . '://' . $old_path_parts['host'] : ''; |
| 214 |
|
| 215 |
// Build pattern to match any URL containing the base filename |
| 216 |
$base_url = $old_domain . $old_dir . '/' . $old_base; |
| 217 |
|
| 218 |
// Get rows with regular URLs |
| 219 |
$rows = $wpdb->get_results( |
| 220 |
$wpdb->prepare( |
| 221 |
'SELECT * FROM %i WHERE %i LIKE %s', |
| 222 |
$table, |
| 223 |
$column, |
| 224 |
'%' . $wpdb->esc_like( $base_url ) . '%' |
| 225 |
) |
| 226 |
); |
| 227 |
|
| 228 |
// Also create a pattern for JSON-escaped version |
| 229 |
$json_base_url = str_replace( '/', '\/', $base_url ); |
| 230 |
|
| 231 |
// Get rows with JSON-escaped URLs |
| 232 |
$json_rows = $wpdb->get_results( |
| 233 |
$wpdb->prepare( |
| 234 |
'SELECT * FROM %i WHERE %i LIKE %s', |
| 235 |
$table, |
| 236 |
$column, |
| 237 |
'%' . $wpdb->esc_like( $json_base_url ) . '%' |
| 238 |
) |
| 239 |
); |
| 240 |
|
| 241 |
// Merge results, avoiding duplicates |
| 242 |
$processed_ids = []; |
| 243 |
$all_rows = array_merge( $rows, $json_rows ); |
| 244 |
|
| 245 |
if ( empty( $all_rows ) ) { |
| 246 |
return 0; |
| 247 |
} |
| 248 |
|
| 249 |
foreach ( $all_rows as $row ) { |
| 250 |
$id_field = $row->ID ?? $row->id ?? null; |
| 251 |
if ( ! $id_field ) { |
| 252 |
foreach ( $row as $field => $value ) { |
| 253 |
if ( stripos( $field, 'id' ) !== false ) { |
| 254 |
$id_field = $value; |
| 255 |
break; |
| 256 |
} |
| 257 |
} |
| 258 |
} |
| 259 |
|
| 260 |
if ( ! $id_field ) { |
| 261 |
continue; |
| 262 |
} |
| 263 |
|
| 264 |
// Skip if we've already processed this row |
| 265 |
if ( isset( $processed_ids[ $id_field ] ) ) { |
| 266 |
continue; |
| 267 |
} |
| 268 |
$processed_ids[ $id_field ] = true; |
| 269 |
|
| 270 |
$content = $row->$column; |
| 271 |
$new_content = $this->replace_image_urls( $content, $old_url, $new_url ); |
| 272 |
|
| 273 |
if ( $content !== $new_content ) { |
| 274 |
$wpdb->update( |
| 275 |
$table, |
| 276 |
[ $column => $new_content ], |
| 277 |
[ 'ID' => $id_field ] |
| 278 |
); |
| 279 |
++$count; |
| 280 |
} |
| 281 |
} |
| 282 |
|
| 283 |
return $count; |
| 284 |
} |
| 285 |
|
| 286 |
/** |
| 287 |
* Handle column using PHP for serialized data |
| 288 |
* |
| 289 |
* @param string $table Table name. |
| 290 |
* @param string $column Column name. |
| 291 |
* @param array $primary_keys Primary keys. |
| 292 |
* @param string $old_url Old URL. |
| 293 |
* @param string $new_url New URL. |
| 294 |
* |
| 295 |
* @return int Number of replacements |
| 296 |
*/ |
| 297 |
private function php_handle_column( $table, $column, $primary_keys, $old_url, $new_url ) { |
| 298 |
global $wpdb; |
| 299 |
|
| 300 |
$count = 0; |
| 301 |
$json_old_url = str_replace( '/', '\/', $old_url ); |
| 302 |
|
| 303 |
// Build the query and allow processing with multiple primary keys. |
| 304 |
$query = 'SELECT '; |
| 305 |
foreach ( $primary_keys as $key => $value ) { |
| 306 |
$query .= $wpdb->prepare( '%i, ', $value ); |
| 307 |
} |
| 308 |
$query .= '%i FROM %i WHERE %s LIKE %s LIMIT 100'; |
| 309 |
|
| 310 |
// Get the rows that need updating - first for regular URLs |
| 311 |
$rows = $wpdb->get_results( |
| 312 |
$wpdb->prepare( |
| 313 |
$query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
| 314 |
$column, |
| 315 |
$table, |
| 316 |
$column, |
| 317 |
'%' . $wpdb->esc_like( $old_url ) . '%' |
| 318 |
) |
| 319 |
); |
| 320 |
|
| 321 |
// Also get rows with JSON-escaped URLs |
| 322 |
$json_rows = $wpdb->get_results( |
| 323 |
$wpdb->prepare( |
| 324 |
$query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
| 325 |
$column, |
| 326 |
$table, |
| 327 |
$column, |
| 328 |
'%' . $wpdb->esc_like( $json_old_url ) . '%' |
| 329 |
) |
| 330 |
); |
| 331 |
|
| 332 |
// Merge results, avoiding duplicates |
| 333 |
$processed_ids = []; |
| 334 |
$all_rows = array_merge( $rows, $json_rows ); |
| 335 |
|
| 336 |
foreach ( $all_rows as $row ) { |
| 337 |
// Generate a unique identifier for this row based on primary keys |
| 338 |
$row_id = ''; |
| 339 |
foreach ( $primary_keys as $key ) { |
| 340 |
$row_id .= $row->$key . '|'; |
| 341 |
} |
| 342 |
|
| 343 |
// Skip if we've already processed this row |
| 344 |
if ( isset( $processed_ids[ $row_id ] ) ) { |
| 345 |
continue; |
| 346 |
} |
| 347 |
|
| 348 |
$processed_ids[ $row_id ] = true; |
| 349 |
$value = $row->$column; |
| 350 |
|
| 351 |
// Skip empty values |
| 352 |
if ( empty( $value ) ) { |
| 353 |
continue; |
| 354 |
} |
| 355 |
|
| 356 |
// Replace URLs in the value (handling serialized data) |
| 357 |
$new_value = $this->replace_urls_in_value( $value, $old_url, $new_url ); |
| 358 |
|
| 359 |
// Skip if no change |
| 360 |
if ( $value === $new_value ) { |
| 361 |
continue; |
| 362 |
} |
| 363 |
|
| 364 |
// Build WHERE clause for this row |
| 365 |
$where_conditions = []; |
| 366 |
foreach ( $primary_keys as $key ) { |
| 367 |
$where_conditions[ $key ] = $row->$key; |
| 368 |
} |
| 369 |
|
| 370 |
// Update the row |
| 371 |
$updated = $wpdb->update( |
| 372 |
$table, |
| 373 |
[ $column => $new_value ], |
| 374 |
$where_conditions |
| 375 |
); |
| 376 |
|
| 377 |
if ( $updated ) { |
| 378 |
++$count; |
| 379 |
} |
| 380 |
} |
| 381 |
|
| 382 |
return $count; |
| 383 |
} |
| 384 |
|
| 385 |
/** |
| 386 |
* Replace URLs in a value, handling serialized data |
| 387 |
* |
| 388 |
* @param string $value The value to process. |
| 389 |
* @param string $old_url Old URL. |
| 390 |
* @param string $new_url New URL. |
| 391 |
* |
| 392 |
* @return string The processed value |
| 393 |
*/ |
| 394 |
private function replace_urls_in_value( $value, $old_url, $new_url ) { |
| 395 |
// Check if the value is serialized |
| 396 |
if ( $this->is_serialized( $value ) ) { |
| 397 |
$unserialized = @unserialize( $value ); |
| 398 |
|
| 399 |
// If unserialize successful, process the data |
| 400 |
if ( $unserialized !== false ) { |
| 401 |
$replaced = $this->replace_in_data( $unserialized, $old_url, $new_url ); |
| 402 |
return serialize( $replaced ); |
| 403 |
} |
| 404 |
} |
| 405 |
|
| 406 |
// Handle image sizes for non-serialized content |
| 407 |
if ( $this->handle_image_sizes ) { |
| 408 |
return $this->replace_image_urls( $value, $old_url, $new_url ); |
| 409 |
} |
| 410 |
|
| 411 |
// Simple string replacement for non-serialized data |
| 412 |
return str_replace( $old_url, $new_url, $value ); |
| 413 |
} |
| 414 |
|
| 415 |
/** |
| 416 |
* Replace image URLs including various WordPress size variations and scaled images |
| 417 |
* |
| 418 |
* @param string $content The content to process. |
| 419 |
* @param string $old_url Old URL pattern. |
| 420 |
* @param string $new_url New URL pattern. |
| 421 |
* |
| 422 |
* @return string The processed content |
| 423 |
*/ |
| 424 |
private function replace_image_urls( $content, $old_url, $new_url ) { |
| 425 |
// Get the filename components |
| 426 |
$old_path_parts = parse_url( $old_url ); |
| 427 |
$new_path_parts = parse_url( $new_url ); |
| 428 |
|
| 429 |
if ( ! isset( $old_path_parts['path'] ) || ! isset( $new_path_parts['path'] ) ) { |
| 430 |
// If we can't parse the URLs, fallback to direct replacement |
| 431 |
return str_replace( $old_url, $new_url, $content ); |
| 432 |
} |
| 433 |
|
| 434 |
// Extract file name info |
| 435 |
$old_path = $old_path_parts['path']; |
| 436 |
$new_path = $new_path_parts['path']; |
| 437 |
|
| 438 |
$old_file_info = pathinfo( $old_path ); |
| 439 |
$new_file_info = pathinfo( $new_path ); |
| 440 |
|
| 441 |
$old_base = $old_file_info['filename']; |
| 442 |
$new_base = $new_file_info['filename']; |
| 443 |
$old_ext = isset( $old_file_info['extension'] ) ? $old_file_info['extension'] : ''; |
| 444 |
$new_ext = isset( $new_file_info['extension'] ) ? $new_file_info['extension'] : $old_ext; |
| 445 |
|
| 446 |
// Define domain parts |
| 447 |
$old_domain = isset( $old_path_parts['host'] ) ? 'http' . ( isset( $old_path_parts['scheme'] ) && $old_path_parts['scheme'] === 'https' ? 's' : '' ) . '://' . $old_path_parts['host'] : ''; |
| 448 |
$new_domain = isset( $new_path_parts['host'] ) ? 'http' . ( isset( $new_path_parts['scheme'] ) && $new_path_parts['scheme'] === 'https' ? 's' : '' ) . '://' . $new_path_parts['host'] : ''; |
| 449 |
|
| 450 |
// Replace original URLs |
| 451 |
$content = str_replace( $old_url, $new_url, $content ); |
| 452 |
|
| 453 |
// Replace JSON-escaped URLs |
| 454 |
$json_old_url = str_replace( '/', '\/', $old_url ); |
| 455 |
$json_new_url = str_replace( '/', '\/', $new_url ); |
| 456 |
$content = str_replace( $json_old_url, $json_new_url, $content ); |
| 457 |
|
| 458 |
// If we have a file with extension, handle variations |
| 459 |
if ( ! empty( $old_ext ) && $this->handle_image_sizes ) { |
| 460 |
$old_dir = dirname( $old_path ); |
| 461 |
$new_dir = dirname( $new_path ); |
| 462 |
|
| 463 |
// Replace WordPress image size variations (e.g., image-300x200.jpg) |
| 464 |
$size_pattern = '/' . preg_quote( $old_domain . $old_dir . '/' . $old_base, '/' ) . '-\d+x\d+\.' . preg_quote( $old_ext, '/' ) . '/'; |
| 465 |
|
| 466 |
$content = preg_replace_callback( |
| 467 |
$size_pattern, |
| 468 |
function ( $matches ) use ( $old_base, $new_base, $old_domain, $new_domain, $old_dir, $new_dir, $old_ext, $new_ext ) { |
| 469 |
// Extract the size part (e.g., -300x200) |
| 470 |
$size_part = substr( $matches[0], strlen( $old_domain . $old_dir . '/' . $old_base ), -strlen( '.' . $old_ext ) ); |
| 471 |
|
| 472 |
// Build the new URL with the same size |
| 473 |
return $new_domain . $new_dir . '/' . $new_base . $size_part . '.' . $new_ext; |
| 474 |
}, |
| 475 |
$content |
| 476 |
); |
| 477 |
|
| 478 |
// Replace -scaled variations |
| 479 |
$scaled_pattern = '/' . preg_quote( $old_domain . $old_dir . '/' . $old_base, '/' ) . '-scaled\.' . preg_quote( $old_ext, '/' ) . '/'; |
| 480 |
|
| 481 |
$content = preg_replace_callback( |
| 482 |
$scaled_pattern, |
| 483 |
function ( $matches ) use ( $new_base, $new_domain, $new_dir, $new_ext ) { |
| 484 |
return $new_domain . $new_dir . '/' . $new_base . '-scaled.' . $new_ext; |
| 485 |
}, |
| 486 |
$content |
| 487 |
); |
| 488 |
|
| 489 |
// Replace JSON-escaped variations |
| 490 |
$json_size_pattern = '/' . preg_quote( str_replace( '/', '\/', $old_domain . $old_dir . '/' . $old_base ), '/' ) . '-\d+x\d+\.' . preg_quote( $old_ext, '/' ) . '/'; |
| 491 |
|
| 492 |
$content = preg_replace_callback( |
| 493 |
$json_size_pattern, |
| 494 |
function ( $matches ) use ( $old_base, $new_base, $old_domain, $new_domain, $old_dir, $new_dir, $old_ext, $new_ext ) { |
| 495 |
// Extract the size part (e.g., -300x200) |
| 496 |
$size_part = substr( $matches[0], strlen( str_replace( '/', '\/', $old_domain . $old_dir . '/' . $old_base ) ), -strlen( '.' . $old_ext ) ); |
| 497 |
|
| 498 |
// Build the new URL with the same size |
| 499 |
return str_replace( '/', '\/', $new_domain . $new_dir . '/' . $new_base . $size_part . '.' . $new_ext ); |
| 500 |
}, |
| 501 |
$content |
| 502 |
); |
| 503 |
|
| 504 |
// Replace JSON-escaped scaled variations |
| 505 |
$json_scaled_pattern = '/' . preg_quote( str_replace( '/', '\/', $old_domain . $old_dir . '/' . $old_base ), '/' ) . '-scaled\.' . preg_quote( $old_ext, '/' ) . '/'; |
| 506 |
|
| 507 |
$content = preg_replace_callback( |
| 508 |
$json_scaled_pattern, |
| 509 |
function ( $matches ) use ( $new_base, $new_domain, $new_dir, $new_ext ) { |
| 510 |
return str_replace( '/', '\/', $new_domain . $new_dir . '/' . $new_base . '-scaled.' . $new_ext ); |
| 511 |
}, |
| 512 |
$content |
| 513 |
); |
| 514 |
} |
| 515 |
|
| 516 |
return $content; |
| 517 |
} |
| 518 |
|
| 519 |
/** |
| 520 |
* Recursively replace URLs in data structure |
| 521 |
* |
| 522 |
* @param mixed $data The data to process. |
| 523 |
* @param string $old_url Old URL. |
| 524 |
* @param string $new_url New URL. |
| 525 |
* |
| 526 |
* @return mixed The processed data |
| 527 |
*/ |
| 528 |
private function replace_in_data( $data, $old_url, $new_url ) { |
| 529 |
if ( is_array( $data ) ) { |
| 530 |
// Process arrays recursively |
| 531 |
foreach ( $data as $key => $value ) { |
| 532 |
$data[ $key ] = $this->replace_in_data( $value, $old_url, $new_url ); |
| 533 |
} |
| 534 |
} elseif ( is_object( $data ) ) { |
| 535 |
// Process objects recursively |
| 536 |
foreach ( $data as $key => $value ) { |
| 537 |
$data->$key = $this->replace_in_data( $value, $old_url, $new_url ); |
| 538 |
} |
| 539 |
} elseif ( is_string( $data ) ) { |
| 540 |
// Replace URLs in strings |
| 541 |
if ( $this->handle_image_sizes ) { |
| 542 |
$data = $this->replace_image_urls( $data, $old_url, $new_url ); |
| 543 |
} else { |
| 544 |
$data = str_replace( $old_url, $new_url, $data ); |
| 545 |
} |
| 546 |
} |
| 547 |
|
| 548 |
return $data; |
| 549 |
} |
| 550 |
|
| 551 |
/** |
| 552 |
* Check if a string is serialized |
| 553 |
* |
| 554 |
* @param string|mixed $data String to check. |
| 555 |
* |
| 556 |
* @return bool True if serialized |
| 557 |
*/ |
| 558 |
private function is_serialized( $data ) { |
| 559 |
// If it isn't a string, it isn't serialized |
| 560 |
if ( ! is_string( $data ) ) { |
| 561 |
return false; |
| 562 |
} |
| 563 |
|
| 564 |
$data = trim( $data ); |
| 565 |
if ( 'N;' === $data ) { |
| 566 |
return true; |
| 567 |
} |
| 568 |
|
| 569 |
if ( strlen( $data ) < 4 ) { |
| 570 |
return false; |
| 571 |
} |
| 572 |
|
| 573 |
if ( ':' !== $data[1] ) { |
| 574 |
return false; |
| 575 |
} |
| 576 |
|
| 577 |
$last_char = substr( $data, -1 ); |
| 578 |
if ( ';' !== $last_char && '}' !== $last_char ) { |
| 579 |
return false; |
| 580 |
} |
| 581 |
|
| 582 |
$token = $data[0]; |
| 583 |
switch ( $token ) { |
| 584 |
case 's': |
| 585 |
if ( '"' !== substr( $data, -2, 1 ) ) { |
| 586 |
return false; |
| 587 |
} |
| 588 |
// Fall through |
| 589 |
case 'a': |
| 590 |
case 'O': |
| 591 |
case 'i': |
| 592 |
case 'd': |
| 593 |
return (bool) preg_match( "/^{$token}:[0-9]+:/", $data ); |
| 594 |
default: |
| 595 |
return false; |
| 596 |
} |
| 597 |
} |
| 598 |
} |
| 599 |
|
| 600 |
/** |
| 601 |
* Example usage: |
| 602 |
* |
| 603 |
* $replacer = new Optml_Attachment_Db_Renamer(); |
| 604 |
* |
| 605 |
* // Replace all variations of the image |
| 606 |
* $count = $replacer->replace( |
| 607 |
* 'http://om-wp.test/wp-content/uploads/2025/03/image.jpg', |
| 608 |
* 'http://om-wp.test/wp-content/uploads/2025/03/new-name.jpg' |
| 609 |
* ); |
| 610 |
* |
| 611 |
* echo "Replaced $count instances"; |
| 612 |
*/ |
| 613 |
|