クエリループをショートコードで呼び出す方法|ページビルダー環境でカスタムフィールドまで表示する

クエリループをショートコードで呼び出す方法|ページビルダー環境でカスタムフィールドまで表示する

あなたのサイト、こんなことになっていませんか?

まめすけ

クエリループが便利そうなのに、うちのページビルダーだと置く場所がない……

私はフリーランスでWordPressサイトの制作と保守を請け負っています。あるクライアントから「実績ページの見せ方を、参考サイトのように変えられないか」と相談を受けました。

話はその時点で止まったままなのですが、自分の技術でどこまで近づけられるのかを確かめたくて、テスト環境で実装してみました。この記事はその検証記録です。

結論から言うと、実装できました。ただし3回、方針を転換しています。うまくいかなかった経緯まで含めて残します。

完成イメージ

目次

何を作ったのか?

カスタムフィールドの内容を表示する、カード型の実績一覧です。

仕様は以下のとおりです。

  • 1件につき写真を複数枚登録できるが、一覧では1枚目だけ表示
  • 写真をクリックすると、その案件の写真だけがLightboxで見られる
  • 工事名称・施工年・施工概要を、ラベル付きの行で表示
  • 「公共工事」「民間工事」など、セクションごとに分けて配置できる

使ったプラグインは Custom Post Type UI と Lightbox の2つだけです。ACFは使っていません。


なぜ普通の方法では作れないのか?

壁が2つあるからです。置けないうえに、置けても足りません。

ページビルダーにはクエリループを置けない

このサイトは Themify Builder で構築されています。ページの中身はビルダー独自のモジュールで組み立てる仕組みなので、Gutenbergのクエリループブロックを直接置けません

これはThemifyに限った話ではありません。Elementor、WPBakery、Diviなど、独自ビルダーを使うテーマ全般で起きる問題です。

クエリループはカスタムフィールドを表示できない

もう一つの壁がこちらです。クエリループが出せるのは、タイトル・日付・抜粋・アイキャッチといった標準の項目だけという仕様になっています。

カスタムフィールドを出したければ、通常はブロックを自作するか、ACFのようなプラグインに頼ることになります。


どこで方針を変えたのか?(3回の試行錯誤)

最初の構想から、3回作り直しています。

転換①|抜粋と公開日では項目が足りなかった

最初はプラグインなしのシンプル構成を狙いました。

  • 施工年 → 公開日を施工年月として使う
  • 説明文 → 抜粋を使う
  • 写真 → 本文のギャラリーブロックをそのまま出す

標準機能だけで完結するので、更新にも強い。悪くない発想だと思っていました。

しかし実際に項目を並べてみると足りません。工事名称は投稿タイトルと別に必要ですし、施工年は「令和6年度」のような和暦の年度表記にしたい。公開日を使うと西暦の日付になってしまいます。

表示したい項目が3つある時点で、標準フィールドの流用には無理がありました。

そこでカスタムフィールドに切り替えました。ただしACFは入れません。WordPress標準のカスタムフィールド機能を使います。

works_name  … 工事正式名称
works_year  … 施工年度
works_desc  … 施工概要

標準のカスタムフィールド欄は、初期状態では表示されていません。投稿編集画面の右上「⋮」→ 設定 → 一般 → 高度な設定「カスタムフィールド」をONにしてください。

なお、この設定はユーザーごとです。クライアントが更新する場合は、その方の環境でもONにする必要があります。

カスタムフィールド入力画面のイメージ

転換②|本文の段落では表示を制御できなかった

説明文は当初、本文に書く想定でした。

投稿画面を開いた時点でギャラリーと段落が用意されるよう、テンプレートを設定していました。

$post_type_object->template = array(
	array( 'core/gallery', array( 'linkTo' => 'media' ) ),
	array( 'core/paragraph', array( 'placeholder' => '補足説明があれば入力...' ) ),
);

しかしこれだと、一覧ページでの扱いが難しくなります。本文の中にギャラリーと段落が混在しているため、「写真だけ取り出す」「説明文だけ別の場所に置く」といった制御ができません。

結局、説明文も works_desc としてカスタムフィールドに移しました。本文はギャラリー専用の場所になりました。

転換③|投稿タイプ判定では動かなかった

ここが一番ハマった部分です。

カスタムフィールドを表示させるために render_block フィルターを使ったのですが、このフィルターはサイト全体の全ブロックに効いてしまいます。関係ないページの段落まで書き換わる恐れがありました。

そこで最初は、投稿タイプで判定しようとしました。

// これは動きませんでした
if ( 'works' !== get_post_type() ) {
	return $block_content;
}

動きません。 理由は明確で、このショートコードが実行されるのは固定ページの上だからです。get_post_type() が返すのは page であって works ではありません。

クエリループの内側では投稿が切り替わりますが、フィルターの発火タイミングと噛み合わず、安定しませんでした。

最終的にたどり着いた解決策は後述します。「場所で判定する」から「時間で判定する」への発想の転換でした。


クエリループをページに置くにはどうする?

パターンとして保存し、ショートコードで呼び出します。

パターンとして保存する

投稿編集画面でクエリループを組み立て、パターンとして保存します。ブロックを選択して「⋮」→「パターンを作成」で保存できます。

クラシックテーマの場合、管理画面に「パターン」のメニューが表示されません。以下のURLで直接開けます。

/wp-admin/edit.php?post_type=wp_block

保存したパターンのIDは、編集画面のURLに含まれる post=○○○○ の数字です。

パターンのスクショ

呼び出し用のショートコードを作る

子テーマの functions.php に追記します。

/**
 * パターン(再利用ブロック)をショートコードで呼び出す
 */
function k_suke_display_reusable_block( $atts ) {
	$atts = shortcode_atts( array( 'id' => '' ), $atts );
	if ( empty( $atts['id'] ) ) {
		return '';
	}

	$post = get_post( $atts['id'] );
	if ( ! $post || 'wp_block' !== $post->post_type ) {
		return '';
	}

	// 実績リストを描画中であることを示すフラグを立てる
	$GLOBALS['is_works_loop_active'] = true;

	// ブロックをHTMLに変換
	$content = do_blocks( $post->post_content );
	$content = do_shortcode( $content );

	// フラグを解除
	$GLOBALS['is_works_loop_active'] = false;

	return $content;
}
add_shortcode( 'my_block', 'k_suke_display_reusable_block' );

肝は do_blocks() です。ブロックのデータをHTMLに変換する関数で、これを通すことでパターンの中身が実際の表示になります。

$GLOBALS の2行が、転換③で行き着いた解決策です。詳しくは次の章で説明します。

ページビルダーに貼り付ける

Themify Builder の場合、「HTML / Text / Shortcode」モジュールを置いて、以下を記述します。

[my_block id="3330"]

これだけです。ビルダーの好きな位置に置けるので、セクション見出しの下に配置したり、複数のパターンを使い分けたりできます。

私の場合は「公共電気設備工事」と「民間電気設備工事」でパターンを分け、同じページに2つ置いています。

ショートコード呼び出しのモジュールスクショ
まめすけ

ショートコードにしちゃえば、どんなビルダーでも置けるってことだね


カスタムフィールドはどうやって表示させる?

段落ブロックを「空の器」として置き、中身をPHPで差し替えます。

段落ブロックを「空の器」として使う

パターン側では、値を表示したい位置に段落ブロックを置き、追加CSSクラスを付けておきます。

クラス対応するフィールド
display-works-nameworks_name
display-works-yearworks_year
display-works-descworks_desc

段落の中身は「工事正式名称」のようなダミーテキストで構いません。フロントでは表示されず、実際の値に置き換わります。

フラグで実行区間を限定する

転換③の解決策がこれです。ショートコードの実行中だけフラグを立てます。

$GLOBALS['is_works_loop_active'] = true;
$content = do_blocks( $post->post_content );
$GLOBALS['is_works_loop_active'] = false;

フィルター側では、このフラグだけを見ます。

if ( empty( $GLOBALS['is_works_loop_active'] ) ) {
	return $block_content;
}

「どのページか」ではなく「今このショートコードを描画中か」で判定する。 これなら固定ページだろうとどこだろうと確実に効きますし、それ以外の場所には一切影響しません。

実装コード全文

/**
 * ブロック出力時にカスタムフィールドを差し込む
 */
function k_suke_render_block_filters( $block_content, $block ) {

	// ショートコード描画中でなければ何もしない
	if ( empty( $GLOBALS['is_works_loop_active'] ) ) {
		return $block_content;
	}

	// --- A. カスタムフィールドの埋め込み ---
	if ( 'core/paragraph' === $block['blockName'] && isset( $block['attrs']['className'] ) ) {
		$classes    = $block['attrs']['className'];
		$post_id    = get_the_ID();
		$field_name = '';

		if ( strpos( $classes, 'display-works-name' ) !== false ) {
			$field_name = 'works_name';
		} elseif ( strpos( $classes, 'display-works-year' ) !== false ) {
			$field_name = 'works_year';
		} elseif ( strpos( $classes, 'display-works-desc' ) !== false ) {
			$field_name = 'works_desc';
		}

		if ( $field_name ) {
			$val = get_post_meta( $post_id, $field_name, true );
			if ( ! empty( $val ) ) {
				$display_val = ( 'works_desc' === $field_name )
					? nl2br( esc_html( $val ) )
					: esc_html( $val );
				return preg_replace( '/(<p[^>]*>)(.*?)(<\/p>)/is', '$1' . $display_val . '$3', $block_content );
			}
			return '';
		}
	}

	// --- B. 画像のLightboxグループ分け ---
	if ( 'core/image' === $block['blockName'] ) {
		$post_id     = get_the_ID();
		$pattern     = '/<a(.*?)href="([^"]*?\.(?:jpg|jpeg|gif|png|webp))"(.*?)>/i';
		$replacement = '<a$1href="$2" data-rel="lightbox-gallery-' . $post_id . '"$3>';
		return preg_replace( $pattern, $replacement, $block_content );
	}

	return $block_content;
}
add_filter( 'render_block', 'k_suke_render_block_filters', 10, 2 );

ポイントを3つ挙げます。

  • <p> タグの中身だけを置換しているので、段落に設定した文字色や余白はそのまま残ります
  • 値が空なら return ''その行ごと消えます。未入力の項目が空欄で残りません
  • 説明文だけ nl2br() を通しているので、改行がそのまま反映されます

Lightboxが全部つながってしまうのはどう防ぐ?

投稿IDをグループ名に埋め込みます。

Lightboxは通常、ページ内の画像をひとまとまりとして扱います。そのままだとA案件の写真を見ていて次に進むと、B案件の写真が出てくることになります。

これを防ぐのが、先ほどのコードの後半です。

$replacement = '<a$1href="$2" data-rel="lightbox-gallery-' . $post_id . '"$3>';

投稿ごとに異なるグループ名になるため、案件ごとに独立したギャラリーとして動作します。

前提として、**ギャラリー画像のリンク先が「メディアファイル」**になっている必要があります。投稿テンプレートで 'linkTo' => 'media' を指定しておくと、新規投稿時に自動で設定されます。

function k_suke_works_template() {
	$post_type_object = get_post_type_object( 'works' );
	if ( $post_type_object ) {
		$post_type_object->template = array(
			array( 'core/gallery', array( 'linkTo' => 'media' ) ),
		);
	}
}
add_action( 'init', 'k_suke_works_template' );

一覧に写真が全部並んでしまうのはどう防ぐ?

CSSで2枚目以降を隠します。

クエリループはギャラリーブロックの中身をそのまま出力するので、登録した写真が全部並びます。1件につき10枚あれば、10枚とも表示されてしまいます。

/* ギャラリーの2枚目以降を隠す */
.works-card-list .wp-block-gallery .wp-block-image:not(:first-child) {
	display: none;
}

:not(:first-child) は「1つ目以外」という指定です。これ1行で、一覧はカード型に、Lightboxでは全枚数が見られるという状態が作れます。隠しているだけで、HTMLには存在しているためです。

カード全体の見た目はこちらです。

/* カードの枠組み */
.works-card-list .wp-block-post {
	border: 1px solid #d4d4d4;
	padding: 5px;
	background-color: #fff;
	box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
	transition: transform 0.3s ease, box-shadow 0.3s ease;
	margin-bottom: 24px;
}

/* ホバーで浮き上がる */
.works-card-list .wp-block-post:hover {
	transform: translateY(-5px);
	box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
	border-color: #bbb;
}

/* 画像まわり */
.works-card-list .wp-block-post-content {
	margin: 0;
	padding: 0;
}
.works-card-list .wp-block-gallery {
	margin-bottom: 10px !important;
}
.works-card-list .wp-block-gallery .wp-block-image img {
	cursor: pointer;
	transition: opacity 0.3s;
}
.works-card-list .wp-block-gallery .wp-block-image img:hover {
	opacity: 0.8;
}

/* タイトル帯 */
.works-card-list .works-card-ttl {
	background-color: #386ab1;
	padding: 5px 5px 5px 10px;
	margin-top: 5px;
}

/* 項目行(点線区切り・最終行は線なし) */
.works-card-list .works-card-item-column {
	padding-top: 7px !important;
	padding-bottom: 7px !important;
	font-size: 0.87em;
	border-bottom: 1px dashed #b5b5b5;
}
.works-card-list .works-card-item-column:last-child {
	border-bottom: none;
}

.wp-block-post は、クエリループが各投稿を包むときに自動で付けるクラスです。ここに枠と影を当てるとカードになります

クエリループブロックの「高度な設定 → 追加CSSクラス」に works-card-list を入れるのを忘れないでください。CSSが効かない原因の大半がこれです。


つまずきやすい5つのポイント

実装するとき、この5つで詰まります。

症状原因
カスタムフィールド欄が見つからない投稿編集画面の「⋮」→ 設定 → パネル でONにする
CSSがまったく効かないクエリループに works-card-list を付け忘れ
フィールドの値が出ない段落ブロックに display-works-name などのクラスを付け忘れ
Lightboxが開かないギャラリー画像のリンク先が「メディアファイル」でない
関係ないページの段落が消えたフラグ判定が抜けている。is_works_loop_active の確認を

この方法は誰に向いているのか?

ページビルダーの制約下で、プラグインを増やしたくない人です。

向いているケース

  • ページビルダーを使っていて、クエリループを置けない
  • ACFを入れずにカスタムフィールドを表示したい
  • 表示項目が数個で、レイアウトを自分で決めたい

向いていないケース

  • 表示項目が10個以上ある。管理しきれなくなります
  • クライアントが自分でレイアウトも変更したい。パターンとPHPの両方を触ることになります
  • ブロック開発の知識があり、自作ブロックを作れる。そちらのほうが正攻法です

正直なところ、この実装は正攻法ではありませんpreg_replace でHTMLを書き換えているので、WordPress側の出力仕様が変われば動かなくなる可能性があります。

それでも、ページビルダーの制約下でプラグインを増やさずに実現するという条件では、現実的な選択肢だと考えています。


まとめ

課題解決方法
ビルダーにクエリループを置けないパターン化して do_blocks() でショートコード出力
クエリループがカスタムフィールドを出せないrender_block で段落の中身を差し替え
フィルターがサイト全体に効くショートコード実行中だけグローバルフラグを立てる
Lightboxが全画像でつながるdata-rel に投稿IDを埋めてグループ分け
一覧に写真が全部並ぶ:not(:first-child) で2枚目以降を非表示

3回の方針転換を経ていますが、振り返るとどれも必要な回り道でした。特に投稿タイプ判定が効かなかった経験は、render_block フィルターの性質を理解するうえで大きかったです。

なお、この実装には子テーマが必須です。functions.php を編集するので、親テーマに直接書くとテーマ更新で消えます。

💬 まめすけ うまくいかなかった回り道こそ、次の誰かの近道になるんだね

まめすけ

うまくいかなかった回り道こそ、次の誰かの近道になるんだね


同じようなお悩み、ありませんか?

この記事のような不具合・お困りごとは、実際に状況を見た上でご提案します。
まずは無料相談から、お気軽にどうぞ。

このサイトはテーマ「SWELL」を使っています

「SWELL」はWordpress初心者も慣れた人も使い続けられるテーマだと思います。

よかったらシェアお願いします!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

WordPressでサイトを作り、壊し、また作る。
フリーランスとしてココナラで実案件に向き合いながら、その経験をそのまま言葉にしています。
うまくいった設定も、試したら失敗した話も、ぜんぶここに書いています。
すでにWordPressを持っていて、もう一歩進めたい人に届けば。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

目次