Hide other users’ posts in admin panel

I intended to run a multi-author site, I don’t want the posts from other authors to be shown in /wp-admin/edit.php page.

I managed solve this problem by the codes from this thread. The code is like this:

Read More
function posts_for_current_author($query) {
    global $pagenow;

    if( 'edit.php' != $pagenow || !$query->is_admin )
        return $query;

    if( !current_user_can( 'manage_options' ) ) {
        global $user_ID;
        $query->set('author', $user_ID );
    }
    return $query;
}
add_filter('pre_get_posts', 'posts_for_current_author');

The codes work great, it hide the posts from other authors to be shown at here. But I do find another problem – the menu at top of the page doesn’t change the associated number of posts by the author, it shows the number of all the post in my site.

The menu I mean is like this:

Mine () | All () | Published () | Draft () | Trash ()

How to change the number in the () to reflect the number only associated to the author?

Related posts

Leave a Reply

3 comments

  1. Here is what I use:

    // Show only posts and media related to logged in author
    add_action('pre_get_posts', 'query_set_only_author' );
    function query_set_only_author( $wp_query ) {
        global $current_user;
        if( is_admin() && !current_user_can('edit_others_posts') ) {
            $wp_query->set( 'author', $current_user->ID );
            add_filter('views_edit-post', 'fix_post_counts');
            add_filter('views_upload', 'fix_media_counts');
        }
    }
    
    // Fix post counts
    function fix_post_counts($views) {
        global $current_user, $wp_query;
        unset($views['mine']);
        $types = array(
            array( 'status' =>  NULL ),
            array( 'status' => 'publish' ),
            array( 'status' => 'draft' ),
            array( 'status' => 'pending' ),
            array( 'status' => 'trash' )
        );
        foreach( $types as $type ) {
            $query = array(
                'author'      => $current_user->ID,
                'post_type'   => 'post',
                'post_status' => $type['status']
            );
            $result = new WP_Query($query);
            if( $type['status'] == NULL ):
                $class = ($wp_query->query_vars['post_status'] == NULL) ? ' class="current"' : '';
                $views['all'] = sprintf(__('<a href="%s"'. $class .'>All <span class="count">(%d)</span></a>', 'all'),
                    admin_url('edit.php?post_type=post'),
                    $result->found_posts);
            elseif( $type['status'] == 'publish' ):
                $class = ($wp_query->query_vars['post_status'] == 'publish') ? ' class="current"' : '';
                $views['publish'] = sprintf(__('<a href="%s"'. $class .'>Published <span class="count">(%d)</span></a>', 'publish'),
                    admin_url('edit.php?post_status=publish&post_type=post'),
                    $result->found_posts);
            elseif( $type['status'] == 'draft' ):
                $class = ($wp_query->query_vars['post_status'] == 'draft') ? ' class="current"' : '';
                $views['draft'] = sprintf(__('<a href="%s"'. $class .'>Draft'. ((sizeof($result->posts) > 1) ? "s" : "") .' <span class="count">(%d)</span></a>', 'draft'),
                    admin_url('edit.php?post_status=draft&post_type=post'),
                    $result->found_posts);
            elseif( $type['status'] == 'pending' ):
                $class = ($wp_query->query_vars['post_status'] == 'pending') ? ' class="current"' : '';
                $views['pending'] = sprintf(__('<a href="%s"'. $class .'>Pending <span class="count">(%d)</span></a>', 'pending'),
                    admin_url('edit.php?post_status=pending&post_type=post'),
                    $result->found_posts);
            elseif( $type['status'] == 'trash' ):
                $class = ($wp_query->query_vars['post_status'] == 'trash') ? ' class="current"' : '';
                $views['trash'] = sprintf(__('<a href="%s"'. $class .'>Trash <span class="count">(%d)</span></a>', 'trash'),
                    admin_url('edit.php?post_status=trash&post_type=post'),
                    $result->found_posts);
            endif;
        }
        return $views;
    }
    
    // Fix media counts
    function fix_media_counts($views) {
        global $wpdb, $current_user, $post_mime_types, $avail_post_mime_types;
        $views = array();
        $count = $wpdb->get_results( "
            SELECT post_mime_type, COUNT( * ) AS num_posts 
            FROM $wpdb->posts 
            WHERE post_type = 'attachment' 
            AND post_author = $current_user->ID 
            AND post_status != 'trash' 
            GROUP BY post_mime_type
        ", ARRAY_A );
        foreach( $count as $row )
            $_num_posts[$row['post_mime_type']] = $row['num_posts'];
        $_total_posts = array_sum($_num_posts);
        $detached = isset( $_REQUEST['detached'] ) || isset( $_REQUEST['find_detached'] );
        if ( !isset( $total_orphans ) )
            $total_orphans = $wpdb->get_var("
                SELECT COUNT( * ) 
                FROM $wpdb->posts 
                WHERE post_type = 'attachment' 
                AND post_author = $current_user->ID 
                AND post_status != 'trash' 
                AND post_parent < 1
            ");
        $matches = wp_match_mime_types(array_keys($post_mime_types), array_keys($_num_posts));
        foreach ( $matches as $type => $reals )
            foreach ( $reals as $real )
                $num_posts[$type] = ( isset( $num_posts[$type] ) ) ? $num_posts[$type] + $_num_posts[$real] : $_num_posts[$real];
        $class = ( empty($_GET['post_mime_type']) && !$detached && !isset($_GET['status']) ) ? ' class="current"' : '';
        $views['all'] = "<a href='upload.php'$class>" . sprintf( __('All <span class="count">(%s)</span>', 'uploaded files' ), number_format_i18n( $_total_posts )) . '</a>';
        foreach ( $post_mime_types as $mime_type => $label ) {
            $class = '';
            if ( !wp_match_mime_types($mime_type, $avail_post_mime_types) )
                continue;
            if ( !empty($_GET['post_mime_type']) && wp_match_mime_types($mime_type, $_GET['post_mime_type']) )
                $class = ' class="current"';
            if ( !empty( $num_posts[$mime_type] ) )
                $views[$mime_type] = "<a href='upload.php?post_mime_type=$mime_type'$class>" . sprintf( translate_nooped_plural( $label[2], $num_posts[$mime_type] ), $num_posts[$mime_type] ) . '</a>';
        }
        $views['detached'] = '<a href="upload.php?detached=1"' . ( $detached ? ' class="current"' : '' ) . '>' . sprintf( __( 'Unattached <span class="count">(%s)</span>', 'detached files' ), $total_orphans ) . '</a>';
        return $views;
    }
    

    Source

  2. Shorter solution based on answer https://wordpress.stackexchange.com/a/49200/83038.

    NOTE: Available since WordPress 3.7.0.

    function fix_count_orders( $counts, $type ) {
        global $wpdb;
    
        $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
        $query .= $wpdb->prepare( " AND post_author = %d", get_current_user_id() );
        $query .= ' GROUP BY post_status';
    
        $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
        $counts = array_fill_keys( get_post_stati(), 0 );
    
        foreach ( $results as $row ) {
            $counts[ $row['post_status'] ] = $row['num_posts'];
        }
    
        return (object) $counts;
    }
    
    
    function query_set_only_author( $wp_query ) {
        global $current_user;
    
        // Add here post types for which you want to fix counts ('post' added for example).
        $allowed_types = array( 'post' );
        $current_type = get_query_var( 'post_type' ) ? get_query_var( 'post_type' ) : '';
    
        if( is_admin() && ! current_user_can( 'edit_others_posts' ) && in_array( $current_type, $allowed_types ) ) {
            $wp_query->set( 'author', $current_user->ID );
            add_filter( 'wp_count_posts', 'fix_count_orders' );
        }
    }
    
    add_action( 'pre_get_posts', 'query_set_only_author', 10, 2 );
    
  3. The Best Way

    ALL THESE ANSWERS HERE HAVE SECURITY CONCERNS.

    The best way is adding custom capabilities and managing posts etc. by the capabilities.


    An easy way

    Artem’s solution seems to be better because WP doesn’t refer post counts only on post edit screen but also within Dashboard widget, Ajax response etc.

    For better solution based on Artem’s one.

    1. clear the default post counts cache.
      why: wp_count_posts earlierly returns the cached post counts when the result has been cached before.
    2. cache the result of custom post counts.
      why: cache increases the performance.
    3. respect the 3rd $perm parameter of wp_count_posts hook.
      why: the post counts should includes user’s own private posts based on readable perm.
    4. apply filters as high priority filters.
      why: the filters might be overridden by other filters.
    5. remove (or modify) sticky posts count.
      why: sticky posts count includes other’s posts and they are separatedly counted by WP_Posts_List_Table.
    6. use proper capability for Custom Post Type
      why: read_others_posts capability could be modified.

    You might want to additional tweaks

    • filter others posts’ comments by setting post_author query var to WP_Comment_Query.
    • tweaks comments count by wp_count_comments hook.
    • prevent access to admin screens that should be restricted.

    The following is a modified version based on wp_post_counts() of WP 4.8.

    function clear_cache() {
        // deletes the default cache for normal Post. (1)
        $cache_key = _count_posts_cache_key( 'post' , 'readable' );
    
        wp_cache_delete( $cache_key, 'counts' );
    }
    
    add_action( 'admin_init', 'clear_cache' );    // you might use other hooks.
    
    function fix_count_orders( $counts, $type, $perm ) {
        global $wpdb;
    
        if ( ! post_type_exists( $type ) ) {
            return new stdClass();
        }
    
        $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
    
        $post_type_object = get_post_type_object( $type );
    
        // adds condition to respect `$perm`. (3)
        if ( $perm === 'readable' && is_user_logged_in() ) {
            if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
                $query .= $wpdb->prepare(
                    " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
                    get_current_user_id()
                );
            }
        }
    
        // limits only author's own posts. (6)
        if ( is_admin() && ! current_user_can ( $post_type_object->cap->edit_others_posts ) ) {
            $query .= $wpdb->prepare( ' AND post_author = %d', get_current_user_id() );
        }
    
        $query .= ' GROUP BY post_status';
    
        $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
        $counts  = array_fill_keys( get_post_stati(), 0 );
    
        foreach ( $results as $row ) {
            $counts[ $row['post_status'] ] = $row['num_posts'];
        }
    
        $counts    = (object) $counts;
        $cache_key = _count_posts_cache_key( $type, 'readable' );
    
        // caches the result. (2)
        // although this is not so efficient because the cache is almost always deleted.
        wp_cache_set( $cache_key, $counts, 'counts' );
    
        return $counts;
    }
    
    function query_set_only_author( $wp_query ) {
        if ( ! is_admin() ) {
            return;
        }
    
        $allowed_types = [ 'post' ];
        $current_type  = get_query_var( 'post_type', 'post' );
    
        if ( in_array( $current_type, $allowed_types, true ) ) {
            $post_type_object = get_post_type_object( $current_type );
    
            if (! current_user_can( $post_type_object->cap->edit_others_posts ) ) {    // (6)
                $wp_query->set( 'author', get_current_user_id() );
    
                add_filter( 'wp_count_posts', 'fix_count_orders', PHP_INT_MAX, 3 );    // (4)
            }
        }
    }
    
    add_action( 'pre_get_posts', 'query_set_only_author', PHP_INT_MAX );    // (4)
    
    function fix_views( $views ) {
        // For normal Post.
        // USE PROPER CAPABILITY IF YOU WANT TO RISTRICT THE READABILITY FOR CUSTOM POST TYPE (6).
        if ( current_user_can( 'edit_others_posts' ) ) {
            return;
        }
    
        unset( $views[ 'sticky' ] );
    
        return $views;
    }
    
    add_filter( 'views_edit-post', 'fix_views', PHP_INT_MAX );     // (5)
    

    Known Issue: Sticky posts that don’t belong to user are counted. fixed by removing sticky posts view.