ACFの柔軟コンテンツで改ページを実現する方法

WordPressの標準機能では、投稿本文に <!--nextpage--> を入れることで記事を複数ページに分割できます。ところが、ACFの柔軟コンテンツ(Flexible Content)で本文を組んでいる場合、この仕組みは使えません。

今回は、ACFの柔軟コンテンツに「改ページ」というレイアウトを用意し、改ページごとにHTMLを配列へ保存してから出力することで、長い記事を複数ページに分割する方法を紹介します。

なぜ標準の改ページが使えないのか

標準の <!--nextpage--> は、post_content(投稿本文)の文字列を分割して動いています。柔軟コンテンツの中身は本文ではなくカスタムフィールドに保存されているため、the_content() や wp_link_pages() のページ分割には一切引っかかりません。

そこで、「分割は自分で行い、ページ送りも自分で作る」方針にします。

全体の仕組み

やることは大きく3つです。

  1. 「改ページ」レイアウトを作る:柔軟コンテンツに、中身のない目印用のレイアウトを追加します。
  2. 改ページごとに配列へ保存する:柔軟コンテンツをそのまま出力せず、行ごとのHTMLを $html .= で文字列として連結していきます。改ページに当たったら、そこまでのHTMLを配列に入れて、次のページに切り替えます。
  3. 表示するページだけ出力する:URLのページ番号に対応する配列の要素を echo して、ページ送りを付けます。
柔軟コンテンツ(contents) 改ページごとのHTML($pages) テキスト 画像 ── 改ページ ── テキスト テキスト ── 改ページ ── 画像 改ページで分割 $pages[0](1ページ目のHTML) テキスト 画像 $pages[1](2ページ目のHTML) テキスト テキスト $pages[2](3ページ目のHTML) 画像

改ページのレイアウト自体は出力せず、「区切り」としてだけ使います

① 「改ページ」レイアウトを作る

柔軟コンテンツはACF PRO の機能です。すでに本文用のフィールド(ここでは contents とします)がある前提で、レイアウトを1つ追加します。

  1. フィールドグループを開く:柔軟コンテンツのフィールドがあるフィールドグループを編集します。
  2. 「レイアウトを追加」を押す:ラベルを「改ページ」、名前を pagebreak にします。
  3. サブフィールドは作らない:目印として使うだけなので、中身は不要です。編集画面で何のブロックか分かるように、「メッセージ」フィールドに「ここでページが分かれます」と書いておくと親切です。

メッセージフィールドは値を保存しないため、データには影響しません。

② 改ページごとに配列へ保存する

柔軟コンテンツを have_rows() でループしながら、レイアウトごとのHTMLを $html に連結していきます。pagebreak に当たったら、それまでの $html を $pages に入れて、$html を空に戻します。

functions.php に次の関数を追加します。ここではテキストと画像の2つのレイアウトを例にしています。

/**
 * 柔軟コンテンツを改ページ単位のHTML配列にする
 *
 * @param string $field_name 柔軟コンテンツのフィールド名
 * @param int|false $post_id 投稿ID(省略時は現在の投稿)
 * @return array ページごとのHTML文字列の配列
 */
function get_flexible_pages( $field_name = 'contents', $post_id = false ) {
	$pages = array();
	$html  = '';

	if ( have_rows( $field_name, $post_id ) ) {
		while ( have_rows( $field_name, $post_id ) ) {
			the_row();

			switch ( get_row_layout() ) {

				case 'pagebreak':
					$pages[] = $html; // ここまでを1ページとして確定
					$html      = '';
					break;

				case 'text':
					$html .= '<div class="flex-text">';
					$html .= wp_kses_post( get_sub_field( 'text' ) );
					$html .= '</div>';
					break;

				case 'image':
					$image_id = get_sub_field( 'image' ); // 戻り値は「画像ID」
					if ( $image_id ) {
						$html .= '<figure class="flex-image">';
						$html .= wp_get_attachment_image( $image_id, 'large' );
						$html .= '</figure>';
					}
					break;
			}
		}
	}
	$pages[] = $html; // 最後のページ

	// 先頭や連続した改ページで空になったページを除外して詰め直す
	return array_values( array_filter( $pages, 'strlen' ) );
}

ポイントは次の3つです。

  • pagebreak の case で「配列に入れて、$html を空にする」だけで分割できます。他のレイアウトは普通に出力処理を書くだけで、改ページを意識する必要はありません。
  • エスケープは $html に連結する時点で済ませています(wp_kses_post() など)。そのため、出力時はそのまま echo できます。
  • 最後の array_filter() で空のページを取り除いているため、改ページを置き間違えて連続させても空白ページは生まれません。

レイアウトが増えたら、case を足していくだけです。

③ 表示するページだけ出力する

現在のページ番号を取得する

投稿の2ページ目以降のURL(/記事スラッグ/2/ など)は、WordPressが page というクエリ変数として解釈してくれます。paged ではなく page である点に注意してください(paged はアーカイブ用です)。

<?php
$pages = get_flexible_pages( 'contents' );
$total = count( $pages );
$paged = max( 1, (int) get_query_var( 'page' ) );

// 存在しないページ番号は404にする
if ( $total > 0 && $paged > $total ) {
	global $wp_query;
	$wp_query->set_404();
	status_header( 404 );
	nocache_headers();
	get_template_part( '404' );
	exit;
}

get_header();
?>

該当ページのHTMLを出力する

配列は0始まりなので、$paged - 1 番目を出力します。

<?php if ( $total ) : ?>
	<div class="post-body">
		<?php echo $pages[ $paged - 1 ]; ?>
	</div>
<?php endif; ?>

ページ送りを作る

wp_link_pages() は本文の分割数を元に動くので、柔軟コンテンツでは使えません。ページ送りは自前で作ります。

<?php if ( $total > 1 ) : ?>
	<nav class="pager" aria-label="ページ送り">
		<?php for ( $i = 1; $i <= $total; $i++ ) :
			$url = ( 1 === $i )
				? get_permalink()
				: trailingslashit( get_permalink() ) . $i . '/';
		?>
			<?php if ( $i === $paged ) : ?>
				<span class="pager__item is-current" aria-current="page"><?php echo $i; ?></span>
			<?php else : ?>
				<a class="pager__item" href="<?php echo esc_url( $url ); ?>"><?php echo $i; ?></a>
			<?php endif; ?>
		<?php endfor; ?>
	</nav>
<?php endif; ?>

「前へ」「次へ」のリンクが欲しい場合は、$paged - 1 と $paged + 1 のURLを同じ要領で組み立てれば作れます。

運用で気をつけたいこと

表示しないページ分のHTMLも生成される

この方式は、2ページ目を開いたときも1ページ目・3ページ目のHTMLを組み立てています(使わずに捨てることになります)。通常の記事の量であれば処理時間はごくわずかで、体感できる差は出ません。ただ、レイアウトの中に重い処理(外部データの取得や、関連記事のクエリなど)を入れる場合は、頭の片隅に置いておいてください。

パーマリンク設定

上のページ送りは /記事スラッグ/2/ という形のURLを前提にしています。パーマリンクが「基本」(?p=123)の場合は、add_query_arg( 'page', $i, get_permalink() ) のようにクエリ形式で組み立てる必要があります。

2ページ目以降が正しく表示されるか確認する

投稿の2ページ目以降のURLは、本文に <!--nextpage--> がなくても表示されるはずですが、テーマやプラグイン(キャノニカル制御やリダイレクト系)によっては1ページ目へ飛ばされることがあります。実装後は、実際に /記事スラッグ/2/ を開いて、次の3点を確認してください。

  • 2ページ目が正しく表示される(1ページ目にリダイレクトされない)
  • 存在しないページ番号(/記事スラッグ/99/ など)が404になる
  • 1ページ目に /記事スラッグ/1/ でアクセスしたときの挙動が意図どおりか

SEO面の注意

分割したページはそれぞれ別URLになります。2ページ目以降も title やキャノニカルが1ページ目と同じだと、重複と見なされる可能性があります。2ページ目以降のタイトルに「(2ページ目)」を付ける、キャノニカルを自ページのURLにする、といった調整を検討してください。そもそも分割する必要があるほど長い記事かどうか、というのも最初に考えておきたい点です。

まとめ

手順やること場所
① レイアウト作成中身のない「改ページ」レイアウトを追加ACFのフィールドグループ
② 配列に保存行ごとのHTMLを $html .= で連結し、pagebreak で配列に入れるfunctions.php
③ 出力page クエリ変数のページだけ echo し、ページ送りを付けるテンプレート(single.php など)

柔軟コンテンツを「そのまま出力せず、いったん配列に溜めてから出力する」という考え方さえ押さえておけば、改ページ以外にも応用できます。たとえば、見出しレイアウトだけを集めて目次を作る、といったこともできるので、柔軟コンテンツで記事を組んでいる方はぜひ試してみてください。