かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Wordpress 記事を属性 順序( menu_order ) の順番に並べたい 順序が同じものがあっても大丈夫版

以前にWordPressの投稿をmenu_orderの順番に並べて表示させる方法を書いていました。

一覧表示・管理画面の一覧をmenu_order順にするまでは良かったのですが、個別ページ(single.php)などで前後の投稿へのリンク(previous_post_link, next_post_link)の表示がmenu_orderに同じ数を指定してるものがあると、上手く表示されないバグが有りました。

前後の投稿へのリンク 同じ順序の番号があった時は、投稿日時順で取得する。

一覧画面と同じように同じ順序(menu_order)の番号があった時は、投稿日時(post_date)の古い順で取得するように修正したいと思います。

以下のように取得できるようにすれば意図したとおりに表示されるように思います。

前のページの場合
同じ順序(`menu_order`)かつ投稿日時('post_date')が今の投稿より前の投稿があればこの投稿を
無ければ順序(`menu_order`)が前の投稿を返すようにする
前の投稿も同じ順序(`menu_order`)の投稿が複数ある可能性があるので、
投稿日時を降順にして1件取得する
後のページの場合
同じ順序(`menu_order`)かつ投稿日時('post_date')が今の投稿より後の投稿があればこの投稿を
無ければ順序(`menu_order`)が後の投稿を返すようにする
後の投稿も同じ順序(`menu_order`)の投稿が複数ある可能性があるので、
投稿日時を昇順にして1件取得する

条件に合う投稿が有るかチェックする関数を作成して、それによって発行するSQLを変更するようにすれば良さそうです。

前後の投稿へのリンクを順序の指定・順序が同じ場合は投稿日時順に取得させる

まずは、bookというカスタム投稿を作成して、アーカイブ・管理画面を順序(menu_order)順に表示させます。

<?php // function.php
/**
 * カスタム投稿タイプ book を登録する。
 */
add_action( 'init', 'codex_book_init' );
function codex_book_init() {
  $labels = array(/* 省略 */)
  $args = array(
    'labels'             => $labels,
    'public'             => true,
    'publicly_queryable' => true,
    'show_ui'            => true,
    'show_in_menu'       => true,
    'query_var'          => true,
    'rewrite'            => array( 'slug' => 'book' ),
    'capability_type'    => 'post',
    'has_archive'        => true,
    'hierarchical'       => false,
    'menu_position'      => null,
    'supports'           => array(
      'title',
      'editor',
      'author',
      'thumbnail',
      'page-attributes', // 属性(順序)を表示する
    )
  );
  register_post_type( 'book', $args );
}

/**
 *  管理画面一覧・アーカイブページを menu_order 順に表示させる
 */
// admin & Archive sort by menu_order
function my_posts_per_page( $wp_query ) {
  if( $wp_query->is_post_type_archive('book')
    && post_type_supports( $wp_query->query_vars['post_type'], 'page-attributes' ) ) {
    if( !isset( $wp_query->query_vars['orderby'] ) ) {
      // orderby が明示的に指定されていなければ、順序(menu_order)で並び替える
      $wp_query->query_vars['orderby'] = 'menu_order';
    }
    if ( !isset( $wp_query->query_vars['order'] ) ) {
      // order が明示的に指定されていなければ、順序(menu_order)の数字が小さい方から並べる為にASCを指定
      $wp_query->query_vars['order'] = 'ASC';
    }
  }
}
add_action( 'pre_get_posts', 'my_posts_per_page' );

/**
 *  管理画面の一覧に順序を表示させる
 */
function manage_book_columns($columns) {
  $columns['menu_order'] = '順序';
  return $columns;
}
add_filter('manage_edit-book_columns', 'manage_book_columns');

function add_custom_column($column_name, $post_id) {
  if($column_name === 'menu_order') {
    $post = get_post($post_id);
    $column = $post->menu_order;
  }
  if(isset($column)) {
    echo $column;
  } else {
    echo __('None');
  }
}
add_action('manage_posts_custom_column', 'add_custom_column', 10, 2);

function.phpを保存して管理画面にアクセスするとBooksとうカスタム投稿があるので、Booksの投稿を作成していきます。
この時わざと順序に同じ値を入れた投稿をいくつか作成します。こんな感じで投稿を作成しました。

タイトル 順序 投稿日時
book1 0 2015-09-15 16:00
book2 0 2015-09-15 16:05
book3 3 2015-09-15 16:10
book4 2 2015-09-15 16:15
book5 2 2015-09-15 16:20
book6 1 2015-09-15 16:25
book7 2 2015-09-15 16:30

管理画面とbookのアーカイブページでは下記のように順序の指定順で、順序が同じ場合は投稿の古い順に表示されていると思います。

タイトル 順序 投稿日時
book1 0 2015-09-15 16:00
book2 0 2015-09-15 16:05
book4 2 2015-09-15 16:15
book5 2 2015-09-15 16:20
book7 2 2015-09-15 16:30
book6 1 2015-09-15 16:25
book3 3 2015-09-15 16:10

この状態で例えば book7 のSingleページを表示させても前後の投稿へのリンクは投稿順になったままです。
前後の投稿へのリンク(previous_post_link, next_post_link)の表示をfilterを使って変更させます。

<?php // function.php
/**
 *  同じ menu_order の投稿が有るかチェックする関数
 *    @param $post
 *    @param $is_previous: Boolean true ... 前の投稿 / false ... 後の投稿
 */
function is_same_menu_order_post($post, $is_previous = true) {
  global $wpdb;
  if(!$post) {
    return false;
  }
  $sql = $wpdb->prepare("SELECT count(*) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = %s AND menu_order = %s", $post->post_type, $post->menu_order);
  if( $is_previous ) {
    // 前の投稿 ... menu_order が同じ かつ 投稿日が前のもの
    $sql .= $wpdb->prepare(" AND post_date < %s", $post->post_date);
  } else {
    // 後の投稿 ... menu_order が同じ かつ 投稿日が後のもの
    $sql .= $wpdb->prepare(" AND post_date > %s", $post->post_date);
  }
  $num = $wpdb->get_var($sql);
  if($num) {
    return true;
  } else {
    return false;
  }
}

/**
 *  前後の投稿へのリンクを取得するためのSQLのWHERE節を作成する
 */
// previous
function my_previous_post_where($where, $in_same_term, $excluded_terms) {
  global $post, $wpdb;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
    // 同じmenu_orderかつ投稿日が前の投稿の有無をチェック
    if( is_same_menu_order_post($post, true) ) {
      // 同じmenu_orderで投稿日が前の投稿が有る場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order <= %s AND p.post_date < %s $posts_in_ex_terms_sql", 
        $post->post_type,
        $post->menu_order,
        $post->post_date);
    } else {
      // 同じmenu_orderの投稿はない
      // または、同じmenu_orderでも投稿日が今の$postより前のものは無い場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order < %s $posts_in_ex_terms_sql",
        $post->post_type,
        $post->menu_order);
    }
  }
  // 対象でない場合もreturnが必要
  return $where;
}
// next
function my_next_post_where($where, $in_same_term, $excluded_terms) {
  global $post, $wpdb;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
    // 同じmenu_orderかつ投稿日が後の投稿の有無をチェック
    if( is_same_menu_order_post($post, false) ) {
      // 同じmenu_orderで投稿日が後の投稿が有る場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order >= %s AND p.post_date > %s $posts_in_ex_terms_sql",
        $post->post_type,
        $post->menu_order,
        $post->post_date);
    } else {
      // 同じmenu_orderの投稿はない
      // または、同じmenu_orderでも投稿日が今の$postより後のものは無い場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order > %s $posts_in_ex_terms_sql",
        $post->post_type,
        $post->menu_order);
    }
  }
  // 対象でない場合もreturnが必要
  return $where;
}
add_filter('get_previous_post_where', 'my_previous_post_where', 10, 3);
add_filter('get_next_post_where', 'my_next_post_where', 10, 3);

/**
 *  前後の投稿へのリンクを取得するためのSQLのORDER節とLIMIT節を作成する
 */
// previous
function my_previous_post_sort($sort) {
  global $post;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
   // 順序(menu_order) 大きい順、投稿日(post_date) 新しい順 に並べて1件取得
   return 'ORDER BY p.menu_order DESC, p.post_date DESC LIMIT 1';
  }
  // 対象でない場合もreturnが必要
  return $sort;
}
// next
function my_next_post_sort($sort) {
  global $post;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
    // 順序(menu_order) 小さい順、投稿日(post_date) 古い順 に並べて1件取得
    return 'ORDER BY p.menu_order ASC, p.post_date ASC LIMIT 1';
  }
  // 対象でない場合もreturnが必要
  return $sort;
}
add_filter('get_previous_post_sort', 'my_previous_post_sort');
add_filter('get_next_post_sort', 'my_next_post_sort');

function.phpを保存して、Bookの各Singleページにアクセスすると前後の投稿へのリンクが管理画面の一覧・アーカイブページの表示順になっていると思います。

順序がかぶっていた場合は投稿が新しい順に表示させたい場合

まず、管理画面一覧・アーカイブページを menu_order 順に表示させる の部分でorderを複数の指定ができるようにしなければなりません。詳しくはこちらの記事を参考にしてみてください。

そして、前後の投稿の取得の処理はpost_dateの扱いをWHERE節では不等号を逆にして、ORDER節ではDESCとASCを逆にすればOKです。

<?php // function.php
/**
 *  管理画面一覧・アーカイブページを menu_order 順に表示させる
 */
// admin & Archive sort by menu_order
function my_posts_per_page( $wp_query ) {
  if( $wp_query->is_post_type_archive('book')
    && post_type_supports( $wp_query->query_vars['post_type'], 'page-attributes' ) ) {
    // 複数指定できるように変更
    $wp_query->set('orderby', 'menu_order date');
    $wp_query->set('order', 'ASC');
    $wp_query->set('my_orders', 'ASC DESC');
  }
}
add_action( 'pre_get_posts', 'my_posts_per_page' );

// 一覧でORDER BY節を変更するフィルターを追加
function change_posts_orderby($orderby, $query) {
  // my_orders をスペース区切りで配列に変換する。 ※ my_orders が無ければ値が0個の配列になる
  $orders = array_filter( explode(' ', strtoupper( $query->get('my_orders') )) );
  // my_orders がある時だけORDER BY節を変更する
  if( count($orders) > 0 ) {
    $orderby_arg = array();
    $order_arg = array('DESC', 'ASC');
    // $orderby文のDESC, ASC を削除して , で分割
    foreach( explode(',', str_replace($order_arg, '', $orderby)) as $i => $the_orderby ) {
      if( isset($orders[$i]) && in_array($orders[$i], $order_arg) ) {
        // 対応する order がある時
        $orderby_arg[] = trim($the_orderby) . ' ' . $orders[$i];
      } else {
        // 対応する order が無い あるいは DESC, ASCでない場合は デフォルト値 DESC を使う
        $orderby_arg[] = trim($the_orderby) . ' DESC';
      }
    }
    // それぞれの orderby の入った配列を , で連結した文字列にする
    $orderby = implode(',', $orderby_arg);
  }
  return $orderby;
}

/**
 *  同じ menu_order の投稿が有るかチェックする関数
 *    @param $post
 *    @param $is_previous: Boolean true ... 前の投稿 / false ... 後の投稿
 */
function is_same_menu_order_post($post, $is_previous = true) {
  global $wpdb;
  if(!$post) {
    return false;
  }
  $sql = $wpdb->prepare("SELECT count(*) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = %s AND menu_order = %s", $post->post_type, $post->menu_order);
  if( $is_previous ) {
    // 前の投稿 ... menu_order が同じ かつ 投稿日が "後" のもの
    $sql .= $wpdb->prepare(" AND post_date > %s", $post->post_date);  // post_date の比較を逆に修正
  } else {
    // 後の投稿 ... menu_order が同じ かつ 投稿日が "前" のもの
    $sql .= $wpdb->prepare(" AND post_date < %s", $post->post_date);  // post_date の比較を逆に修正
  }
  $num = $wpdb->get_var($sql);
  if($num) {
    return true;
  } else {
    return false;
  }
}

/**
 *  前後の投稿へのリンクを取得するためのSQLのWHERE節を作成する
 */
// previous
function my_previous_post_where($where, $in_same_term, $excluded_terms) {
  global $post, $wpdb;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
    // 同じmenu_orderかつ投稿日が "後" の投稿の有無をチェック
    if( is_same_menu_order_post($post, true) ) {
      // 同じmenu_orderで投稿日が "後" の投稿が有る場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order <= %s AND p.post_date > %s $posts_in_ex_terms_sql", 
        $post->post_type,
        $post->menu_order,
        $post->post_date);
    } else {
      // 同じmenu_orderの投稿はない
      // または、同じmenu_orderでも投稿日が今の$postより "後" のものは無い場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order < %s $posts_in_ex_terms_sql",
        $post->post_type,
        $post->menu_order);
    }
  }
  // 対象でない場合もreturnが必要
  return $where;
}
// next
function my_next_post_where($where, $in_same_term, $excluded_terms) {
  global $post, $wpdb;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
    // 同じmenu_orderかつ投稿日が "前" の投稿の有無をチェック
    if( is_same_menu_order_post($post, false) ) {
      // 同じmenu_orderで投稿日が "前" の投稿が有る場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order >= %s AND p.post_date < %s $posts_in_ex_terms_sql",
        $post->post_type,
        $post->menu_order,
        $post->post_date);
    } else {
      // 同じmenu_orderの投稿はない
      // または、同じmenu_orderでも投稿日が今の$postより "前" のものは無い場合のWHERE節
      return $wpdb->prepare(
        "WHERE p.post_type = %s AND p.post_status = 'publish' AND p.menu_order > %s $posts_in_ex_terms_sql",
        $post->post_type,
        $post->menu_order);
    }
  }
  // 対象でない場合もreturnが必要
  return $where;
}
add_filter('get_previous_post_where', 'my_previous_post_where', 10, 3);
add_filter('get_next_post_where', 'my_next_post_where', 10, 3);

/**
 *  前後の投稿へのリンクを取得するためのSQLのORDER節とLIMIT節を作成する
 */
// previous
function my_previous_post_sort($sort) {
  global $post;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
   // 順序(menu_order) 大きい順、投稿日(post_date) 古い順 に並べて1件取得
   return 'ORDER BY p.menu_order DESC, p.post_date ASC LIMIT 1';
  }
  // 対象でない場合もreturnが必要
  return $sort;
}
// next
function my_next_post_sort($sort) {
  global $post;
  $post_type = get_post_type($post);
  if($post_type == 'book') {
    // 順序(menu_order) 小さい順、投稿日(post_date) 新しい順 に並べて1件取得
    return 'ORDER BY p.menu_order ASC, p.post_date DESC LIMIT 1';
  }
  // 対象でない場合もreturnが必要
  return $sort;
}
add_filter('get_previous_post_sort', 'my_previous_post_sort');
add_filter('get_next_post_sort', 'my_next_post_sort');

これで、管理画面・アーカイブページ・前後の投稿のリンクが順序が同じ場合は投稿の新しい順で表示させることができました。

感想

都度同じ同じ順序の投稿が有るかチェックしているので、もっと良いSQL文の作り方がありそうな気がしています。処理がもっと簡略にできそうな気も。SQLに詳しい方が居られましたら何卒、教えてください!

[参考]

サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル

サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル


↓ 可処分所得がないので、ご支援いただけると嬉しです꒰✩'ω`ૢ✩꒱ なにとぞ〜!
TALVISOTA vol.1 - COMIC ZIN 通信販売
第一水雷戦隊 軍艦巻き てぬぐい - とらのあな