WordPress 管理画面の固定ページ一覧を、子ページに絞りこんで表示させる

何が言いたいのか分からねーと思うが。要は、固定ページが何千ページもあって階層がたくさんある時に、一覧からページを探すのがしんどいので、親ページを指定してその子ページだけを表示させる、というものを実現したい。

やりたいこと

絞り込み検索に、こんなセレクトボックス↓を追加して、選択した親ページの子ページのみを表示させたい。

セレクトボックスには、「子を持つページ」のみが表示される。自分自身が子ページであったとしても、さらに自分が子ページを持っているならば表示される。

「テスト親」という名前のページを選択してみた。このように、選択したページの子ページに絞られて表示される。素晴らしい!←

ちなみに、孫ページは表示されない。あくまで子ページ(直近の子孫)に絞り込まれる。そうじゃないとあんま意味ない。

完成コード

以下を functions.php にコピペするだけでとりあえず使える。

<?php
class WP_Filter_To_Children {

  private $post_type; //管理画面の一覧の投稿タイプ
  private $key = 'child_of'; // $_GETに指定する、親ページのIDのキー

  public function __construct() {
    $this->set_post_type();

    add_action('restrict_manage_posts', array($this, 'show_selectbox'));
    add_filter('posts_where', array($this, 'posts_where'));
  }

  // 投稿タイプを取得
  private function set_post_type() {
    if(isset($_GET) && isset($_GET['post_type'])) {
      $this->post_type = $_GET['post_type'];
    }
  }

  // 子を持つページオブジェクトのみを返す
  private function get_parents() {
    // 全件取得
    $args = array(
      'numberposts' => -1,
      'post_type' => $this->post_type,
      'sort_order' => 'ASC',
      'sort_column' => 'menu_order'
    );
    $all_posts = get_pages($args);

    if($all_posts) {
      // 親(子どもがいる人)リスト
      $parents = [];

      // 子どもがいるなら親リストに追加
      foreach($all_posts as $post) {
        if($this->has_children($post->ID)) {
          $parents[] = $post;
        }
      }

      return $parents;
    }
  }

  // 子どもがいれば$idを返す
  private function has_children($id) {
    $args = array(
      'post_type' => $this->post_type,
      'child_of' => $id
    );
    $posts = get_pages($args);

    return $posts ? $id : false;
  }

  // 親ページのセレクトボックス
  public function show_selectbox() {
    $posts = $this->get_parents();

    if($posts) {
      $html = '<select name="child_of" id="parent" class="postform" title="選択したページの子ページ一覧を表示します">';
      $html .= '<option value="" disabled>子ページ一覧を表示</option>';
      $html .= '<option value="">すべて表示</option>';
      $html .= sprintf('<option value="0"%s>一番上の階層のみ</option>', selected($_GET[$this->key], 0, false));

      foreach($posts as $post) {
        $depth = count(get_post_ancestors($post->ID));
        $prefix = sprintf('%s ', str_repeat('−', $depth));
        $checked = selected($_GET[$this->key], $post->ID, false);
        $html .= sprintf('<option value="%d"%s>%s%s</option>', $post->ID, $checked, $prefix, $post->post_title);
      }

      $html .= '</select>';

      echo $html;
    }
  }

  // 問い合わせクエリに親ページIDを追加
  public function posts_where($where) {
    if(is_admin()) {
      global $wpdb;

      if(isset($_GET) && isset($_GET[$this->key])) {
        $value = $_GET[$this->key];

        if($value !== '') {
          $where .= " AND {$wpdb->posts}.post_parent='{$_GET[$this->key]}'";
        }
      }
    }
    return $where;
  }
}

$wp_filter_to_children = new WP_Filter_To_Children();

解説

↓を魔改造させていただいた。

上記記事の、親ページIDを静的に記述したり、深い階層の親ページを取得できなかったりするあたりを解消。

魔改造ポイント

  • 固定ページと、階層をもつカスタム投稿タイプすべてで使える
  • 子ページをもつ全ての親ページを自動的に取得しセレクトボックスに表示
  • 階層がめっちゃ深くても取得できる
  • 絞り込み検索した時にセレクトボックスが選択状態になるように
  • 一番上の階層のみの表示もできるように

コードについての解説

「子をもつすべてのページ」オブジェクトを取得し、その一覧をセレクトボックスの選択肢として、選択された親ページのIDをもとに子ページに絞り込んで表示するというもの。

まず悩ましいのは、has_children() みたいな子どもがいるかチェックする関数がないこと。なので、力技で自作。

それを使って子どもを持つページのみの一覧が取得できたら、セレクトボックスを作成して、アクションフック restrict_manage_posts に引っかける。これでGETパラメータに選択したページのIDを送ることができるようになった。

ちなみに、セレクトボックスのページタイトルで階層を表現しているが、これは get_post_ancestors で先祖の数を取得して、その数だけダッシュ記号を反復表示させている。

あとは、そのGETパラメータから取得したIDをもとに、MySQLの問い合わせ文を変更し絞り込めばOK。

問い合わせ分文の変更は、フィルターフック posts_where に引っかける。以上。

暇があったらプラグイン化するから使ってね!

参考文献