Skip to content

Commit e067cf7

Browse files
committed
fix: address review bot feedback from PR #825 (GH#834)
- Replace in-place mutation of $args->from_site_id with a separate $template_site_id variable so copy_users() and any other downstream consumers retain a consistent source reference after copy_data() has already run with the original site ID. - Add rewrite_backfilled_postmeta_urls() that applies the same source→target URL replacement MUCD's db_update_data() does, but scoped to the postmeta table. backfill_postmeta() inserts rows after MUCD's rewrite pass; without this step, _menu_item_url custom links and _elementor_* JSON would retain template-site domains. - Tighten test_wu_duplicate_site_action_includes_from_site_id: fail explicitly with the WP_Error message instead of silently skipping all assertions when duplicate_site() returns an error. Resolves #834
1 parent 002b9f9 commit e067cf7

1 file changed

Lines changed: 96 additions & 6 deletions

File tree

inc/helpers/class-site-duplicator.php

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,20 @@ protected static function process_duplication($args) {
261261
* customer's real choice in the wu_template_id site meta key.
262262
* Prefer that over the explicit param when available.
263263
*
264+
* Intentionally kept in a separate variable: copy_data() and
265+
* copy_files() have already run with $args->from_site_id. Mutating
266+
* that property would cause copy_users() and downstream callers to
267+
* reference a different source than the one whose data was copied,
268+
* creating an inconsistent clone. Use $template_site_id only for the
269+
* post-copy backfill, integrity check, and action payload.
270+
*
264271
* @since 2.3.1
265272
* @see https://github.com/Ultimate-Multisite/ultimate-multisite/issues/820
266273
*/
267-
$meta_template = (int) get_site_meta($args->to_site_id, 'wu_template_id', true);
268-
if ($meta_template > 0 && $meta_template !== (int) $args->from_site_id) {
269-
$args->from_site_id = $meta_template;
274+
$template_site_id = (int) $args->from_site_id;
275+
$meta_template = (int) get_site_meta($args->to_site_id, 'wu_template_id', true);
276+
if (0 < $meta_template && $meta_template !== (int) $args->from_site_id) {
277+
$template_site_id = $meta_template;
270278
}
271279

272280
/*
@@ -281,7 +289,19 @@ protected static function process_duplication($args) {
281289
* @since 2.3.1
282290
* @see https://github.com/Ultimate-Multisite/ultimate-multisite/issues/820
283291
*/
284-
self::backfill_postmeta($args->from_site_id, $args->to_site_id);
292+
self::backfill_postmeta($template_site_id, $args->to_site_id);
293+
294+
/*
295+
* Rewrite source URLs to target URLs in backfilled postmeta rows.
296+
*
297+
* backfill_postmeta() inserts rows after MUCD_Data::copy_data() has
298+
* already run its source→target URL replacement pass, so those rows
299+
* contain raw template URLs. Apply the same replacement here.
300+
*
301+
* @since 2.3.2
302+
* @see https://github.com/Ultimate-Multisite/ultimate-multisite/issues/834
303+
*/
304+
self::rewrite_backfilled_postmeta_urls($template_site_id, $args->to_site_id);
285305

286306
/*
287307
* Verify Kit integrity after backfill.
@@ -293,7 +313,7 @@ protected static function process_duplication($args) {
293313
* @since 2.3.1
294314
* @see https://github.com/Ultimate-Multisite/ultimate-multisite/issues/820
295315
*/
296-
self::verify_kit_integrity($args->from_site_id, $args->to_site_id);
316+
self::verify_kit_integrity($template_site_id, $args->to_site_id);
297317

298318
if ($args->keep_users) {
299319
\MUCD_Duplicate::copy_users($args->from_site_id, $args->to_site_id);
@@ -316,7 +336,7 @@ protected static function process_duplication($args) {
316336
do_action(
317337
'wu_duplicate_site',
318338
[
319-
'from_site_id' => $args->from_site_id,
339+
'from_site_id' => $template_site_id,
320340
'site_id' => $args->to_site_id,
321341
]
322342
);
@@ -384,6 +404,76 @@ protected static function backfill_postmeta($from_site_id, $to_site_id) {
384404
self::backfill_kit_settings($from_site_id, $to_site_id);
385405
}
386406

407+
/**
408+
* Rewrite source-site URLs to target-site URLs in backfilled postmeta rows.
409+
*
410+
* backfill_postmeta() inserts rows after MUCD_Data::copy_data() has already
411+
* run its source→target URL replacement pass (db_update_data()), so those
412+
* rows contain raw template-site URLs. This method applies the same URL
413+
* substitution to the target's postmeta table, correcting any template
414+
* references left by the backfill (e.g. _menu_item_url custom links,
415+
* _elementor_* JSON containing the template domain).
416+
*
417+
* Safe to run after MUCD has already rewritten the copied rows: those rows
418+
* no longer contain the source URL, so REPLACE() is a no-op for them.
419+
*
420+
* @since 2.3.2
421+
* @see https://github.com/Ultimate-Multisite/ultimate-multisite/issues/834
422+
*
423+
* @param int $from_site_id Source (template) blog ID.
424+
* @param int $to_site_id Target (cloned) blog ID.
425+
*/
426+
protected static function rewrite_backfilled_postmeta_urls($from_site_id, $to_site_id) {
427+
428+
global $wpdb;
429+
430+
$from_site_id = (int) $from_site_id;
431+
$to_site_id = (int) $to_site_id;
432+
433+
if ( ! $from_site_id || ! $to_site_id || $from_site_id === $to_site_id) {
434+
return;
435+
}
436+
437+
$from_blog_url = get_blog_option($from_site_id, 'siteurl');
438+
$to_blog_url = get_blog_option($to_site_id, 'siteurl');
439+
440+
$from_clean = wu_replace_scheme((string) $from_blog_url);
441+
$to_clean = wu_replace_scheme((string) $to_blog_url);
442+
443+
if ($from_clean === $to_clean) {
444+
return;
445+
}
446+
447+
$to_prefix = $wpdb->get_blog_prefix($to_site_id);
448+
449+
/*
450+
* Mirror MUCD's two-pass approach: plain URL replacement and a
451+
* JSON-escaped variant (forward slashes encoded as \/).
452+
*/
453+
$replacements = [
454+
$from_clean => $to_clean,
455+
str_replace('/', '\\/', $from_clean) => str_replace('/', '\\/', $to_clean),
456+
];
457+
458+
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
459+
foreach ($replacements as $from => $to) {
460+
461+
if ($from === $to) {
462+
continue;
463+
}
464+
465+
$wpdb->query(
466+
$wpdb->prepare(
467+
"UPDATE {$to_prefix}postmeta SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_value LIKE %s",
468+
$from,
469+
$to,
470+
'%' . $wpdb->esc_like($from) . '%'
471+
)
472+
);
473+
}
474+
// phpcs:enable
475+
}
476+
387477
/**
388478
* Backfill nav_menu_item postmeta from template to cloned site.
389479
*

0 commit comments

Comments
 (0)