First of all you have to deny access to trash page in admin. To show trash, the 'post_status'
argument for main query must be 'trash', so you can check that and redirect if current user is not an administrator:
add_action( 'pre_get_posts', 'deny_trash_access' );
function deny_trash_access( $query ) {
// nothing to do if not in admin or if user is an admin
if (
! is_admin()
|| ! $query->is_main_query()
|| current_user_can( 'remove_users' )
) return;
$status = $query->get( 'post_status' );
if (
$status === 'trash'
|| ( is_array( $status ) && in_array( 'trash', $status, true ) )
) {
wp_safe_redirect( admin_url() );
exit();
}
}
However, that not prevent users to restore or delete permanently posts.
That can be done using 'wp_insert_post_data' filter, and if in current post data the post status is "trash", and a user try to change it, you can abort the editing by forcing 'post_status'
to be "trash":
add_filter( 'wp_insert_post_data', 'prevent_trash_status_change', 999, 2 );
function prevent_trash_status_change( $data, $postarr ) {
if ( ! isset( $postarr['ID'] ) || empty( $postarr['ID'] ) ) return $data;
if ( current_user_can( 'remove_users' ) ) return $data; // admin can edit trash
// get the post how it is before the update
$old = get_post( $postarr['ID'] );
// if post is trashed, force to be trashed also after the update
if ( $old->post_status === 'draft' ) {
$data['post_status'] = 'draft';
}
return $data;
}
Put this 2 code snippets in your function.php or in a plugin file and you are done.
Edit: This is an old post, for anybody reading this today, the solution above does not work (any more?). Users can still delete from trash, and publishing drafts doesn't work reliably.