3

I have created a parent > child > grandchild structure. (Courses > Series > Episodes).

I have created the below code which updates the urls for each of the custom post types so that I can have the url structure

http://mywebsite.co.uk/system/%course%/%series%/%episode%.

For example, a course called 'crash course', with a series of 'the basics', with an episode of 'checking your mirrors' would look like this

http://mywebsite.co.uk/system/crash-course/the-basics/checking-your-mirrors

Everything is working perfectly fine, but i've noticed a very weird bug. Every other time I save an episode, the permalink changes itself to try and match a random series?

For example, if I save the episode once, my permalink is

http://mywebsite.co.uk/system/crash-course/the-basics/checking-your-mirrors.

Then as soon as I go back into the episode, make any changes and try to save it again, once i've saved and the page refreshes, it changes itself to

http://mywebsite.co.uk/system/crash-course/the-basics/the-basics-2, and I have to click edit and manually change it back.

My full custom post type registration code is below, please let me know if anyone has any ideas! Its not ideal to be changing the permalink back manually every other time I make a change!

Note - My apologies for the huge code snippet below, i thought it would be best if I pasted ALL of my custom post type code in to help debug!

<?php
/* ----------------------------------------------------------------------------------------------------------
        Register our custom post types
---------------------------------------------------------------------------------------------------------- */
function create_post_type() {
    //Courses
    register_post_type( 'course',
      [
        'labels' => array(
          'name' => __( 'Courses' ),
          'singular_name' => __( 'Course' )
        ),
        'description' => 'All courses',
        'public' => true,
        'hierarchical' => true,
        'rewrite' => array(
            'slug'       => 'system',
        ),
        'menu_icon' => 'dashicons-welcome-learn-more',
        'supports' => ['title', 'custom_fields', 'page-attributes']
      ]
    );

    //Series
    register_post_type( 'series',
      [
        'labels' => array(
          'name' => __( 'Series' ),
          'singular_name' => __( 'Series' )
        ),
        'description' => 'Course Series',
        'public' => true,
        'hierarchical' => false,
        'show_ui'              => true,
        'show_in_menu'         => 'edit.php?post_type=course',
        'supports' => ['title', 'custom_fields', 'page-attributes']
      ]
    );

    //Episodes
    register_post_type( 'episodes',
      [
        'labels' => array(
          'name' => __( 'Episodes' ),
          'singular_name' => __( 'Episode' )
        ),
        'description' => 'Series Episodes',
        'public' => true,
        'hierarchical' => false,
        'show_ui'              => true,
        'show_in_menu'         => 'edit.php?post_type=course',
        'supports' => ['title', 'custom_fields', 'page-attributes']
      ]
    );
}
add_action( 'init', 'create_post_type' );



/* ----------------------------------------------------------------------------------------------------------
        Add our meta boxes to our series and episode add/edit page
---------------------------------------------------------------------------------------------------------- */
function my_add_meta_boxes() {
  //Series Meta Boxes
  add_meta_box( 'series-parent', 'Course', 'series_attributes_meta_box', 'series', 'side', 'high' );

  //Episode Meta Boxes
  add_meta_box( 'episode-parent', 'Series', 'episode_attributes_meta_box', 'episodes', 'side', 'high' );
}
add_action( 'add_meta_boxes', 'my_add_meta_boxes' );

function series_attributes_meta_box($post) {
  $post_type_object = get_post_type_object($post->post_type);
    $pages = wp_dropdown_pages([
    'post_type' => 'course',
    'selected' => $post->post_parent,
    'name' => 'parent_id',
    'show_option_none' => __( '(no parent)' ),
    'sort_column'=> 'menu_order, post_title',
    'echo' => 0
  ]);
  if (!empty($pages)){
        echo $pages;
    }
}

function episode_attributes_meta_box( $post ) {
  $post_type_object = get_post_type_object( $post->post_type );
  $select = "<select name='parent_id' id='parent_id'>";
    $select .= "<option value=''>(No Parent)</option>";
    //Get Series
    $args = [
      'post_type' => 'series',
      'posts_per_page' => -1,
      'meta_key' => 'series_number',
      'orderby' => 'meta_value_num',
      'order' => 'ASC'
    ];
    $seriesQuery = new WP_Query($args);
    //Loop the series
    while($seriesQuery->have_posts()){
      $seriesQuery->the_post();
      $select .= "<option " . (get_the_ID() == $post->post_parent ? 'selected' : '') . " class='level-0' value='" . get_the_ID() . "'>" . get_the_title(wp_get_post_parent_id(get_the_ID())) . ' - ' . get_the_title() . "</option>";
    }
    wp_reset_postdata();
  $select .= "</select>";
  echo $select;
}


/* ----------------------------------------------------------------------------------------------------------
        String replace on the permalinks before we save them
---------------------------------------------------------------------------------------------------------- */
function custom_permalinks($permalink, $post, $leavename) {

  $post_id = $post->ID;
  $parent = $post->post_parent;
  $parent_post = get_post( $parent );

  //Series or Episodes
  switch($post->post_type){
    case 'episodes':
        if(empty($permalink) || in_array($post->post_status, array('draft', 'pending', 'auto-draft'))){
          return $permalink;
        } else {
          $grandparent_post = get_post( $parent_post->post_parent );
          $search = ['%course%', '%series%'];
          $replace = [$grandparent_post->post_name, $parent_post->post_name];
          $permalink = str_replace($search, $replace, $permalink);
          return $permalink;
        }
    break;
    case 'series':
        if(empty($permalink) || in_array($post->post_status, array('draft', 'pending', 'auto-draft'))){
          return $permalink;
        } else {
          $search = ['%course%'];
          $replace = [$parent_post->post_name];
          $permalink = str_replace($search, $replace, $permalink);
          return $permalink;
        }
    break;
    default:
        return $permalink;
    break;
  }

}
add_filter('post_type_link', 'custom_permalinks', 10, 3);

function my_add_rewrite_rules() {

  //Episodes
  add_permastruct('episodes', '/system/%course%/%series%/%episodes%', false,  ['walk_dirs' => false]);
  add_rewrite_tag('%episodes%', '([^/]+)', 'episodes=');
  add_rewrite_rule('^system/([^/]+)/([^/]+)/([^/]+)?','index.php?episodes=$matches[3]','top');

  //Series
  add_permastruct('series', '/system/%course%/%series%', false,  ['walk_dirs' => false]);
    add_rewrite_tag('%series%', '([^/]+)', 'series=');
  add_rewrite_rule('^system/([^/]+)/([^/]+)/?','index.php?series=$matches[2]','top');


}
add_action( 'init', 'my_add_rewrite_rules' );

?>
  • Does it happen every time you edit an episode? – Krzysiek Dróżdż Apr 25 at 4:41
  • Every other time – S_R Apr 25 at 7:55
2

BETTER SOLUTION

Actually, there is a note on the WP_Query reference:

Note: Ticket #18408 For querying posts in the admin, consider using get_posts() as wp_reset_postdata() might not behave as expected.

So instead of attempting to reset the global post data, the more proper solution is either:

  1. Use get_posts():

    $seriesPosts = get_posts($args);
    foreach($seriesPosts as $p){
      $select .= "<option " . ($p->ID == $post->post_parent ? 'selected' : '') . " class='level-0' value='" . $p->ID . "'>" . get_the_title(wp_get_post_parent_id($p->ID)) . ' - ' . $p->post_title . "</option>";
    }
    
  2. Or loop through the $seriesQuery->posts:

    $seriesQuery = new WP_Query($args);
    foreach($seriesQuery->posts as $p){
      $select .= "<option " . ($p->ID == $post->post_parent ? 'selected' : '') . " class='level-0' value='" . $p->ID . "'>" . get_the_title(wp_get_post_parent_id($p->ID)) . ' - ' . $p->post_title . "</option>";
    }
    

And do not call setup_postdata() in the loop.


ORIGINAL ANSWER

The problem is that the wp_reset_postdata() fails to reset the global $post object, because the post property of the global $wp_query object is a null (i.e. $GLOBALS['wp_query']->post is empty):

function episode_attributes_meta_box( $post ) {
    ...
    wp_reset_postdata(); // <- fails to reset $GLOBALS['post']
    ...
}

So an easy fix is, instead of using wp_reset_postdata(), you can copy/backup the $post object passed to your meta box callback and then set the global $post to that copy at the end of the function (or wherever you think necessary to reset the global post data):

function episode_attributes_meta_box( $post ) {
    $_post = $post; // copy it to $_post

    $post_type_object = get_post_type_object( $post->post_type );
    $select = "<select name='parent_id' id='parent_id'>";
    ... your code ...
    //wp_reset_postdata();
    $select .= "</select>";
    echo $select;

    // Here resets the global post data.
    $GLOBALS['post'] = $_post;
    unset( $_post );
}

There might be other options, but the above trick worked well for me. :)

  • Fantastic! This fixed it perfectly, a great solution! Can I ask, to help me in the future, what was your process to figure this out? :) – S_R Apr 25 at 15:09
  • 1
    Now that's a tough question :p.. but this may help: reveal/enable the "Slug" box on the post edit screen (click the "Screen Options" button at the top and check the "Slug" option). If you see the slug is not the one you manually set, then you can assume WordPress isn't using the right $post object - because that slug is coming right from $post->post_name although it could be filtered by plugins or your/custom code. – Sally CJ Apr 25 at 15:23
  • Answer revised - and hopefully it's more helpful. And +1 for the question. :) – Sally CJ Apr 27 at 1:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.