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.

2 Answers 2

0

Revised Answer

( PS: I actually wanted to revise this answer long ago, but eventually I kept forgetting to do so. :) )

So in this revised answer, the main point is the first one below, but I hope the rest also help you:

  1. What you're trying to do is not going to be (easily) possible with the way the IDs are stored, which is serialized such as a:3:{i:0;i:1;i:1;i:10;i:2;i:11;} (for array( 1, 10, 11 )).

    What if the current user ID is 10 and in the meta value, there's an i:10;i:9, i.e. array item keyed 10 with the value (a user ID) of 9 (i.e. $some_variable[10] = 9), but the ID 10 is not actually in the list?

  2. So because of the above, you might better off create a custom database table and store the user ID and post ID in their own column.

    And if you want, you can check a simplified example/demo on DB Fiddle.

  3. Or you could instead store each user ID in individual post meta named post_is_read. That way, filtering the posts would be easy — but the post meta table would end up being really huge with many, many rows...

    // Saving the meta:
    add_post_meta( 1, 'post_is_read', 10 );
    add_post_meta( 1, 'post_is_read', 11 );
    
    // Then when filtering the posts:
    $user_id = 10;
    $args = array(
        'meta_key'     => 'post_is_read',
        'meta_value'   => $user_id,
        'meta_compare' => '!=',
    );
    $query = new WP_Query( $args );
    
  4. Or you may use a serialized value, but store the user name/login and not the ID:

    // Store usernames and not IDs.
    $usernames = array( 'foo', 'user-bar', 'etc_etc' );
    
    update_post_meta( 123, 'post_is_read', $usernames );
    

    And then you can use the NOT LIKE comparison like so:

    $username = 'user-bar';
    $args = array(
        'meta_key'     => 'post_is_read',
        'meta_value'   => '"' . $username . '"', // search for exactly "<username>" (including the quotes)
        'meta_compare' => 'NOT LIKE',
    );
    $query = new WP_Query( $args );
    

Original Answer

Please check the revisions, but basically in my original answer, I was saying if you'd rather use the user IDs, then you could store them as a comma-separated list like 1,10,11 and then use the NOT REGEXP comparison:

// Saving the meta:
update_post_meta( 1, 'post_is_read', '1,10,11' );

// Then when filtering the posts:
$user_id = 10;
$args = array(
    'meta_key'     => 'post_is_read',
    'meta_value'   => "(^|,)$user_id(,|$)",
    'meta_compare' => 'NOT REGEXP',
);
$query = new WP_Query( $args );

And that did (and still does) work (with WordPress 5.6.1), except that it needs an extra parsing when retrieving the meta value, e.g.:

$ids = get_post_meta( 1, 'post_is_read', true );
$ids = wp_parse_id_list( $ids ); // convert to array
9
  • 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, 2019 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, 2019 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, 2019 at 13:09
  • Thank you very much Sally, will try your solution and let you know. I am just curious, would your method work the same way if I was storing words instead of IDs? I suppose I should use wp_parse_list instead. Is wp_parse_id_list used solely for IDs or works for any number regardless? I am sorry to bother you but I am more interested in learning how things work instead of just coping/pasting some code.
    – Arg Geo
    Aug 12, 2019 at 13:36
  • 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, 2019 at 10:04
4

I store my IDs as strings so this is what I do

$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'   => serialize(strval($current_user->ID)),
        'compare' => 'LIKE',  
    )
),);

The same should work even if you store it as int:

'value'   => serialize($current_user->ID)
1
  • Thank you, man. that works perfectly.
    – Arman H
    Jun 14, 2022 at 10:21

Your Answer

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

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