Skip to content

Fix deletion of wrong assets in multisite setup#1162

Merged
gabrielcld2 merged 3 commits intodevelopfrom
feature/parent-multisite-check
Apr 24, 2026
Merged

Fix deletion of wrong assets in multisite setup#1162
gabrielcld2 merged 3 commits intodevelopfrom
feature/parent-multisite-check

Conversation

@gabrielcld2
Copy link
Copy Markdown
Collaborator

@gabrielcld2 gabrielcld2 commented Apr 20, 2026

Fixes #1138

Context

This bug is related to non-media assets such as Theme files, plugin files, WP core files, and upload-dir content.

The issue

In a WordPress Multisite, PHP can switch blog contexts within the same process via switch_to_blog() — this happens during cron, AJAX handlers, WooCommerce, WPML, and other plugins.
If the switch happen during the assets purging process, current Cloudinary plugin might delete assets from the wrong site.

The scenario

  1. The singleton Assets instance loads with Site B's parent posts in $this->asset_parents (loaded during init_asset_parents()), now stamped with blog_id = 2.
  2. Something (cron, another plugin) switches the context back to Site A (blog_id = 1).
  3. activate_parents() runs in Site A's context: get_current_blog_id() returns 1.
  4. Site B's parents are NOT in Site A's $this->active_parents (Site A has different configured paths).
    1. Without the fix: Site B's parents — and all their child assets — get purged. Asset posts deleted, Cloudinary sync records cleared.
    2. With the fix: The $parent->blog_id !== $blog_id guard skips them.

What the Fix Does

  1. add_asset_parent() method stamps each parent post object with blog_id = get_current_blog_id() when running on a multisite, at the moment of loading/creation. This happens in both init_asset_parents() and
    create_asset_parent().
  2. Guard in activate_parents() — before calling purge_parent() + wp_delete_post() on an inactive parent, it now checks: if $parent->blog_id is set and doesn't match the current blog ID, skip it.

QA Notes

Prerequisites

  • WordPress Multisite with at least 2 active subsites (Site A and Site B).
  • Cloudinary plugin network-activated (or manually activated on both sites).
  • Cloudinary credentials connected and configured on both sites (same or separate accounts).
  • WP-CLI & SQL client access for reliable reproduction and testing

Setup Phase

On Site B:

  1. Go to Cloudinary → Connect → scroll to Additional Asset Sync Settings.
  2. Enable asset syncing and toggle ON at least one asset group — e.g., Themes or Plugins.
  3. Save settings.
  4. Visit a Site B frontend page (any page) to trigger asset discovery.
  5. Return to Site B's Cloudinary admin — this triggers activate_parents() and creates the parent posts.

Record the baseline (DB check)

(Note: Replace wp_3_ with your Site B's table prefix if different):

SELECT ID, post_title, post_type, post_status                                                                                                                                                                                     
  FROM wp_3_posts                                                                                                                                                                                                                   
  WHERE post_type = 'cloudinary_asset' AND post_parent = 0;

Note the IDs and count of Site B's asset parents.

Reproducing the issue

Reproducing the Cross-Blog Scenario (Reliable Method via WP-CLI)

Run the following the command via WP CLI (make sure to replace the URL param to point to your site A, and site B's ID in the switch_to_blog call within the script if needed):

wp --url=cloudinary-multisite.local/site-1/ eval '
$plugin = Cloudinary\get_plugin_instance();
$assets = $plugin->get_component( "assets" );
switch_to_blog( 3 );

$r = new ReflectionClass( $assets );
$m = $r->getMethod( "init_asset_parents" );
$m->setAccessible( true );
$m->invoke( $assets );

restore_current_blog();

$m2 = $r->getMethod( "activate_parents" );
$m2->setAccessible( true );
$m2->invoke( $assets );'

This script basically emulates what a cron or plugin might do which is to trigger the assets parents purge across both sites while switching to another blog.

Verify

After running, re-check the DB:
SELECT COUNT(*) FROM wp_3_posts WHERE post_type = 'cloudinary_asset' AND post_parent = 0;

Expected with the fix: Count is unchanged. Site B's parents survive and didn't get deleted.

Testing Normal Purge Still Works (Regression)

To ensure the fix doesn't break the legitimate purge behavior:

  1. On Site A, enable Plugins asset sync and save → verify plugin parents are created.
  2. On Site A, disable Plugins asset sync and save → verify Site A's plugin parents and their children are deleted.
  3. Site B's assets should still be unaffected throughout.

Copy link
Copy Markdown
Contributor

@PatelUtkarsh PatelUtkarsh left a comment

Choose a reason for hiding this comment

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

Looks good!

Comment thread php/class-assets.php
*/
protected function add_asset_parent( $post ) {
if ( is_multisite() ) {
$post->blog_id = get_current_blog_id();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 thought: Stamping blog_id as a dynamic property on WP_Post only works because WP core annotated WP_Post with #[AllowDynamicProperties] (WP 6.4+). On sites running PHP 8.2+ with older WordPress, this triggers deprecation warnings. Which is odd combo anyway.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point! It's probably fine if it's just a deprecation warning given the odd combination of WP & PHP version like you said, it's quite unlikely to happen.

@gabrielcld2 gabrielcld2 merged commit ad71ccb into develop Apr 24, 2026
4 checks passed
This was referenced Apr 27, 2026
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.

3 participants