0

In every post of my custom post type called "announcements" I save the users' ID inside an array which resides in a meta_key called "post_is_read" whenever a user reads an announcement.

Just to explain: "post_is_read" contains an array (1, 2, 5, 12, 86, 100) where every number represents a user ID.

I achieved this using this code: https://wordpress.stackexchange.com/a/344315/15801

I am now trying to create a template which will show the posts a user has not read with the following code:

$current_user = wp_get_current_user();
$up_an_query_args = array(
    'post_type'      => 'announcement',
    'post_status'    => 'publish',
    'posts_per_page' => -1,
    'orderby'        => 'date', 
    'order'          => 'DSC',
    'meta_query' => array(
        array(
            'key'     => 'post_is_read', 
            'value'   => $current_user->ID,
            'compare' => 'NOT IN',  
        )
    ),                                      
);

I have read every related article/answer I've found both on and off wordpress stackexchange and no method worked for me. The most I could get it to work is using 'compare' => 'NOT LIKE' but it messed up the posts being displayed for users having at least a common digit in their IDs (ex between 1, 10, 11... etc) which is something I understand why is happening.

1

How about changing the value type?

I would save the meta value as a comma-separated list of numbers (user IDs) than saving it as a serialized string (meta values that are array will be serialized prior to being saved to the database):

  • Example for reading the value:

    $post_id = get_the_ID(); // or whatever value it is
    $ids = get_post_meta( $post_id, 'post_is_read', true );
    $ids = wp_parse_id_list( $ids ); // convert to array
    var_dump( $ids );
    
  • Example for updating the value:

    $post_id = get_the_ID(); // or whatever value it is
    $user_id = get_current_user_id();
    $ids = get_post_meta( $post_id, 'post_is_read', true );
    $ids = wp_parse_id_list( $ids ); // convert to array
    if ( ! in_array( $user_id, $ids ) ) {
        $ids[] = $user_id;
        $ids_str = implode( ',', $ids ); // convert to string
        update_post_meta( $post_id, 'post_is_read', $ids_str );
    }
    

And then in the meta query, you can use the NOT REGEXP operator to query for posts where the user has not yet read, like so:

$current_user = wp_get_current_user();

// Generate the REGEXP string.
$regex = '(^|,)' . $current_user->ID . '(,|$)';

$up_an_query_args = array(
    'post_type'      => 'announcement',
    'post_status'    => 'publish',
    'posts_per_page' => -1,
    'orderby'        => 'date',
    'order'          => 'DESC',
    'meta_query' => array(
        array(
            'key'     => 'post_is_read',
            'value'   => $regex,
            'compare' => 'NOT REGEXP',
        ),
    ),
);

So yes, you can also use REGEXP/NOT REGEXP on serialized database values, but comma-separated list is smaller than serialized string and the REGEXP query would be more reliable — serialized values of arrays with just numbers inside are in this format: a:<number of items>:{i:<index>;i:<value>;i:<index>;i:<value>;...} (e.g. a:3:{i:0;i:1;i:1;i:2;i:2;i:5;} for [ 1, 2, 5 ]), so you can't just use, for example (to match a user ID of 2), i:[0-9]+;i:2; in the REGEXP string (i.e. regular expression pattern).

And as an alternate solution, other than using a custom table, you could save multiple post_is_read metadata for each post, which means the meta value would be a single user ID... but the post meta table would end up being really huge (i.e. having many, many rows)..

  • Thank you for your answer Sally. Isn't it better to save the meta as serialized like the way all the data are being saved in the database by WP itself? I mean, in a sense of having a solid, well preserved/structured database. Also what should I place in my single page (or how to alter the existing code) in order to read the value in the posts loop? Is it like if ( ! in_array( $current_user->ID, $ids ) ) { ? As for the "alternate" solution, it would be a bad practice as the website I am building will have way too many users (students) and DB would end up huge indeed. – Arg Geo Aug 12 at 12:50
  • WordPress serializes meta values which are array because the values have to be saved as string in the database. And I've already explained in my answer how a comma-separated list is better than a serialized string - at least in your case - but if you do not need to query the meta value using the meta_query (or meta_value and meta_compare) parameter(s), then the meta value can just be saved as a serialized string. And I didn't suggest using NOT LIKE because you've already tried it and you know it didn't work properly.. :) – Sally CJ Aug 12 at 13:06
  • And regarding using it (in The Loop, etc.), basically you just need this extra part which converts the list to an array: $ids = wp_parse_id_list( $ids ); - after that, you can use in_array( $user_id, $ids ) to check if a certain user ID is within the list. – Sally CJ Aug 12 at 13:09
  • So IMO, it's really up to you to decide the type of the data that gets saved to the database - i.e. if in PHP it's an array, then in the database it doesn't need to be serialized if you don't want to. Also, if the database values are all in this format: <id>, <id>, ... like 1, 2, 5, isn't that is considered well-structured as well? =) – Sally CJ Aug 12 at 13:29
  • 1
    It works like a charm! Thank you very much! I didn't include the "relation" though, I like the idea of setting a default meta value (0 in my case) better. – Arg Geo Aug 13 at 10:04

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.