Change role after the user has read all the posts in a category

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.

<?php
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(
        'foo',
        'bar',
        'baz'
    );

    public function __construct() {

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

            $this->get_user_data();
            $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(
          array(
            'numberposts'   => -1, // get all posts.
            'tax_query'     => array(
                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(
            $this->user_data,
            array(
                '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.

Leave a Comment