Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I'm creating a platform with "X" categories and "X" roles with the same name to each category.

I would like to know how to change the user's role after they have read all the posts in a category. When the role changes, the user will have access to the next category.

To make the categories private and to create the related groups of users I'm using the Role Scoper plugin.

This solution is very similar to what I mean: http://stackoverflow.com/questions/9181440/wordpress-change-user-role-conditionally


share|improve this question
Since your comment expands on your Question, please edit your Question and place it in there. More people will see it. You can delete your comment afterward. Also, please only submit an Answer to your own Question if it is a real Answer. Any questions you have about someone else's Answer should be posed as a comment under that Answer. –  akTed Jan 22 '13 at 14:05
add comment

1 Answer

Just to clarify:
You have, for example, the categories 'foo', 'bar' and 'baz'. New users with the role 'foo' can only read posts in the category 'foo'. After they have read all the posts in 'foo', their role will be changed to 'bar' so they can read the posts in category 'bar'. And so on...

Everything starts with a good plan, so let's make a plan.

  • The first thing is to keep the post IDs the user has read. Every time the user opens a post (i.e. opens the single view of a post), we save the post ID of this post.
  • If we save a post ID, we have to check if the user has read all needed posts to get updated to the next level. This means the user has read all the posts from category X.
  • If the user has read all the necessary posts, update the user to the next level.

Sounds easy, doesn't it? I put it all together in one class because this saves some code and makes some things easier. Call this class with $updated = new WPSE78727(); inside your single post template (single.php), e.g. at the beginning of the template.

If the user was updated, $updated->updated is set to the new role of the user, otherwise it is set to false.

class WPSE78727
    // set this to a proper name for a metakey    
    const METAKEY = 'YourUserMetaKey';

    public $user_ID      = 0;
    public $user_data    = null;
    public $current_role = '';
    public $updated      = false;

    // edit this to your needs
    // this is the sequence of category names the user
    // has to read to get updated to the next level
    public $role_sequence = array(

    public function __construct() {

        if( is_single() || user_is_logged_in() ) {

            $updated = $this->save_post_id_in_user_meta();

            $this->updated = ( is_string( $updated ) ) ?
                 $updated : false;



    public function get_user_data() {

        $user               = wp_get_current_user();
        $this->user_ID      = $user->ID;
        $this->user_data    = get_userdata( $this->user_ID );
        $this->current_role = $user_data->roles[0]; // get the first role

        return true;

    public function save_post_id_in_user_meta() {

      global $post;

        // make the code more readable
        $role = &$this->current_role;

        // $read_post should be an array
        $posts_read = get_user_meta( $this->user_ID, self::METAKEY, true );

        // be sure $posts_read is an array
        if( ! is_array( $posts_read ) )
            $posts_read = array();

            // make an array for the post IDs which belongs to the current role
        if( ! isset( $posts_read[$role] ) || ! is_array( $posts_read[$role] ) )
            $read_posts[$role] = array();

        // if the current post ID is not in the array, push it into the array
        if( ! in_array( $post->ID, $posts_read[$role] ) )
            array_push( $posts_read[$role], $post->ID );

            // update the metadata
        update_user_meta( $this->user_ID, self::METAKEY, $posts_read );

        $new_role = $this->maybe_all_posts_read( $posts_read );

        return ( ! is_array( $new_role ) ) ?
            $new_role : $posts_read;


    public function maybe_all_posts_read( $posts_read = array() ) {

        $current_cat_id = get_cat_ID( $this->current_role );

        // get all post IDs from this category
        $post_ids = get_posts(
            'numberposts'   => -1, // get all posts.
            'tax_query'     => array(
                    'taxonomy'  => 'category',
                    'field'     => 'id',
                    'terms'     => $current_cat_id,
            'fields'        => 'ids', // Only get post IDs

        // check if the user has read all posts. Simply check the differences between
        // $post_ids (all post IDs in the current category (e.g. foo) and the read posts
        // stored in user meta
        $left_posts = array_diff( $post_ids, $posts_read[$this->current_role] );

        // if no post is left, update the user
        if( empty( $left_posts ) )
            $new_role = $this->update_user();

        return ( empty( $left_posts ) ) ?
            $new_role : $left_posts;


    public function update_user() {

        // get the next role out of the sequence
        $key = array_search( $this->current_role, $this->role_sequence );

        // don't run out of the array!
        $max = count( $this->role_sequence );
        if( $key > $max )
            $key = $max - 1;

        $new_role = $role_sequence[$key+1];

        // merge old and new data
        $new_user_data = array_merge(
                'ID'   => $id,
                'role' => $new_role

        // using wp_insert_user instead of wp_update_user without any reasons
        wp_insert_user( $new_user_data );

        return $new_role;



This code is untested and may be buggy. It should be considered a starting point and not as a complete solution.

share|improve this answer
OK, first i want to thank you for the time you grant me and for your kindness! I read carefully your answer and sure, this is what I meant... So I start with the plan! –  emanuele Jan 12 '13 at 1:52
I run the first test of the code, i'll write soon how it goes. There is not problem if it's buggy. When I have a first result i write here the answer! See you son! –  emanuele Jan 12 '13 at 1:54
What is the correct const METAKEY? I'm searching in wordpress database but I dont know where to find this... The code is correct but i'dont know the const METAKEY = 'YourUserMetaKey'. Any idea? Thanks –  emanuele Jan 22 '13 at 13:12
You have to setup this value at your own. The value YourUserMetaKey is just an example. –  Ralf912 Jan 22 '13 at 16:42
Yes i know. But i've tried to insert const METAKEY = 'wp_user_level' and checking the database actually the wp_user_level change from wp_user_level = 0 to wp_user_level = a:1:{s:0:"";N;}. But this is incorrect right? Then I don't know the value to be put in this string... –  emanuele Jan 22 '13 at 17:17
show 9 more comments

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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