WordPress.org

Make WordPress Core

Opened 3 years ago

Last modified 10 days ago

#25293 new defect (bug)

Switch_to_blog not switching the siteid

Reported by: Rahe Owned by:
Milestone: Future Release Priority: normal
Severity: minor Version: 3.0
Component: Networks and Sites Keywords:
Focuses: multisite Cc:

Description

When having multiple network on multisite making the following:

switch_to_blog(1);

$options = get_site_option( 'my_option' );

restore_current_blog();

The options retrieved are the options of the current siteid and not the siteid of the switched blog.
One of the options is to make something like this :

global $wpdb;
// Get the previous siteid
$previous_site_id = $wpdb->siteid;
$previous_blog_id = $wpdb->blogid;
		
// Go to site 1
switch_to_blog(1);
		
// Set the blog siteid to 1
$wpdb->set_blog_id( 1, 1 );

// Get the options
$options = get_site_option( 'my_option' );
		
restore_current_blog();
$wpdb->set_blog_id( $previous_blog_id , $previous_site_id );

Or

// Get the previous siteid
$site_id = $wpdb->siteid;
		
// Set the blog siteid to 1
$wpdb->set_blog_id( $wpdb->blogid, 1 );
	
// Get the options
$options = get_site_option( 'my_options' );
	
$wpdb->set_blog_id( $wpdb->blogid , $site_id );

The thing is that the switch_to_blog function does not specify the switched siteid on the method $wpdb->set_blog_id if the network is not the same as the current blog.

Change History (9)

#1 @jeremyfelt
3 years ago

  • Version changed from 3.6.1 to 3.0

One of the great things about switch_to_blog() is that it requires no database operation to succeed. It is passed a $blog_id (which should be named $site_id), which it sets to the $_GLOBALS['blog_id'], it performs $wpdb->set_blog_id() to get the correct table prefixes setup for when queries are made, and it loads in the user's capabilities for the new $blog_id.

I don't think we want to introduce a database call to find the site's network ID from the $blog_id that was passed. It would be interesting to have switch_to_blog() accept another parameter for $network_id so that the caller could explicitly determine what site and network should be available. The deprecated parameter that is there now was $validate until [13125] it looks like.

More refreshing would be a new switch_to_site( $site_id, $network_id ) function that wraps switch_to_blog() and a switch_to_network( $network_id ) function to try and limit some of the confusion at the code level. Even then, it may be that passing $network_id is a decision left to the developer as the one who knows whether a network level lookup will be needed.

#2 @jeremyfelt
3 years ago

  • Milestone changed from Awaiting Review to Future Release

#3 @jeremyfelt
3 years ago

  • Component changed from Multisite to Networks and Sites
  • Focuses multisite added

This ticket was mentioned in Slack in #core-multisite by jeremyfelt. View the logs.


15 months ago

#5 @GregLone
12 months ago

In 10 days, we'll celebrate this ticket 2y anniversary >_>

This ticket was mentioned in Slack in #core-multisite by jeremyfelt. View the logs.


11 months ago

#7 @Rahe
10 days ago

With the new 4.6 out, is this ticket still relevant ? Or is the bug still here ?

#8 @flixos90
10 days ago

@Rahe:
The ticket is still relevant.
Actually I'm just seeing this ticket right now. It would be great to get this implemented somehow as it would also give a ticket like #37414 more value.

@jeremyfelt:
For switch_to_network() (and the related restore function), we could, for the most part, take the function from the WP Multi Network plugin. What I like about that implementation is that the entire network object is stored in the switched stack global.

For the site switching, I really like your idea of introducing a switch_to_site() for it. Maybe the second parameter could be a bool $switch_network instead of the network ID so that the function detects the network to switch to automatically. I don't think there's a use-case for switching the site and network simultaneously in a way that they don't belong together. switch_to_network() could have a similar second parameter $switch_site.

To prevent database queries on every switch, maybe both functions should accept _either_ an ID or an object. The site object has the network ID already in place (so does the network object have the site ID), so no additional query necessary. If the user passes an ID, we call get_site( $id ) (or get_network( $id ) respectively) which is not too unlikely to get a cache hit under these circumstances. In the related restore_current_*() functions, we could prevent queries in a similar way, by storing the entire objects in the switched stack globals. Maybe we should make what we store there dependent on whether the user provided $switch_network = true. If switching the network too, we store the object, if only the site, we store the ID. Then in the restore_current_*() function we can detect whether that last switch only switched the site or also the network. This way we wouldn't have DB queries there either, and we wouldn't even need a conditional parameter whether to switch the network as well since that would be automatically detected.

If we hold on to switch_to_blog() to use it internally (in switch_to_site()), we would need to adjust that to also accept an object and store that in stack. Or we create switch_to_site() from scratch with a global _wp_switched_sites_stack to not mess with switch_to_blog(), considering it deprecated at some point.

#9 @flixos90
10 days ago

For the above concept, it could look something like this:

function switch_to_site( $site, $switch_network = false ) {
	if ( $switch_network ) {
		if ( ! is_object( $site ) {
			$site = get_site( $site );
		}
		$site_id = $site->blog_id;
		$GLOBALS['_wp_switched_sites_stack'][] = $GLOBALS['current_blog'];
	} else {
		if ( is_object( $site ) {
			$site_id = $site->blog_id;
			$site = $site_id;
		} else {
			$site_id = $site;
		}
		$GLOBALS['_wp_switched_sites_stack'][] = $GLOBALS['current_blog']->blog_id;
	}

	// do rest of the regular logic with $site_id
	
	if ( $switch_network ) {
		switch_to_network( $site->site_id );
	}
}

function restore_current_site() {
	if ( empty( $GLOBALS['_wp_switched_sites_stack'] ) ) {
		return false;
	}

	$restore_network = false;

	$site = array_pop( $GLOBALS['_wp_switched_sites_stack'] );
	if ( is_object( $site ) ) {
		$site_id = $site->blog_id;
		$restore_network = true;
	} else {
		$site_id = $site;
	}

	// do rest of the regular logic with $site_id
	
	if ( $restore_network ) {
		restore_current_network();
	}
}

function switch_to_network( $network, $switch_site = false ) {
	if ( $switch_site ) {
		if ( ! is_object( $network ) {
			$network = get_network( $network );
		}
		$network_id = $network->id;
		$GLOBALS['_wp_switched_networks_stack'][] = $GLOBALS['current_site'];
	} else {
		if ( is_object( $network ) {
			$network_id = $network->id;
			$network = $network_id;
		} else {
			$network_id = $network;
		}
		$GLOBALS['_wp_switched_networks_stack'][] = $GLOBALS['current_site']->id;
	}

	// do rest of the regular logic with $network_id
	
	if ( $switch_site ) {
		switch_to_site( $network->blog_id );
	}
}

function restore_current_network() {
	if ( empty( $GLOBALS['_wp_switched_networks_stack'] ) ) {
		return false;
	}

	$restore_site = false;

	$network = array_pop( $GLOBALS['_wp_switched_networks_stack'] );
	if ( is_object( $network ) ) {
		$network_id = $network->id;
		$restore_site = true;
	} else {
		$network_id = $network;
	}

	// do rest of the regular logic with $network_id
	
	if ( $restore_site ) {
		restore_current_site();
	}
}
Last edited 10 days ago by flixos90 (previous) (diff)
Note: See TracTickets for help on using tickets.