カスタムフィールドを自作する(グループ&繰り返しもあるよ)

参考サイトを表示するカスタムフィールドを実装しようとしたら、脆弱性の問題からCustom Field Suite(CFS)が配信停止していた。プラグインを漁りまくったけど、無料でカスタムフィールドのグループを繰り返しで表示できるものはなかった。

いっそのこと自作してまえ!!!!

目次expand_more

WordPressのカスタムフィールドとは?

カスタムフィールドとは、WordPressの記事やページの編集画面に、特定の情報を入力するための専用フィールドを追加できる機能です。これにより、投稿ごとに独自のデータを簡単に管理・表示できるようになります。例えば、価格やイベント日程、著者情報など、必要な項目を事前に設定しておくことで、編集作業が効率化されるだけでなく、情報の一貫性も保てます。

ループ(繰り返し処理)とは?

ループ(繰り返し処理)とは、同じ項目を単数でも複数でも好きなだけ登録できるようにすることです。

必ずしも登録したいフィールドは1つとは限らないですよね?

この記事では、

  • 参考サイトのURL
  • サイト名
  • 執筆者様

を登録できるカスタムフィールドを自作しようとしています。

しかし、参考サイトは1つの場合もあれば、2つの場合もあるし、3つの場合もあります。
数が決まっていないので、フォームを好きなだけ追加できる仕様にします。

それをループ繰り返し処理と呼称します。

プラグインはないの?

プラグインを検討したけど、欲しい機能がなかったんです!
有料で良いならカスタマイズ性が段違いなプラグインがあるので、よかったらそちらも検討してください。

Custom Field Suite

今までこちらを使ってたのですが、2024年8月20日にWordPress公式サイトからダウンロードできなくなりました。複数の脆弱性があってプラグイン作成者が削除依頼したようです。

無料で使えたし、便利だったのになー・・・。

Smart Custom Fields

開発がストップしており、ダウンロードはできますが、現在のWPバージョンでテストされていません。最低限の脆弱性には対応しているとかしてないとか。(ちゃんとした情報源を見つけられなかった)
自己責任でどうぞ。

Advanced Custom Fields

めちゃくちゃ便利ですが、有料です。10年前は無料だったのに。
繰り返し処理が必要ない場合は無料で使えるので、一考もあり。

Meta Box

グループでの繰り返し処理は有料でした。
カスタムフィールド1個単位だったら無料で繰り返し処理できます。
私はグループで繰り返ししたかったので候補から外しました。

とりあえずカスタムフィールド(グループ繰り返し)のソースコードを公開します

とりあえずコピペできるように先にコード公開!!!(そのまんまのソースコードを公開していいのだろうか?セキュリティ的に・・・。)

functions.php

<?php
function add_referentsite() {
    add_meta_box(
        'referentsite_fields',
        '参考サイト',
        'referentsite_callback',
        'post',
        'normal',
        'high'
    );
}
add_action('add_meta_boxes', 'add_referentsite');

function referentsite_callback($post) {
    wp_nonce_field('referentsite_nonce_action', 'referentsite_nonce_field');

    $referentsites = get_post_meta($post->ID, '_referentsites', true);
    $referentsites = $referentsites ? json_decode($referentsites, true) : [];

    echo '<div id="referentsite-container">';
    if (!empty($referentsites)) {
        foreach ($referentsites as $key => $site) {
            echo '<div class="referentsite-row">';

            echo '<div class="referentsite-row_child">';
            echo '<label>参考サイト名:</label>';
            echo '<input type="text" name="referentsites[' . $key . '][name]" value="' . esc_attr($site['name']) . '" />';
            echo '</div>';

            echo '<div class="referentsite-row_child">';
            echo '<label>URL:</label>';
            echo '<input type="url" name="referentsites[' . $key . '][url]" value="' . esc_attr($site['url']) . '" />';
            echo '</div>';

            echo '<div class="referentsite-row_child">';
            echo '<label>神の名:</label>';
            echo '<input type="text" name="referentsites[' . $key . '][owner]" value="' . esc_attr($site['owner']) . '" />';
            echo '</div>';

            echo '<button type="button" class="remove-referentsite">削除</button>';
            echo '</div>';
        }
    }
    echo '</div>';

    echo '<button type="button" id="add-referentsite">フィールド追加</button>';

    ?>
    <script>
        document.addEventListener('DOMContentLoaded', function () {
            const container = document.getElementById('referentsite-container');
            const addButton = document.getElementById('add-referentsite');

            addButton.addEventListener('click', function () {
                const count = container.children.length;
                const fieldHTML = `
                    <div class="referentsite-row">
                        <div class="referentsite-row_child">
                            <label>参考サイト名:</label>
                            <input type="text" name="referentsites[${count}][name]" />
                        </div>
                        <div class="referentsite-row_child">
                            <label>URL:</label>
                            <input type="url" name="referentsites[${count}][url]" />
                        </div>
                        <div class="referentsite-row_child">
                            <label>神の名:</label>
                            <input type="text" name="referentsites[${count}][owner]" />
                        </div>
                        <button type="button" class="remove-referentsite">削除</button>
                    </div>`;
                container.insertAdjacentHTML('beforeend', fieldHTML);
            });

            container.addEventListener('click', function (e) {
                if (e.target.classList.contains('remove-referentsite')) {
                    e.target.parentElement.remove();
                }
            });
        });
    </script>
    <?php
}
function save_referentsite_meta_box($post_id) {
    if (
        !isset($_POST['referentsite_nonce_field']) ||
        !wp_verify_nonce($_POST['referentsite_nonce_field'], 'referentsite_nonce_action') ||
        (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) ||
        !current_user_can('edit_post', $post_id)
    ) {
        return;
    }

    if (isset($_POST['referentsites'])) {
        $sites = [];
        foreach ($_POST['referentsites'] as $site) {
            $sites[] = [
                'name' => sanitize_text_field($site['name']),
                'url' => esc_url_raw($site['url']),
                'owner' => sanitize_text_field($site['owner']),
            ];
        }
        update_post_meta($post_id, '_referentsites', json_encode($sites, JSON_UNESCAPED_UNICODE));
    } else {
        delete_post_meta($post_id, '_referentsites');
    }
}
add_action('save_post', 'save_referentsite_meta_box');

single.php

投稿ページの表示したいところにこのコードを入れてください。

$referentsites = get_post_meta(get_the_ID(), '_referentsites', true);
$referentsites = $referentsites ? json_decode($referentsites, true) : [];
if (!empty($referentsites)) {
    echo '<aside class="referentsite_wrap"><h2>参考サイト</h2><p>以下のサイトを参考にしました!より詳しいことが書かれているかも?</p><ul>';
    foreach ($referentsites as $site) {
        echo '<li>';
        echo '<a href="' . esc_url($site['url']) . '" target="_blank" rel="noopener noreferrer">' . esc_html($site['name']) . '</a>';
        if (!empty($site['owner'])) {
            echo '(' . esc_html($site['owner']) . 'さん)';
        }
            echo '</li>';
        }
        echo '</ul></aside>';
}     

functions.phpのソースコードの解説!

このセクションは途中までしか書いてません!!!ごめんなさい🙇‍♀️🙇‍♀️🙇‍♀️
少しずつ更新中です!

今回の機能は、WPがすでに持っているカスタムフィールド機能を使って作成しています。
ちょっと長くなるので頑張って書きます!

メタボックスを追加

function add_referentsite() {
    add_meta_box(
        'referentsite_fields', // メタボックスのID
        '参考サイト',         // メタボックスのタイトル
        'referentsite_callback', // メタボックスの内容を出力するコールバック関数
        'post',               // 投稿タイプ (この場合は「投稿」)
        'normal',             // 表示位置 (normal, side, advanced)
        'high'                // 表示優先度
    );
}
add_action('add_meta_boxes', 'add_referentsite');

function add_referentsite() { からまずは関数を書きましょう。 add_referentsite は任意の名前をつけます。ソースコードが増えても何に使っているかわかりやすい名前をつけましょうね。

その中に add_meta_box( を書きました。これはWP関数で、WordPressが用意してくれている関数です。これをルール通りに記載していきます。

add_meta_box の中身

メタボックスのID
referentsite_fieldsと書かれている部分です。カスタムフィールドが複数ある場合にこのIDが重複していると予期せぬエラーが起こります。必ず重複しないわかりやすい名前をつけるように心がけましょう。
カスタムフィールドのタイトル
参考サイトと書かれている部分です。編集画面のカスタムフィールドのタイトルとなります。ここだけ日本語OKです。
コールバック関数
referentsite_callbackと書かれている部分です。メタボックスの内容を出力する関数名です。
投稿タイプ
postと書かれている部分です。対象となる投稿タイプです。以下のタイプを登録できます。
複数設定する場合は、配列で書きます。
[‘post’, ‘page’]
カスタム投稿タイプに表示させたい場合は、カスタム投稿タイプの名前を入れてください。
  • post – 投稿タイプに表示
  • page – ページタイプに表示
表示位置
normalと書かれている部分です。編集画面に表示する位置を指定します。
  • normal – エディタの下に表示
  • side – 右サイドバーに表示
  • advanced – normalのさらに下に表示
表示優先度
highと書かれている部分です。上から順に優先度となります。
  • high
  • core
  • default
  • low

これで「参考サイト」のカスタムフィールドが投稿画面に表示されているはずです!でも、今はまだタイトルしか表示されていないと思います!次へ行きましょう!

カスタムフィールドを出力するための処理を追加

これを追加することにより、カスタムフィールドが保存されていたら、投稿編集画面に表示されます。

function referentsite_callback($post) {
    // セキュリティ対策
    wp_nonce_field('referentsite_nonce_action', 'referentsite_nonce_field');

    // 保存されたデータの取得
    $referentsites = get_post_meta($post->ID, '_referentsites', true);
    $referentsites = $referentsites ? json_decode($referentsites, true) : [];

    // 入力フィールドの出力
    echo '<div id="referentsite-container">';
    if (!empty($referentsites)) {
        foreach ($referentsites as $key => $site) {
            echo '<div class="referentsite-row">';
            echo '<label>参考サイト名:</label>';
            echo '<input type="text" name="referentsites[' . $key . '][name]" value="' . esc_attr($site['name']) . '" />';
            echo '<label>URL:</label>';
            echo '<input type="url" name="referentsites[' . $key . '][url]" value="' . esc_attr($site['url']) . '" />';
            echo '<label>神の名:</label>';
            echo '<input type="text" name="referentsites[' . $key . '][owner]" value="' . esc_attr($site['owner']) . '" />';
            echo '</div>';
        }
    }
    echo '</div>';
    echo '<button type="button" id="add-referentsite">フィールド追加</button>';
}

セキュリティ対策

wp_nonce_field を使ってセキュリティ対策を行います。

wp_nonce_field('referentsite_nonce_action', 'referentsite_nonce_field');

私もこれを見たのは初めてで、いろいろ調べたのですが、長くなりそうなので割愛します。
余力があれば別記事で解説したいですね。(願望だけ

保存されているデータの取得

$referentsites = get_post_meta($post->ID, '_referentsites', true);
$referentsites = $referentsites ? json_decode($referentsites, true) : [];

get_post_meta($post->ID, '_referentsites', true); はWP関数です。
get_post_meta 関数の構文は以下のようになります。

get_post_meta($post_id, $meta_key, $single);

第3引数の $single は、 true なら単一の値として返し、 false なら配列を返します。
今回は単一の値なので、 true にします。

2行目の処理は、「値が保存されていたらjson_decode」して、「保存されていないなら何もしない」という記述です。

$referentsites = $referentsites ? json_decode($referentsites, true) : [];

値はjson形式で保存されているので、PHPの配列に変換しなければなりません。
三項演算子によって条件分岐をしています。

フィールドの出力

    echo '<div id="referentsite-container">';
    if (!empty($referentsites)) {
        foreach ($referentsites as $key => $site) {
            echo '<div class="referentsite-row">';

            echo '<div class="referentsite-row_child">';
            echo '<label>参考サイト名:</label>';
            echo '<input type="text" name="referentsites[' . $key . '][name]" value="' . esc_attr($site['name']) . '" />';
            echo '</div>';

            echo '<div class="referentsite-row_child">';
            echo '<label>URL:</label>';
            echo '<input type="url" name="referentsites[' . $key . '][url]" value="' . esc_attr($site['url']) . '" />';
            echo '</div>';

            echo '<div class="referentsite-row_child">';
            echo '<label>神の名:</label>';
            echo '<input type="text" name="referentsites[' . $key . '][owner]" value="' . esc_attr($site['owner']) . '" />';
            echo '</div>';

            echo '<button type="button" class="remove-referentsite">削除</button>';
            echo '</div>';
        }
    }
    echo '</div>';

    echo '<button type="button" id="add-referentsite">フィールド追加</button>';

    ?>

ちょっと長いですが、よく見ると簡単です。ほとんど echo を使ってHTMLを書いています。

フィールド全体を囲むタグ

echo '<div id="referentsite-container">';
    /* この間にフィールドのコードを書く */
echo '</div>';

idをつけたdivで全体を囲みます。
これがないと後々の処理でフィールドを追加できなくなるので、必ず入れましょう。
classじゃなくてidですよ!
絶対に重複しないid名をつけてくださいね。

値が保存されていたらフィールドを表示する

echo '<div id="referentsite-container">';
if (!empty($referentsites)) {
    /* この間にフィールドのコードを書く */
}
echo '</div>';

フィールド全体を囲むタグの中に if文を追加しましょう。
$referentsites に値が無い(= empty)以外(= !)だったら表示します。

保存されている値を表示

最もシンプルに書くとこのようになります。

foreach ($referentsites as $key => $site) {
    echo '<label>参考サイト名:</label>';
    echo '<input type="text" name="referentsites[' . $key . '][name]" value="' . esc_attr($site['name']) . '" />';
    echo '<label>URL:</label>';
    echo '<input type="url" name="referentsites[' . $key . '][url]" value="' . esc_attr($site['url']) . '" />';
    echo '<label>神の名:</label>';
    echo '<input type="text" name="referentsites[' . $key . '][owner]" value="' . esc_attr($site['owner']) . '" />';
    echo '<button type="button" class="remove-referentsite">削除</button>';
}

「参考サイト名」「URL」「神の名」が表示されるように、foreachを使って配列を繰り返して出力します。

間違えて登録した場合には削除したいので削除ボタンも追加しましょう。
フィールドグループごとに削除できるようにしたいので、foreachの中に書きます。

echo '<button type="button" class="remove-referentsite">削除</button>';

フィールドグループを追加するためのボタンを追加

if文の外側に、フィールド追加ボタンを記述します。

echo '<button type="button" id="add-referentsite">フィールド追加</button>';

削除ボタンと追加ボタンを動かすJS

WordPressはPHPで開発されているので、同一画面上でフォームを追加したり削除したりすることはできません。
そのため、JSを使って追加・削除がされるようにします。

ループ(繰り返し処理)を使わない場合、このJSは不要です。

<script>
  document.addEventListener('DOMContentLoaded', function () {
    const container = document.getElementById('referentsite-container');
    const addButton = document.getElementById('add-referentsite');

    addButton.addEventListener('click', function () {
      const count = container.children.length;
      const fieldHTML = `
        <div class="referentsite-row">
          <div class="referentsite-row_child">
            <label>参考サイト名:</label>
            <input type="text" name="referentsites[${count}][name]" />
          </div>
          <div class="referentsite-row_child">
            <label>URL:</label>
            <input type="url" name="referentsites[${count}][url]" />
          </div>
          <div class="referentsite-row_child">
            <label>神の名:</label>
            <input type="text" name="referentsites[${count}][owner]" />
          </div>
          <button type="button" class="remove-referentsite">削除</button>
        </div>`;
      container.insertAdjacentHTML('beforeend', fieldHTML);
    });

    container.addEventListener('click', function (e) {
      if (e.target.classList.contains('remove-referentsite')) {
        e.target.parentElement.remove();
      }
    });
  });
</script>

CFSなくなって困ったね!

困ったなっていう人が他にもいるかなと思って取り急ぎ自作コード作成しました!本当にびっくりした!!!

0件のコメント

コメントはまだありません。最初の一人になりましょう!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です