Remove pages from backend list *and* update counter accordingly

Yes, the count for pages in various statuses that you see above the list table is obtained running wp_count_posts but the pages in the table are obtained running a WP_Query and the 2 things are completely unrelated.

If you want to also modify the count, you have to filter also the output of wp_count_posts using the 'wp_count_posts' filter hook.

Also you should check the status of the page(s) you want to exclude from edit, because wp_count_posts count all the statuses separately.

So we have 2 tasks:

  • remove the pages from the query
  • remove the pages from the count

I’ll write 2 functions, for the 2 tasks, and in both I need to check if we are in admin and if the current user has the needed capability and finally if the current screen is the right one. I’ll write an addtional function that does these checks, to avoid writing same code 2 times:

function in_pages_edit() {
  if ( ! is_admin() || ! current_user_can( 'edit_pages' ) ) return FALSE; 
  $s = get_current_screen();
  return ( $s instanceof WP_Screen && $s->id === 'edit-page' );
}

Now we can add the 2 hooks for out 2 tasks, I’ll add these 2 hooks inside a 'load-edit.php' action:

function go_exclude_pages_from_edit() {
  if ( ! in_pages_edit() ) return;
  global $exclude_pages_from_edit;
  $exclude_pages_from_edit = array( 662 ); // <-- set here the page ids to exclude
  add_filter( 'wp_count_posts', 'change_pages_count', 10, 3 );
  add_action( 'pre_get_posts', 'exclude_pages_from_edit' );
}

add_action( 'load-edit.php', 'go_exclude_pages_from_edit' );

Now we can remove the pages from being queried:

function exclude_pages_from_edit( $query ) {
  if ( ! in_pages_edit() || ! $query->is_main_query() ) return;
  global $exclude_pages_from_edit;
  if ( ! empty($exclude_pages_from_edit) ) {
    $query->set( 'post__not_in', $exclude_pages_from_edit );
  }
}

and adjust the counts (see inline comments for explaination):

function get_valid_page_from_post( $p ) {
  if ( $p instanceof WP_Post &&  $p->post_type === 'page' ) return $p;
}

function change_pages_count( $counts, $type, $perm  ) {
  // do nothing if not on right page
  if ( ! in_pages_edit() || $type !== 'page' ) return $counts;
  // do the work only once
  static $new_counts;
  if ( ! is_null( $new_counts ) ) return $new_counts;
  global $exclude_pages_from_edit;
  // get the pages objects and be sure they are valid pages
  $excluded = array_filter(
    array_map( 'get_post', $exclude_pages_from_edit ), 'get_valid_page_from_post'
  );
  // if no page object obtained do nothing
  if ( empty( $excluded ) ) return $counts;
  // count the statuses of the page objectt
  $statuses_excluded = array_count_values ( wp_list_pluck( $excluded, 'post_status' ) );
  // subtract from each status count the excluded pages status count
  foreach ( $statuses_excluded as $status => $num ) {
    if ( isset( $counts->$status ) ) {
      $counts->$status = (string) $counts->$status - $num;
    }       
  }
  // save the ajusted count for next running and return
  $new_counts = $counts;
  return $counts;
}