0

I am building a proprietary LMS(learning management system), and am attempting to add a simple bookmarking function that saves the $post->ID for the last page visited within my "course" CPT(custom post type). It seems like this should be simply a matter of saving the post id for the current page as a custom meta data value for the user, but without fail the update_user_meta() function saves the the post id for a post that is at the same level in the hierarchy that has been created for this "course" CPT.

Here's the code:

function nb_course_post_actions(){

    global $current_user, $post;

    echo "I'm about to give up! Post type is: {$post->post_type}.";
    if( $post->post_type === 'course' ){
        $sid = $current_user->ID;
        $bookmark_id = $post->ID;
        print( $bookmark_id );

        if(  update_user_meta( $sid, 'course_bookmarks', $bookmark_id ) === FALSE ){
            echo "Failed to update metadata";

        } else {
            echo "Metadata was updated: ";
            if ( get_user_meta($sid, 'course_bookmarks', true ) !== $bookmark_id )
                wp_die('An error occurred');
        }

    }
}

add_action( 'wp_footer', 'nb_course_post_actions' );

So here's what is suppose to happen: the logged-in user visits a page within the course CPT, on each new page load the additional action of recording the current post id is added to the user's meta data, so that when a user leave and comes back at a later date in time, they can click on a link that takes them to the page they were last on within the "course" CPT.

The problem is that update_user_meta() inevitably records the wrong post_id and the function goes to the wp_die() function everytime. What's stranger is that it seems to have some intelligence to be saving an id that is relative to the original post, such as a sibling of the same CPT nested at the same level hierarchically, when there is no other information being sent or no other information that should be interfering with the metadata in question. The meta_key "course_bookmark" does not exist anywhere else in the code. For all I know, the update_user_meta() function doesn't even know that it is saving a POST id as the only thing that it is receiving is a NUMERIC value. It could be saving someone's age or zip code for all that the code should known, it's just a numeric value with no other references to that fact that it is a post id.

It shouldn't know that that this is a post id, and it shouldn't know that it has any relationship to other post_ids that happen to be the same CPT, the same hierarchical level, and that is a published ID, not a revision.

My original function was much more elaborate and I have moved this code all over in my plugin, only realize that the issue does in fact appear to be with wordpress's update_user_meta() function, and not the position in which I am calling it or anything else for that matter. The right ID is being sent to the function, but the wrong ID is being recorded (I checked the recorded value in PHPMyAdmin) and returned. But why?

UPDATE:

If you follow the comments for this post and Pieter's suggestions, this is where I've gotten to:

For some reason it appears that this bug seemed to be directly related to the <link rel='next' href='path\to\next\post\' /> which WordPress generates in the <head> section of the html. I'm assuming this is placed there to help with SEO. I am not running any SEO-related plugins on this particular site, but there is something either in my code (not likely, as I'm not that sophisticated) or the theme that I'm using (Pinnacle by Kadence Themes - wonderful theme, by the way) that is taking that information and causing the next page to run via a $_GET command according to my browser's network monitor. As an interim solution, I'm following the suggestion provided in this post by Hamergil and have added this block of code to my LMS plugin:

add_filter( 'index_rel_link', 'disable_stuff' );
add_filter( 'parent_post_rel_link', 'disable_stuff' );
add_filter( 'start_post_rel_link', 'disable_stuff' );
add_filter( 'previous_post_rel_link', 'disable_stuff' );
add_filter( 'next_post_rel_link', 'disable_stuff' );

function disable_stuff( $data ) {
    return false;
}

Not sure if this is the best way to approach this or not, so I'm still leaving this open as unanswered.

3
  • So, I've determined that the same behavior exists by bypassing the update_user_meta() function all together and doing a direct call to the $wpdb::update() method. Very frustrating. I'm almost ready to create a new database table to see if the behavior still exists.
    – Brent
    Commented Mar 8, 2016 at 22:04
  • Here's another update. And this one is most revealing... Turns out that for some reason, the function is being called twice. Not sure why, but the second time, it gets called there is a different post_id. Not sure how to counteract this.
    – Brent
    Commented Mar 8, 2016 at 22:22
  • You should post updates as edits inside your question. Important info that might help someone to answer your question should never be in comments ;-) Commented Mar 9, 2016 at 7:14

1 Answer 1

0

I believe your issue is related to getting the correct post object on your single post page. From what I can detect from your question is that $post does not contain the post object which you expect.

You must remember, $post is one of those very crappy globals which WordPress uses to store the current post object in. Inside the loop, $post is normally quite reliable, but outside the loop, it is a totally different ball game altogether. The issue is, $post can be altered by any code on your page, even by the main loop. Any query making use of setup_postdata( $post ) or the_post() sets the $post global to the current post in the loop, so $post at any given time can be something different depending where on the page you are.

Forgetting to use something simple like wp_reset_postdata() after a custom query loop will leave the value of $post to the last post in the custom query, and not the current page object. It seems to be the issue you are having, something has set the $post global to something other that the current page object.

There are ways to get the current single post page's object more reliably. You can have a look at my post here and read up on the subject. Because functions like query_posts also breaks the main query object, I lately started using the values from $GLOBALS['wp_the_query'] which is the most reliable way to get correct data.

One other issue here is, you are running this on every page load. Simply checking for post type is not enough. Remember, you using the $post global which can hold any post object. If that particular post object has the correct post type, your code will execute regardless of the page you are currently on, so we must check whether or not if we are on the desired post type's single post page

Lets rewrite your function (watered down version) to be more reliable: (NOTE: The code is untested and requires PHP 5.4+, also, you can always sub this by making use of cookies to store the data on client side, although it is a less reliable method)

VERSION 1

If you need the complete post object, this will be the version to use

/**
 * We can use the template_redirect hook here aswell. Change back to
 * wp_footer if this suits you better
 */ 
add_action( 'template_redirect', 'nb_course_post_actions' );
function nb_course_post_actions(){
    global $current_user;

    // Make sure we are on a singular course post type page, if not, bail
    if ( !is_singular( 'course' )
        return;

    // Make sure we have a logged in user
    if ( !is_user_logged_in() )
        return; 

    // Great, we are on a single course post page and user is logged in, lets continue

    $post = sanitize_post( $GLOBALS['wp_the_query']->get_queried_object() );

    $sid         = $current_user->ID;
    $bookmark_id = $post->ID;

    // Update the usermeta
    update_user_meta( $sid, 'course_bookmarks', $bookmark_id );
}

VERSION 2

If you only need the post ID, and nothing else

/**
 * We can use the template_redirect hook here aswell. Change back to
 * wp_footer if this suits you better
 */ 
add_action( 'template_redirect', 'nb_course_post_actions' );
function nb_course_post_actions(){
    global $current_user;

    // Make sure we are on a singular course post type page, if not, bail
    if ( !is_singular( 'course' )
        return;

    // Make sure we have a logged in user
    if ( !is_user_logged_in() )
        return; 

    // Great, we are on a single course post page and user is logged in, lets continue

    $sid         = $current_user->ID;
    $bookmark_id = filter_var( $GLOBALS['wp_the_query']->get_queried_object_id(), FILTER_VALIDATE_INT );

    // Update the usermeta
    update_user_meta( $sid, 'course_bookmarks', $bookmark_id );
}
6
  • Pieter, thanks for taking the time to write this up. You gave me some good suggestions on how to improve my code base. I was really hopeful that the template_redirect action hook would have done the trick, but alas it is not so. It as if the entire wordpress process is being run a second time in the background without the second page being loaded to the browser. It seems that all the action hooks are being triggered a second time by the later, un-requested post. I wonder if this is a feature of the theme that I'm running (Pinnacle), or maybe some other plugin that would cause this.
    – Brent
    Commented Mar 9, 2016 at 17:40
  • 1
    Have you tested this on a vanilla install with a bundled theme. Seems like you have some redirection issue coming from somewhere. This issue could be anywhere Commented Mar 9, 2016 at 17:44
  • Yeah, I'm looking for that now. This is completely different that what I had supposed to be the issue. It seems like there's an image or some other resource that's being called. This is going to be something I track down on my own. What's really still got me baffled though is why it's loading a close sibling to the post? I actually think it's loading the next available published post of the same post type.
    – Brent
    Commented Mar 9, 2016 at 18:18
  • That something is changing the $post global, and from your comments, it really seems to have something to do with some kind of pagination function. It is unfortunately something that I cannot help you with as it is not default core behavior. All I can offer is a good luck Commented Mar 9, 2016 at 18:24
  • I'm good. I'll post my answer when I find it. It does seem to be pagination or something related to that. Thnks!
    – Brent
    Commented Mar 9, 2016 at 18:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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