Skip to content

fix(template-switch): harden override_site() against context pollution and identity loss#1083

Merged
superdav42 merged 1 commit intomainfrom
fix/1082-template-switch-review
May 4, 2026
Merged

fix(template-switch): harden override_site() against context pollution and identity loss#1083
superdav42 merged 1 commit intomainfrom
fix/1082-template-switch-review

Conversation

@superdav42
Copy link
Copy Markdown
Collaborator

@superdav42 superdav42 commented May 4, 2026

Summary

Incorporates the template-switch fixes from #1082 by @kenedytorcatt (KursoPro) with review corrections applied on top.

Original contribution: @kenedytorcatt identified and fixed real production bugs where consecutive override_site() calls caused customer site corruption — $wpdb context pollution from MUCD_Data::copy_data(), admin_email loss, and stale Elementor Kit CSS. Their fix was validated with 18/18 passes across 6 templates in a production-mirror environment.

This PR preserves that work (their commit is included with full git authorship) and addresses issues found during review:

  1. Blog context preservation — replaced while(ms_is_switched()) full-stack unwind with captured $caller_blog_id + restore-on-exit, so the caller's switch_to_blog() context is preserved
  2. Identity restore guard — changed ! empty($opt_val) to false !== $opt_val so intentionally blank values (e.g. empty blogdescription) are not silently dropped
  3. Error path cleanup — added cleanup_override_context() to both return false paths; previously cache/context cleanup only ran on the happy path
  4. Removed duplicate Elementor methodforce_copy_elementor_kit() duplicated the existing backfill_kit_settings() pipeline added in 2.3.1; moved the one net-new feature (CSS regen via \Elementor\Core\Files\CSS\Post::update()) into backfill_kit_settings() instead
  5. Memory limit guardini_set('memory_limit', '512M') now checks wp_convert_hr_to_bytes() first to avoid lowering an existing higher limit
  6. Version tags — replaced @since 2.x.x placeholder with @since 2.4.0

Verification

  • PHP syntax: clean
  • PHPStan: 0 errors
  • PHPUnit Site_Duplicator_Test (16 tests): identical results before/after — no regressions
  • PHPUnit Site_Template_Switching_* (17 tests): identical results before/after — no regressions

Attribution

Original fix by @kenedytorcatt in #1082. Review refinements in this PR.

Supersedes #1082.


aidevops.sh v3.14.42 plugin for OpenCode v1.14.33 with claude-opus-4-6 spent 8h 12m and 23,197 tokens on this with the user in an interactive session.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved site duplication stability with enhanced context management and cache handling
    • Ensured site identity information (name, description, URLs) is properly retained during duplication
    • Enhanced Elementor CSS regeneration to complete successfully after site duplication operations

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Warning

Rate limit exceeded

@superdav42 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 18 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8463fc90-5a56-4dcc-811e-dbf4bdd8ee7c

📥 Commits

Reviewing files that changed from the base of the PR and between 622d88a and b09e48b.

📒 Files selected for processing (1)
  • inc/helpers/class-site-duplicator.php
📝 Walkthrough

Walkthrough

The PR enhances Site_Duplicator::override_site() to capture and preserve the caller's WordPress multisite context, unwind any lingering switched states, manage resource limits, snapshot and restore target-site identity options, and centralize post-duplication cleanup. Additionally, backfill_kit_settings() now regenerates Elementor Kit CSS after clearing compiled CSS metadata.

Changes

Site Duplication Context Management & Restoration

Layer / File(s) Summary
Context Setup & Initialization
inc/helpers/class-site-duplicator.php (lines 103–159)
Capture caller blog ID, unwind multisite switched state, flush caches, extend execution time and conditionally raise memory limit, define WP_IMPORTING, and snapshot target-site identity options (blogname, blogdescription, home, siteurl, admin_email).
Pre-Duplication Cache Cleanup
inc/helpers/class-site-duplicator.php (lines 169–178)
Pre-clean user cache for target customer before process_duplication() runs to ensure consistent admin/user operations.
Post-Duplication Restoration
inc/helpers/class-site-duplicator.php (lines 200–219)
Restore snapshotted identity options after duplication completes, preserving intentionally blank values using false !== $opt_val guard.
Cleanup Handler
inc/helpers/class-site-duplicator.php (lines 240–246, 255–288)
Call new cleanup_override_context() on duplication failure and after successful site save; new private helper centralizes state restoration, cache invalidation, and caller blog context reinstatement.
Elementor CSS Regeneration
inc/helpers/class-site-duplicator.php (lines 881–895)
backfill_kit_settings() regenerates Elementor Kit CSS post-clear via Elementor\Core\Files\CSS\Post::update(), suppressing non-fatal errors with try/catch.

Sequence Diagram

sequenceDiagram
    actor Caller
    participant Dup as Site_Duplicator
    participant WP as WordPress Multisite
    participant Cache as Cache System
    participant Elm as Elementor

    Caller->>Dup: override_site(to_site_id, from_site_id, customer)
    Dup->>Dup: Capture caller blog context
    Dup->>WP: restore_current_blog() if switched
    Dup->>Cache: Flush caches
    Dup->>WP: Extend execution time & memory
    Dup->>WP: Define WP_IMPORTING
    Dup->>Dup: Snapshot identity options (blogname, admin_email, etc.)
    Dup->>Cache: Pre-clean user cache for customer
    Dup->>Dup: process_duplication(from_site_id, to_site_id)
    Dup->>WP: update_blog_option() restore identity options
    Dup->>Dup: cleanup_override_context(to_site_id, customer, caller_blog_id)
    Dup->>WP: restore_current_blog() if needed
    Dup->>Cache: Flush & invalidate target blog cache
    Dup->>Cache: Clear user cache for customer
    Dup->>Elm: Call backfill_kit_settings()
    Elm->>Cache: Clear _elementor_css meta
    Elm->>Elm: Instantiate Elementor\Core\Files\CSS\Post
    Elm->>Elm: Call update() for Kit CSS regeneration
    Elm-->>Dup: Return (errors suppressed)
    Dup->>Caller: Return success/failure
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

origin:interactive


🐰 A duplicator's dance, so neat,
Context captured, cache complete,
Identity options saved and restored,
Elementor CSS freshly poured,
Site copies now run so sweet!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately and specifically describes the main change: hardening the override_site() method against context pollution and identity loss, which aligns with the core improvements documented in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1082-template-switch-review

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 18 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Rework the KursoPro template-switch fixes from PR #1082 to align with
the current codebase and fix issues flagged during review:

1. Replace while(ms_is_switched) full-stack unwind with captured
   caller blog ID + restore-on-exit via cleanup_override_context().
   Preserves the caller's switch_to_blog() context instead of
   unwinding to network root.

2. Change empty($opt_val) to false !== $opt_val on identity restore
   so intentionally blank values (e.g. empty blogdescription) are
   preserved instead of silently dropped.

3. Add cleanup_override_context() call to BOTH error return paths
   (process_duplication failure + save failure), not just the happy
   path. Prevents poisoned $wpdb/cache state from persisting after
   a failed override.

4. Remove duplicate force_copy_elementor_kit() public method — the
   existing backfill_kit_settings() + backfill_elementor_postmeta() +
   verify_kit_integrity() pipeline already handles Kit copying. Add
   active CSS regen via Elementor\Core\Files\CSS\Post::update() to
   backfill_kit_settings() instead.

5. Guard ini_set('memory_limit', '512M') against lowering an existing
   higher limit by checking wp_convert_hr_to_bytes() first.

6. Use proper @SInCE 2.4.0 version tags (replaces 2.x.x placeholder).

Ref #1082
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
inc/helpers/class-site-duplicator.php (1)

282-286: 💤 Low value

Consider adding a note about the intentionally unbalanced switch_to_blog().

The switch_to_blog($caller_blog_id) at line 285 intentionally lacks a matching restore_current_blog() to restore the caller's context. While the comment at lines 282-283 explains the intent, future maintainers might flag this as a bug. Consider adding a brief note that this is deliberate and that the caller is responsible for their own switch stack.

📝 Suggested clarification
-		// Restore the caller's blog context rather than leaving the
-		// request in the network root context.
+		// Restore the caller's blog context rather than leaving the
+		// request in the network root context. Intentionally unbalanced:
+		// the caller owns their switch_to_blog stack, we only restore
+		// the blog they were on when they called override_site().
 		if ( get_current_blog_id() !== $caller_blog_id ) {
 			switch_to_blog($caller_blog_id);
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@inc/helpers/class-site-duplicator.php` around lines 282 - 286, Update the
inline comment around the switch_to_blog($caller_blog_id) call to explicitly
document that the unbalanced call is intentional: state that we only switch
context back to the caller when get_current_blog_id() differs, that we
deliberately do not call restore_current_blog() here, and that the caller is
responsible for managing the switch/restore stack (i.e., calling
restore_current_blog() if required); reference the symbols switch_to_blog(),
get_current_blog_id(), $caller_blog_id and restore_current_blog() so future
maintainers understand the decision and responsibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@inc/helpers/class-site-duplicator.php`:
- Around line 282-286: Update the inline comment around the
switch_to_blog($caller_blog_id) call to explicitly document that the unbalanced
call is intentional: state that we only switch context back to the caller when
get_current_blog_id() differs, that we deliberately do not call
restore_current_blog() here, and that the caller is responsible for managing the
switch/restore stack (i.e., calling restore_current_blog() if required);
reference the symbols switch_to_blog(), get_current_blog_id(), $caller_blog_id
and restore_current_blog() so future maintainers understand the decision and
responsibility.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 616178f5-e37e-4b51-82eb-10235d10d53a

📥 Commits

Reviewing files that changed from the base of the PR and between 4664cf0 and 622d88a.

📒 Files selected for processing (1)
  • inc/helpers/class-site-duplicator.php

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

Performance Test Results

Performance test results for e0bc728 are in 🛎️!

Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown.

URL: /

Run DB Queries Memory Before Template Template WP Total LCP TTFB LCP - TTFB
0 41 37.78 MB 905.50 ms (+74.00 ms / +8% ) 152.50 ms (-8.50 ms / -6% ) 1090.00 ms 2118.00 ms (+114.00 ms / +5% ) 2033.00 ms (+110.80 ms / +5% ) 90.70 ms
1 56 49.10 MB 965.00 ms 150.50 ms 1117.50 ms 2126.00 ms (+54.00 ms / +3% ) 2042.35 ms (+52.45 ms / +3% ) 82.65 ms

@superdav42 superdav42 merged commit c1e0cd9 into main May 4, 2026
11 checks passed
@superdav42 superdav42 deleted the fix/1082-template-switch-review branch May 6, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant