Cuándo un usuario con rol «autor» entra en el panel de administración de WordPress, puede ver el listado de todos los posts que hay en el sitio, incluso posts de otros autores que están en estado de borrador. Aunque no podrá ver el contenido ni editarlo, creo que es muy interesante que en una web multi-autor esa información pueda quedar reservada a editores y administradores.

En realidad, como decía en el post sobre permitir acceso a wp-admin sólo a administradores, lo más lógico no es hablar de roles de usuario, como es «autor», sino de las capacidades del usuario (user capabilities, yo prefiero traducirlo como «competencias de usuario», que tiene más sentido en este contexto pero parece que se ha impuesto el término «capacidades»). Cada rol de usuario tiene unas capacidades preestablecidas pero se pueden modificar, por eso comprobar roles de usuario no es un método fiable para decidir lo que le permitimos o no al usuario.

En el caso que nos ocupa, lo más lógico sería que los usuarios sin capacidad para editar posts de otros autores sólo puedan ver los suyos propios. Dicho de otro modo, sólo los usuarios con la competencia edit_others_posts asignada podrán ver todos los posts en la administración de WordPress. Cosa lógica, si sólo puedo editar mis posts, ¿para que quiero ver los posts de los demás en el dashboard?

La lógica a seguir podría resumirse en tres pasos:

  1. Comprobar que estamos en la pantalla de posts del panel de administración
  2. Comprobar si el usuario actual puede editar los posts de otros autores
  3. Modificar el query para incluir el argumento author y establecerlo en el ID del usuario actual

De forma general, el query de posts en WordPress se puede modificar utilizando el action hook pre_get_posts. El siguiente código utiliza este action y funcionaría tanto para el tipo de posts estándar como para custom post types:

add_action( 'pre_get_posts', 'cyb_list_own_posts_for_authors' );
function cyb_list_own_posts_for_authors( $query ) {

  // solo para la administración y el query principal
  if( is_admin() && $query->is_main_query() ) {

    // Obtenemos la inforamción sobre la pantalla actual
    // y el post type solicitado
    $current_screen = get_current_screen();
    $post_type_object = get_post_type_object( get_query_var( 'post_type' ) );

    // Comprobamos que la pantalla sea la de edición de posts
    // y si el usuario actual puede editar los posts de otros autores
    // Nota: get_current_screen() y get_post_type_object() pueden devolver null
    if(
        ( ! is_null( $current_screen ) && $current_screen->base == 'edit' )
        && ( ! is_null( $post_type_object ) && ! current_user_can( $post_type_object->cap->edit_others_posts ) )
    ) {

      // Establecer el parámetro "author" igual al usuario actual
      $query->set( 'author', get_current_user_id() );

    }

  }
}

También se puede conseguir el mismo resultado pero con menos código utilizando el action load-edit.php junto al action parse_resquest. Al utilizar el action load-edit.php ya no necesitamos comprobar si estamos en el backend ni comprobar la pantalla actual. Y como el action parse_request sólo afecta al query principal también nos podemos ahorrar esa comprobación:

add_action( 'load-edit.php', 'cyb_list_own_posts_for_authors' );
function cyb_list_own_posts_for_authors() {

    add_action( 'parse_request', function( $query ) {
        $post_type_object = get_post_type_object( $query->query_vars['post_type'] );
 
        if ( ! is_null( $post_type_object ) && ! current_user_can( $post_type_object->cap->edit_others_posts ) ) {
            $query->query_vars['author'] = get_current_user_id();
        }

    } );

}

Ambos métodos son igualmente válidos; el segundo es mucho más limpio pero el primero podría ser más conveniente en casos de que necesitemos más libertad o aplicar la restricción de una forma más amplia.

Mostrar el recuento correcto de posts

Con cualquiera de los métodos anteriores, el usuario actual ya vería sólo sus propios posts pero ahora algunos de los filtros (views) en las pantallas de edición muestran un recuento de posts incorrecto. Se puede arreglar utilizando el filtro views_edit-{post-type} y haciendo de nuevo el recuento. Por ejemplo, el siguiente código haría el recuento para el tipo de post estándar (post) y el tipo de post news:

add_filter('views_edit-post', 'cyb_views_filter_for_own_posts' );
add_filter('views_edit-news', 'cyb_views_filter_for_own_posts' );
function cyb_views_filter_for_own_posts( $views ) {
 
  $post_type = get_query_var( 'post_type' );
  $post_type_object = get_post_type_object( $post_type );
  
  // No seguir si el usuario puede editar los posts de otros autores
  if ( is_null( $post_type_object ) || current_user_can( $post_type_object->cap->edit_others_posts ) ) {
    return $views;
  }

  unset( $views['mine'] );
 
  $new_views = array(
    'all' => __('All'),
    'publish' => __('Published'),
    'private' => __('Private'),
    'pending' => __('Pending Review'),
    'future' => __('Scheduled'),
    'draft' => __('Draft'),
    'trash' => __('Trash')
  );

  $author = get_current_user_id();
 
  foreach( $new_views as $view => $name ) {
 
    $query = array(
      'author' => $author,
      'post_type' => $post_type
    );
 
    if($view == 'all') {
 
      $query['all_posts'] = 1;
      $class = ( get_query_var('all_posts') == 1 || get_query_var('post_status') == '' ) ? ' class="current"' : '';
      $url_query_var = 'all_posts=1';
 
    } else {
 
      $query['post_status'] = $view;
      $class = ( get_query_var('post_status') == $view ) ? ' class="current"' : '';
      $url_query_var = 'post_status='.$view;
 
    }
 
    $result = new WP_Query($query);
 
    if($result->found_posts > 0) {
 
      $views[$view] = sprintf( 
        '<a href="%s"'. $class .'>'.__($name).' <span class="count">(%d)</span></a>',
        admin_url('edit.php?'.$url_query_var.'&post_type='.$post_type),
        $result->found_posts
      );
 
    } else {
 
      unset($views[$view]);
 
    }
 
  }

  return $views;

}

Ten en cuenta que este «recuento» es efectivamente un re-cuento, quiero decir que se hace dos veces. Una vez la que hizo WordPress y otra la que hacemos nosotros después. Esto supone más queries a la base de datos y puede tener implicaciones negativas en la performance, aunque no es en la parte pública y no habría impacto negativo sobre los usuarios del frontend.

También puedes quitar el contador de posts en cada filtro:

add_filter( 'views_edit-post', 'cyb_remove_views_filter_counter' );
add_filter( 'views_edit-news', 'cyb_remove_views_filter_counter' );

function cyb_remove_views_filter_counter( $views ) {

  foreach ( $views as $index => $view ) {

    $views[ $index ] = preg_replace( '/ <span class="count">\([0-9]+\)<\/span>/', '', $view );

  }

  return $views;

}

O quitar todos esos filtros si nos los quieres:

add_filter('views_edit-post', 'cyb_remove_views_filter' );
add_filter('views_edit-news', 'cyb_remove_views_filter' );

function cyb_remove_views_filter( $views ) {

  return array();

}

Y si lo queréis en un plugin listo para usar, aquí lo tenéis.