Nested CPT URLs + Posts 2 Posts

I’m working on a client site that has two different custom post types: artist and portfolio. I’m using the posts-to-posts plugin to create a relationship from each artist to their portfolios.

Artist URL’s are setup as {siteUrl}/artist, while portfolio URL’s are setup to receive the artist name: {siteUrl}/artist/%artistname%

Read More

I wrote the following to check if the portfolio is connected to an artist, and if so, change the URL of that portfolio to {siteUrl}/artist/artist-name/portfolio-name. If it doesn’t have an artist connected, it directs changes the URL to {siteUrl}/portfolio/portfolio-name.

function filter_portfolio_link( $post_link, $post ) {
  if ( $post->post_type === 'portfolio' ) {
    $connected = new WP_query( array(
      'post_type' => 'artist',
      'connected_type' => 'portfolios_to_artists',
      'connected_items' => $post,
      'nopaging' => true,
    ) );
    if ($connected->have_posts() ) {
      foreach ( $connected as $connectedPost ) {
        setup_postdata($connectedPost);
        if ($connectedPost->post_type === 'artist') {
          $artistName = $connectedPost->post_name;
          $first = false;
        }
      }
      $post_link = str_replace( '%artist_name%', $artistName, $post_link );
    }
    else {
      $post_link = str_replace( 'artist/%artist_name%', 'portfolio', $post_link);
    }
  }
  return $post_link;
}
add_filter( 'post_type_link', 'filter_portfolio_link', 10, 2);

This pulls the correct data and inserts it in the URL correctly, but with lots of PHP notices. I’m not so worried about those at the moment.

So while this changes the slug correctly, it isn’t changing the permalink and I’m getting 404 errors on the front-end. I imagine this needs a rewrite function to be paired with it? Just not sure where to go from here.

Related posts

Leave a Reply

1 comment

  1. So after a lot of time spent and a lot of trying different things, I finally figured out the right way to do this. I used MonkeyMan Rewrite Analyzer and the WP Debug Bar to help figure out how to construct the rewrite.

    So first, my custom post type URLs are rewritten as such:

    // Artist Rewrite Args
    $rewrite = array(
      'slug'                => 'artist',
      'with_front'          => true,
      'pages'               => true,
      'feeds'               => true,
    );
    
    // Portfolio Rewrite Args
    $rewrite = array(
      'slug'                => 'portfolio',
      'with_front'          => false,
      'pages'               => true,
      'feeds'               => true,
    );
    

    Then, I register a rewrite tag %artistname% for the artist name, which we’ll grab via a WP_Query function, as well as a rewrite rule to fill in the slug to be occupied by the artist’s name, which will come before the slug of the portfolio being shown.

    // Portfolio Rewrite Rule
    add_action( 'init', 'portfolio_rewrite_tag' );
    function portfolio_rewrite_tag() {
    
        add_rewrite_tag('%artistname%','[^/]+');
        // defines the rewrite structure for 'portfolios'
        // says that if the portfolio URL matches this rule, then it should display the 'artists' post whose post name matches the last slug set
        add_rewrite_rule( '^artist/[^/]+/([^/]+)/?$','index.php?portfolio=$matches[1]','top' );   
    }
    

    Next, I refined the URL filter to check if it’s a portfolio post, and if so, pull the slug of the first connected artist, and then plop that slug into the post link string.

    // Grab connected artist name and swap it into the URL
    function filter_portfolio_link( $post_link, $post ) {
      if ( $post->post_type === 'portfolio' ) {
        $connected = new WP_query( array(
          'post_type' => 'artist',
          'connected_type' => 'portfolios_to_artists',
          'connected_items' => $post,
          'nopaging' => true,
        ) );
        if ($connected->have_posts() ) {
          $first = true;
          foreach ( $connected as $connectedPost ) {
              setup_postdata($connectedPost);
              if ($connectedPost->post_type === 'artist') {
                if ($first) {
                  $artistName = $connectedPost->post_name;
                  $first = false;
                }
              }
          }
          $post_link = str_replace( 'portfolio', 'artist/' . $artistName, $post_link );
        }
      }
      return $post_link;
    }
    add_filter( 'post_type_link', 'filter_portfolio_link', 10, 2);
    

    So that’s it! While I didn’t use the same plugin as discussed in the thread Nested custom post types with permalinks, the whole thread was super helpful in arriving at this conclusion.