Make WordPress Core

Opened 12 years ago

Closed 6 years ago

Last modified 4 years ago

#20861 closed defect (bug) (wontfix)

switch_to_blog() breaks custom post type permalinks

Reported by: sickhippie's profile sickhippie Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Multisite Keywords: close
Focuses: Cc:

Description (last modified by SergeyBiryukov)

When using switch_to_blog() and custom post types, there are very strange results when calling the_permalink() or get_permalink(). For reference, we'll say the root site is blog1 and the second site containing the posts is blog2.

Using this code inside the loop:

switch_to_blog( $post->blog_id );
get_permalink();

If the post type is not registered on the current blog, the permalink given will be sitename.com/blog2/slug - when clicking it as href, it brings you the first instance of that slug in the database.

If the post type is registered on the current blog, the permalink given will be sitename.com/blog2/blog1/post-type/slug - this link will 404.

Related issue: #14992

Change History (19)

#1 @SergeyBiryukov
12 years ago

  • Description modified (diff)

#3 @wonderboymusic
12 years ago

  • Milestone Awaiting Review deleted
  • Resolution set to invalid
  • Status changed from new to closed

"sitename.com/blog2/blog1/post-type/slug" - in trunk this doesn't happen, I get this: http://wordpress-core/second/stuff/test-me/

#4 @SergeyBiryukov
12 years ago

  • Resolution changed from invalid to worksforme

#5 @jaroat
12 years ago

  • Cc jaroat added
  • Resolution worksforme deleted
  • Status changed from closed to reopened
  • Version 3.3.2 deleted

Sorry - I can clearly reproduce that behaviour in a current (directory-based) multisite-project:

As-Is State:

  • WP 3.4.2
  • Directory based Multisite installation
  • Custom Post Type "clipping" was registered in all blogs
  • Actual clipping-entries lie in blog_id 2
  • main blog address is "mydomain.tld/blog"
  • blog 2 address is "mydomain.tld/verband"

home-template of main-blog:

switch_to_blog(2);
$clipping_id = get_id_from_some_function();
$clipping_url = get_permalink($clipping_id);
restore_current_blog();

Problem:

$clipping_url / permalink returned is:

http:// mydomain.tld /verband /blog /slug-of-post-type /slug-of-clipping

I narrowed this down to the function get_extra_permastruct (wp-includes/rewrite.php) called in function get_post_permalink (wp-includes/link-template.php), which wrongly prepends "/blog" to all clipping slugs.

get_permalink does of course correctly work in templates of blog 2.

Wild assumption

Seems that get_extra_permastruct does not know that it has to act in the context of blog 2 after using switch_to_blog.

br from Salzburg,

  • Johannes
Last edited 12 years ago by jaroat (previous) (diff)

#6 @SergeyBiryukov
12 years ago

  • Milestone set to Awaiting Review

#7 @jaroat
12 years ago

Seems that get_permalink for custom post types depends on get_extra_permastruct in wp-includes/rewrite.php and acts wrongly if the target custom post types was registered with the "with_front" parameter (or without: with_front=1 is default).

get_extra_permastruct relies on it's internal var extra_permastructs which doesn't get updated to the new context when calling switch_to_blog.

So all permalinks fetched after switch_to_blog get constructed in the rewrite-context of the original blog calling the function - not the blog context the programmer switched to.

br from Salzburg,

  • Johannes
Last edited 12 years ago by jaroat (previous) (diff)

#8 @ryanduff
12 years ago

  • Cc ryan@… added

#9 @williamsba1
12 years ago

  • Cc brad@… added

#10 @toscho
11 years ago

  • Cc info@… added

#11 @mordauk
11 years ago

  • Cc pippin@… added

#12 @jeremyfelt
11 years ago

  • Keywords close added

I can reproduce on current trunk.

Also of note: If the post type is only registered on blog ID 2, and you ask for the permalink from blog ID 1, the default post structure is returned because blog ID 1 is not aware that clippings exists.

I can't imagine a fix for this being possible without every site in a network installation being able to generate the rewrite rules of every other site on demand. With all of the methods available around creating and modifying rewrite rules, this does not seem plausible.

I can start to imagine some creative ways of solving this on individual setups. I think that's more appropriate here.

Suggesting wontfix.

#13 @jeremyfelt
11 years ago

  • Milestone Awaiting Review deleted
  • Resolution set to wontfix
  • Status changed from reopened to closed

Closing as wontfix. No feedback after 8 months and this doesn't seem plausible.

#14 @danielpataki
6 years ago

  • Resolution wontfix deleted
  • Status changed from closed to reopened

We "fixed" this issue by looking at rewrite rules. This works for our purposes but I haven't done extensive research to see if this would break anywhere. Perhaps it can spark some interest though.

<?php
    switch_to_blog(3);
    $url = get_permalink(1234);
    $post_type = get_post_type(1234);
    $post_type_object = get_post_type_object($post_type);

    if(!empty($post_type_object->rewrite) && array_key_exists('slug', $post_type_object->rewrite) ) {
      $rewrite = get_option('rewrite_rules');
      $rule = array_search('index.php?post_type=' . $post_type, $rewrite);
      $base = str_replace('/?$', '', $rule);
      $url = str_replace('/' . $post_type_object->rewrite['slug'] . '/', '/' . $base . '/', $url);
    }

    $translations[$i]['url'] = $url;
    restore_current_blog();

This is not quite the exact code we use but it's a useful example. I'm super unsure about the quick and dirty str_replace bits at the end, it feels like spit and duct tape. I hope this is useful for someone, if anyone has a better idea, it would be great to get this one moving :)

#15 @ocean90
6 years ago

  • Resolution set to wontfix
  • Status changed from reopened to closed

#16 @SergeyBiryukov
5 years ago

#38305 was marked as a duplicate.

#17 @SergeyBiryukov
5 years ago

Might be related: #29039, #12002.

#18 @SergeyBiryukov
5 years ago

#49309 was marked as a duplicate.

#19 @mennoll
4 years ago

I was able to make a fix you can put in your themes functions.php file.
It works when your permalink structure is set to /%postname%/ .
I have not tested other permalink structures, but maybe this can still help you to find the right solution.

<?php
        /**
         * Fix multisite custom post type permalinks after switching blogs
         * @see https://core.trac.wordpress.org/ticket/20861
         *
         * @param string $post_link
         * @param WP_Post $post
         * @return string
         */
         function fix_multisite_cpt_permalinks( $post_link, $post ) {
                // Only run if switched to other site
                if ( ! isset( $GLOBALS['switched'] ) || ! $GLOBALS['switched'] || empty( $GLOBALS['_wp_switched_stack'] ) ) {
                        return $post_link;
                }

                // Only run if permalink structure is %postname%
                $current_permalink_structure = get_option( 'permalink_structure' );
                if ( $current_permalink_structure !== '/%postname%/' ) {
                        return $post_link;
                }

                // Get post type url prefix
                $url_prefix       = $post->post_type;
                $post_type_object = get_post_type_object( $post->post_type );
                if ( $post_type_object instanceof WP_Post_Type && ! empty( $post_type_object->rewrite ) && array_key_exists( 'slug', $post_type_object->rewrite ) ) {
                        $url_prefix = $post_type_object->rewrite['slug'];
                }

                // Generate post permalink
                $post_link = home_url( '/' . $url_prefix . '/' . $post->post_name . '/' );

                return $post_link;
        }
        add_filter( 'post_type_link', 'fix_multisite_cpt_permalinks', 10, 2 );
Note: See TracTickets for help on using tickets.