How to filter backend post list showing only posts having a shortcode

AFAIK that sort of hook does not exists. You have 2 choiches:

  1. use 'post_class' filter hook, check if in admin and if the query string contain a shortcode and if so and if the post has not that shortcode add hidden class that via core admin CSS is set to display: none. In this way the posts without shortcode are retrieved, but hidden.
  2. Second solution is to use a filter on 'posts_where' and add a SQL where clause using REGEXP MySQL function in this way post are not retrieved

I prefer the second solution, that seems to me more elegant and performant, however, maybe the core has_shortcode function is more reliable than a simple SQL regex.

Solution 1

add_filter( 'post_class', 'filter_posts_by_shortcode_css', 10, 3 );

function filter_posts_by_shortcode_css( $classes, $class, $postid ) {
  if ( is_admin() ) {
    $screen = get_current_screen();
    $sh = filter_input( INPUT_GET, 'shortcode', FILTER_SANITIZE_STRING );
    if ( ! empty( $sh ) && $screen->base === 'edit' ) {
      $post = get_post( $postid );
      if( ! has_shortcode( $post->post_content, $sh ) ) {
        $classes[] = 'hidden';
      }
    }
  }
  return $classes;
}

Solution 2

add_action('posts_where', 'filter_posts_by_shortcode');

function filter_posts_by_shortcode( $where ) {
  if ( is_admin() ) {
    $screen = get_current_screen();
    $sh = filter_input( INPUT_GET, 'shortcode', FILTER_SANITIZE_STRING );
    if ( $screen->base === 'edit' && ! empty( $sh ) && shortcode_exists( $sh ) ) {
      $where .= " AND post_content REGEXP '\\\[([[:blank:]]*)$sh([^\\\[]*)\\\]'";
    }
  }
  return $where;
}

Please note that to make both solution work, the shortcode(s) must be registered via add_shortcode, so if the shortcode(s) is(are) registered by yourself or by a third party plugin/theme, be sure that it/they is/are registered in admin screens and before the query happen.