Open41

WP既存テーマの改修

じゃーにゃりすとじゃーにゃりすと

記事にアイキャッチ画像を登録していないときに「NO IMAGE」のアイキャッチ画像を出ないようにする

home.phpとindex.phpのちがい

home.php

ホームページのテンプレートファイル
ブログのメインページ
投稿の一覧ページを表示

index.php

デフォルトのメインテンプレートファイル
home.phpがない場合、index.phpがメインページ

get_template_part()

テーマファイル内で別のファイルを読み込むための関数
コード分割に使われる
コードを再利用できるようにするため
保守性を上げるため

該当ファイルの検索

home.phpで1カラムかどうか、スマホかどうか、記事スタイルは何を選んでいるかの条件分岐

wp_is_mobile()とis_mobile()のちがい

PCとスマホの区別
タブレットをどちらにするかで使い分け

wp_is_mobile()

WPの関数
タブレットはスマホ扱いになる

is_mobile()

自分で関数を設定する
タブレットをPC扱いにすることが可能

インフィード広告表示回数が設定されているかどうかで場合分け

<?php if( isset($ad_infeed_pc_num) || isset($ad_infeed_sp_num) ) :?>

アイキャッチ画像が設定されていない場合に画像が出力される部分を消去

<?php echo get_jin_noimage_url(); ?>
じゃーにゃりすとじゃーにゃりすと

トップ画面の各記事のリード文下にタグを表示する

タグの取得

get_the_tags()

記事のタグをすべて取得してオブジェクトの配列で返す

$post_tags = get_the_tags();
if ($post_tags) {
    echo '<div class="post-list-tags">';
    foreach ($post_tags as $tag) {
        echo $tag->name ;
    echo '</div>';
}

$tag->name

特定の記事の特定のタグの名前を取得

そのタグがついている記事一覧のリンクを付けるかも検討した。
クリックするときはその記事を見たいことが多いと考え、リンクがあると邪魔になる(記事のページにいきたいのにタグのページに飛んでしまう)ので今回はリンクはつけなかった。

CSSの追加

該当ファイルの検索

まずはstyle.css(scss)を探す
大量のcssファイルがimportされているのでコメントやファイル名を見ながら探す
phpファイルを見るとclass名が書かれているのでそれを頼りにcssファイルの中で検索をかける

検証ツールをみてもいいが、最終的にはstyle.cssに集約されているのでファイル名がでてこない
ツールで出てきたcssファイルを遡っていくとコメントがある場合は、そのコメントがstyle.cssに直接書かれている可能性があるので、それを頼りにcssファイルを見つける

じゃーにゃりすとじゃーにゃりすと

OGP設定

OGPはOpen Graph ProtocolでSNSでシェアされたときにタイトル、説明文、サムネイル画像などをどのように表示するかを決めることができる

OGPの設定方法

  1. head要素にprefix属性を追加(OGPを使うと宣言)
  2. headerタグ内のmetaタグで要素を追加
<meta property="og:url" content="ページのURL" />
<meta property="og:type" content="ページの種類" />
<meta property="og:title" content="ページのタイトル" />
<meta property="og:description" content="ページの説明文" />
<meta property="og:site_name" content="サイト名" />
<meta property="og:image" content="サムネイル画像のURL" />

テーマ「JIN」では外観→カスタマイズ→SNS設定(OGP)で簡単にOGPを設定することができる

noindex設定

検索エンジンに登録しないようにする設定
リリース前のサイトや、エラーページ、重複したコンテンツがあるページなどに設定される
これらをnoindexにすることでSEO評価を上げる

noindexにしてもURLを直接打ち込めばサイトを見ることはできるので注意する
また、noindex設定を尊重するかどうかは検索エンジンの判断によるので絶対ではない

コードで直接打ち込む場合

<head>
    <meta name="robots" content="noindex, follow">
</head>

WPの設定から設定する場合

設定→表示設定→「検索エンジンがサイトをインデックスしないようにする」にチェックをつける

じゃーにゃりすとじゃーにゃりすと

カテゴリー、タグ、タクソノミー、タームの違い

タクソノミー

記事を分類するためにつけるもの
カテゴリーもタグもタクソノミーの1つ
カテゴリーとタグはWPにデフォルトで組み込まれているタクソノミー
この2つ以外のタクソノミーも自分で作ることができる
自分で作ったものがカスタムタクソノミー

カスタムタクソノミー

functions.phpに記述したり、プラグインをいれることで独自のタクソノミーを作ることができる

function create_custom_taxonomy() {
    // タクソノミーのラベルや設定を定義
    $labels = [
        'name'          => 'Brands',         // タクソノミー全体の名前(複数形)
        'singular_name' => 'Brand',          // タクソノミーの名前(単数形)
        'search_items'  => 'Search Brands',  // 検索ボックスに表示するテキスト
        'all_items'     => 'All Brands',     // 一覧表示されるときのテキスト
        'edit_item'     => 'Edit Brand',     // 個別の項目を編集するときの表示テキスト
        'update_item'   => 'Update Brand',   // 更新するときの表示テキスト
        'add_new_item'  => 'Add New Brand',  // 新しい項目を追加するときの表示テキスト
        'new_item_name' => 'New Brand Name', // 新しい項目を追加するフォームに表示
        'menu_name'     => 'Brands',         // 管理メニューに表示されるメニュー名(基本'name'と同じ名前)
    ];

    // タクソノミーを作成
    register_taxonomy('brand', 'product',[
        'hierarchical'      => true,    // 階層構造を持つかどうか
        'labels'            => $labels, // 表示オプションやラベルを設定した連想配列(上の$labelsが使われる)
        'show_ui'           => true,    // タクソノミーの管理メニューを表示するかどうか
        'show_admin_column' => true,    // タクソノミーをカスタムポストタイプの一覧画面に表示するかどうか
        'query_var'         => true,    // タクソノミーをクエリ変数として使うかどうか
        'rewrite'           => ['slug' => 'brand'],
    ]);
}

add_action('init', 'create_custom_taxonomy');

カテゴリーとタグの違い

カテゴリー

大まかな分類
階層がある
サイト全体で一部の主要なカテゴリーのみが使われる

タグ

細かな分類
階層がない
記事ごとに個別のタグが割り当てられる

ターム

カテゴリーやタグといった各タクソノミーに登録されているひとつひとつの項目がターム
「カテゴリー」「タグ」はタクソノミー名であってタームではない
カテゴリーの中の「PHP」の項目がターム
「PHP」は「カテゴリーのターム」であり、「カテゴリー」ではない
項目は「カテゴリー」や「タグ」ではなく「カテゴリーのターム」や「タグのターム」である

カテゴリー、タグ、カスタムタクソノミーの使い分け

必要なタクソノミーが1,2種類であればカテゴリーとタグを使う
必要なタクソノミーが3種類以上であればカスタムタクソノミーを使う
カスタムタクソノミーを使うときはカテゴリーやタグは使わずすべてカスタムタクソノミーを使う方がいい
カテゴリーやタグはget_category()やget_tag()を使うことができるので、カスタムタクソノミーと混ぜると挙動が異なるところが出てきてしまう可能性があるため

get_categories()、get_tags()、get_terms()の使い分け

get_categories()

カテゴリーを取得するための関数

$categories = get_categories();

get_tags()

タグを取得するための関数

$tags = get_tags();

get_terms()

カスタムタクソノミーを取得するための関数

$custom_taxonomy_terms = get_terms( 'custom_taxonomy' );
じゃーにゃりすとじゃーにゃりすと

searchform.phpとsearch.php

searchform.php

検索フォームの表示を制御
検索ボックスを生成する

検索フォームの外観と挙動に焦点を当てる

<form class="search-box" role="search" method="get" id="searchform" action="<?php echo home_url( '/' ); ?>">
	<input type="search" placeholder="" class="text search-text" value="" name="s" id="s">
	<input type="submit" id="searchsubmit" value="&#xe931;">
</form>
  • method="get"
    検索クエリをURLに表示する
    検索クエリは公開情報なのでgetで問題ない

  • action="<?php echo home_url( '/' ); ?>"
    フォームが送信されたときのアクションを指定
    ホームページのURLを取得して検索結果の処理をしている

search.php

検索結果の表示を制御
検索結果がある場合→検索結果を表示
検索結果がない場合→検索結果がない旨のメッセージ等を表示

検索結果ページの外観と挙動に焦点を当てる

じゃーにゃりすとじゃーにゃりすと

カテゴリー絞り込み検索機能を作る

検索フォーム作成

refine_searchform.php
<form method="get" action="<?php bloginfo( 'url' ); ?>" class="refine_searchform">
    <label class="refine_searchform_label">絞り込み検索</label>
    <?php wp_dropdown_categories('orderby=name&hide_empty=-&show_option_all=カテゴリー&selected=0&class=refine_searchform_custom_dropdown'); ?>
    <div class="refine_searchform_box_button">
	    <input name="s" class="refine_searchform_box" id="s" type="text" placeholder="" />
	    <button class="refine_searchform_botton" type="submit">検索</button>
    </div>
</form>

カテゴリーの一覧をセレクトボックス形式で表示する

orderby

  • name 名前順に並べる
  • date 投稿日時順
  • title タイトル順
  • rand ランダム
  • ID 投稿のID順

hide_empty

  • 1 または true 非表示のカテゴリーを隠す
  • 0 または false 非表示のカテゴリーを表示
  • -すべてのカテゴリーを表示

show_option_all

セレクトボックスの最初のオプション名
これを選択するとすべてのカテゴリーを対象とした検索になる(絞り込みしない状態)

初期で選択されている項目

  • 0 カテゴリーIDが0の項目を選択

ショートコード作成

functions.php
function shortcode_refine_searchform () {
	get_template_part('refine_searchform');
}
add_shortcode('refine_searchform', 'shortcode_refine_searchform');

ウィジェットエリアに追加

外観→ウィジェット→サイドバー
ショートコードに[refine_searchform]と打つと完成

じゃーにゃりすとじゃーにゃりすと

カテゴリー絞り込み検索機能を作る

 <?php $tags = get_tags(); if ( $tags ) : ?>
    <select name='tag' id='tag'  class='refine_searchform_custom_tag'>
        <option value="" selected="selected">タグ</option>
        <?php foreach ( $tags as $tag ): ?>
            <option value="<?php echo esc_html( $tag->slug); ?>"><?php echo esc_html( $tag->name ); ?></option>
        <?php endforeach; ?>
    </select>
<?php endif; ?>

タグの取得

get_tags()でタグを取得し、タグが存在するか条件分で確認

取得したタグをループさせる

foreachでループ

タグのスラッグをHTMLエスケープしてvalueに設定

esc_html( $tag->slug);

選択肢の表示されるテキストを表示

echo esc_html( $tag->name );

同じくHMLエスケープ

じゃーにゃりすとじゃーにゃりすと

Contact Form 7を使ってお問い合わせフォームを模写

CSSの追加

お問い合わせ→フォームからフォームのHTMLをいじれる
はじめからついているフォーム全体のclass名は"wpcf7"

自動入力autocomplete

autocomplete属性を使用すると入力欄に自動的に値を提案してくれる
on/offで有効/無効
name email telなどそれぞれに関する値を提案してくれる

じゃーにゃりすとじゃーにゃりすと
<label> お名前(全角)<span class=name-required>必須</span>
    [text* inquiry-name class:inquiry-name autocomplete:name id:inquiry-name "(例)山田 太郎"]</label>

<label> 電話番号 <span class=tel-required>必須</span>
    [text* your-tel class:inquiry-tel autocomplete:tel "(例)0312345678"] </label>

<label> メールアドレス <span class=email-required>必須</span>
    [email* your-email autocomplete:email "(例)example@mail"] 
    <div class="email-text">※会員登録をされている場合は、ご登録済みのメールアドレスを入力してください。</div>
    <div class="email-text">※HTMLメールで送信しますので、パソコンやWebメールのアドレスをおすすめします。</div>
    <div class="email-text">※迷惑メール対策をしている場合、受信許可リストに「ma@il.com」を登録してください。</div>
</label>

<label> お問い合わせの種類 <span class=inquiry-type-required>必須</span>
    [select* inquiry-type class:inquiry-type "種類の選択" "会員登録・ログインについて" "商品・注文について"] </label>

<label> 返信希望 <div class=reply-text>返信不要の場合は、チェックしてください。</div>
    [checkbox reply-checkbox class:inquiry-reply-checkbox "返信を希望しない"] </label>

<label> ご注文番号
    [text your-order-number] </label>

<label> お問い合わせ内容 <span class=message-required>必須</span>
    [textarea* your-message] </label>

[submit "お問い合わせ内容の確認"]
じゃーにゃりすとじゃーにゃりすと

問い合わせ内容の確認画面を作成

Contact Form 7 Multi-Step Formsをインストール

確認画面のフォーム作成

お問い合わせ→コンタクトフォームから事前に作った「問い合わせフォーム」を複製
それぞれの名前をコピーして「multiform」の「name」欄にペーストしていく
送信の前に[previous "戻る"]を追加して前の画面に戻れるようにしておく

確認画面の固定ページ作成

ブロックから「Contact Form 7」を追加
確認画面を選択
URLを「contact-confirm」に変更

サンクス画面の固定ページ作成

確認画面と同様にする
URLを「contact-thanks」に変更

問い合わせフォームに確認画面へのページ遷移タグをつける

送信ボタン(入力内容の確認)の下に「multistep」を配置
「First Step」にチェックをつける
Next Page URLに「/contact-confirm」を入力

確認画面のフォームにサンクスページへの遷移タグをつける

送信ボタンの下に「multistep」を配置
「Last Step」にチェックをつける
Next Page URLに「/contact-thanks」を入力

確認画面のフォーム

<label> お名前(全角)
[multiform "inquiry-name"]</label>

<label> 電話番号
[multiform "your-tel"]</label>

<label> メールアドレス
[multiform "your-email"]</label>

<label> お問い合わせの種類
[multiform "inquiry-type"]</label>

<label> 返信希望
[multiform "reply-checkbox"]</label>

<label> ご注文番号
[multiform "your-order-number"]</label>

<label> お問い合わせ内容
[multiform "your-message"]</label>

[previous "戻る"]
[submit "送信"]
[multistep multistep-154 last_step send_email "/contact-thanks"]

参考

https://www.xserver.ne.jp/bizhp/contact-form-7-confirm-thanks/

じゃーにゃりすとじゃーにゃりすと

dl dt ddタグを使って確認画面を整える

dl dt ddタグとは

  • dl リスト(list)
  • dt 言葉 (term)
  • dd 説明(description)

dtが説明したいこと、ddがそれに関する説明

<dl>
 <dt>ソフトドリンク</dt>
 <dd>一般の飲料</dd>
 <dt>お酒</dt>
 <dd>アルコール入り飲料</dd>
</dl>

dlとulの使い分け

ulは単純な項目のリスト

<ul>
  <li>りんご</li>
  <li>ぶどう</li>
  <li>バナナ</li>
</ul>

dlは項目とその説明

<dl>
  <dt>りんご</dt>
  <dd>赤い果物</dd>
  <dt>ぶどう</dt>
  <dd>紫の果物</dd>
  <dt>めろん</dt>
  <dd>黄色の果物</dd>
</dl>

dtとddを横並びにする

dtにfloat: left;をつける
ddにはつけない
つけると全部が横並びになる

<div class="confirmation-screen">以下の内容でよろしければ「お問い合わせ内容の送信」ボタンを押してください。
  <dl>
    <dt>お名前</dt>
    <dd>[multiform "inquiry-name"] 様</dd>

    <dt>電話番号</dt>
    <dd>[multiform "your-tel"]</dd>

    <dt>メールアドレス</dt>
    <dd>[multiform "your-email"]</dd>

    <dt>お問い合わせの種類</dt>
    <dd>[multiform "inquiry-type"]</dd>

    <dt>返信希望</dt>
    <dd>[multiform "reply-checkbox"]</dd>

    <dt>ご注文番号</dt>
    <dd>[multiform "your-order-number"]</dd>

    <dt>お問い合わせ内容</dt>
    <dd>[multiform "your-message"]</dd>
  </dl>
</div>

<div class="submit_btn">
  <span class="go_back">[previous "前に戻る"]</span>
  <span class="submit">[submit "お問い合わせ内容の送信"]<span>
</div>
[multistep multistep-154 last_step send_email "/contact-thanks"]

参考

https://webliker.info/html/13381/

じゃーにゃりすとじゃーにゃりすと

返信希望の欄にチェックがついていないときに「返信を希望する」と表示させる

はじめはJSを使ってIDを取得し、if文で分岐からの表示をさせようとしたが、取得がうまくいかず別のアプローチをとった。
うまくいかなかったコード

<dt>返信希望</dt>
<dd class="reply-checkbox">[multiform id:inquiry-reply-checkbox "reply-checkbox"]
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const replyCheckbox = document.getElementById('inquiry-reply-checkbox');
            if (!replyCheckbox) {
                const messageElement = document.createElement('p');
                messageElement.textContent = '返信を希望する';
                document.querySelector('.reply-checkbox').appendChild(messageElement);
            }
        });
    </script>
</dd>

いくらやっても
const replyCheckbox = document.getElementById('inquiry-reply-checkbox');
がnullになった

そこで「返信を希望する」と「返信を希望しない」2つのチェックボックス用意して表示させた

<label> 返信希望
    [checkbox reply-checkbox use_label_element exclusive class:inquiry-reply-checkbox id:inquiry-reply-checkbox "返信を希望する" "返信を希望しない"] 
</label>

どちらか1つだけ選択させたいときは「exclusive」をつけておくと複数選択ができなくなるので便利

CSSを整えて完成

じゃーにゃりすとじゃーにゃりすと

フロントページの表示件数を5件にする

クエリとは

ワードプレスがデータベースに対して条件に合致するデータの取得を要求すること
「カテゴリ名」「投稿ID」「取得する順番」「取得投稿データ数」など

メインクエリとは

メインのコンテンツを表示するために行われるクエリ
ワードプレスによって自動的に作成・発行される
メインクエリが取得した情報に基づいて、ページや投稿のコンテンツが生成・表示される

サブクエリとは

メインクエリ以外のもの

functions.php
function custom_limit_front_page_posts($query) {
    if ($query->is_main_query() && is_front_page()) {
        $query->set('posts_per_page', 5);
    }
}
add_action('pre_get_posts', 'custom_limit_front_page_posts');
じゃーにゃりすとじゃーにゃりすと

フロントページに表示されるカテゴリーを絞り込む

$category_slug

特定のカテゴリーを絞り込むための変数
$category_slugに表示したいカテゴリーを代入して$query->setでセットする

pre_get_posts

pre_get_postsフィルターフックはクエリが実行される前に呼び出されるので、このフックを使ってクエリを変更するとよい

function custom_limit_front_page_category($query) {
    if ($query->is_main_query() && is_front_page()) {
        $category_slug = 'カテ1';
        $query->set('category_name', $category_slug);
    }
}
add_action('pre_get_posts', 'custom_limit_front_page_category');
じゃーにゃりすとじゃーにゃりすと

フロントページに表示されるカテゴリーを絞り込む(複数表示)

implode関数

配列の要素を文字列に結合

$category_slugs = ['category1', 'category2', 'category3'];
implode(',', $category_slugs)
// 'category1,category2,category3'になる

これで複数のカテゴリーを表示できるようになる

function custom_limit_front_page_categories($query) {
    if ($query->is_main_query() && is_front_page()) {
        $category_slugs = ['カテ1', 'カテゴリー2'];
        $query->set('category_name', implode(',', $category_slugs));
    }
}
add_action('pre_get_posts', 'custom_limit_front_page_categories');
じゃーにゃりすとじゃーにゃりすと

フロントページに表示する数やカテゴリーを管理画面の設定画面から選択できるようにする

テーマの設定画面の作り方は以下の通り

functions.php
function frontpage_options_menu() {
    add_menu_page(
        'フロントページ設定',         // メニューのページタイトル
        'フロントページ設定',         // メニューの表示名
        'manage_options',        // 権限
        'theme-options',         // ページのスラッグ
        'frontpage_options_page' // ページの表示関数
    );
}
add_action('admin_menu', 'frontpage_options_menu');

function frontpage_options_page() {
    echo '<h2>フロントページ設定</h2>';
}
じゃーにゃりすとじゃーにゃりすと

設定項目の入力と保存のためのform生成

settings_fields()

設定を保存するときのセキュリティ
CSRF攻撃対策になる

do_settings_sections('frontpage-options')

後に出てくる'frontpage-options'というスラッグの設定項目を表示する

submit_button()

設定を保存するボタンの生成

functions.php
function frontpage_options_page() {
    ?>
    <div class="wrap">
        <h2>フロントページ設定</h2>
        <form method="post" action="options.php">
            <?php
            settings_fields('frontpage_options_group');
            do_settings_sections('frontpage-options');
            submit_button();
            ?>
        </form>
    </div>
    <?php
}
じゃーにゃりすとじゃーにゃりすと

管理画面にメニューを追加、その中に「ページごとの表示件数」と「表示するカテゴリー」2つの項目を追加

register_setting

$option_group

グループ名
テーマやプラグインごとに独自のグループ名を指定
複数の設定がある場合、それらをグループ別にまとめるために使う

$option_name

オプション名
データベースに保存される時のキーになる名前

$sanitize_callback

サニタイズコールバック関数
オプションが保存される前に入力データを安全に変換する関数

設定の保存→オプションの値をデータベースに格納→ get_optionで取得できるようになる

functions.php
register_setting(
    'frontpage_options_group',   // グループ名
    'frontpage_options',         // オプション名
    'frontpage_options_sanitize' // 入力データのサニタイズコールバック
);

サニタイズとエスケープの違い

どちらもデータを安全な形に変換する

サニタイズ

悪意のあるものを無効にする
データを保存する前に適用

エスケープ

入力されたものをHTMLやJSのコードとして解釈されないようにする
データを表示する際に適用

functions.php
add_settings_field(
    'posts_per_page',             // フィールドID
    'ページごとの表示記事数',          // フィールドのラベル
    'posts_per_page_callback',    // フィールドのコールバック関数
    'frontpage-options',          // ページスラッグ
    'front_page_settings_section' // セクションID
);

設定ページのセクションを追加

add_settings_section

$id

セクションID

$title

セクションのタイトル
<h3>で表示される
今回は必要ないため空欄

$callback

セクションを表示するためのコールバック関数
説明文や補足情報を表示するために使われる
今回は機能の使い方について記述した

$page

セクションが表示される設定ページのスラッグ
スラッグとは識別用にURLの末部につけるもの
https://...themes.php?page=frontpage-optionsの「frontpage-options」の部分

functions.php
add_settings_section(
    'front_page_settings_section',          // セクションID
    '',                                     // セクションのタイトル
    'front_page_settings_section_callback', // セクションのコールバック関数
    'frontpage-options'                     // ページスラッグ
);

設定ページに表示させる項目を作成

今回は「ページごとの表示記事数」と「表示するカテゴリー」の2つのフィールドを作成

add_settings_field

$id

フィールドID

$title

項目に表示されるタイトル

$callback

設定項目を表示するためのコールバック関数
入力欄や説明文を表示させる
今回は入力欄のみ表示させる

$page

設定項目が表示される設定ページのスラッグ
add_settings_sectionの$pageのスラッグと同じものを使用

$section

設定項目のセクションのID
add_settings_sectionの$idと同じIDを使用

functions.php
add_settings_field(
    'posts_per_page',             // フィールドID
    'ページごとの表示記事数',          // フィールドのラベル
    'posts_per_page_callback',    // フィールドのコールバック関数
    'frontpage-options',          // ページスラッグ
    'front_page_settings_section' // セクションID
);

add_settings_field(
    'selected_category',
    '表示するカテゴリー',
    'selected_category_callback',
    'frontpage-options',
    'front_page_settings_section'
);

管理画面にアクセスするときにフックする

function.php
add_action('admin_init', 'frontpage_options_init');

「ページごとの表示記事数」のフィールドを表示させる

function posts_per_page_callback()

add_settings_fieldで指定したもの

保存されている設定オプションを取得

get_option('frontpage_options')を使用
保存されている場合、その値を取得
保存されていない場合、デフォルト値を取得

取得したオプションが配列かつ['posts_per_page']が存在するかを確認

get_option('frontpage_options')で取得される値はfrontpage_optionsグループ内の全ての設定項目を含む連想配列であるため、期待通り配列になっているかを確認
その上で['posts_per_page']がある場合に処理をする

設定オプションが存在する場合は<input>を生成し、その中に取得した値を表示させる

このときにesc_attrを使用してエスケープ処理も行う

設定オプションが存在しない場合はデフォルト値を設定しておき表示する

functions.php
function posts_per_page_callback() {
    $options = get_option('frontpage_options');

    if (is_array($options) && isset($options['posts_per_page'])) {
        echo '<input type="number" name="frontpage_options[posts_per_page]" value="' . esc_attr($options['posts_per_page']) . '" />';
    } else {
        echo '<input type="number" name="frontpage_options[posts_per_page]" value="5" />';  // デフォルト値などを設定
    }
}

「表示するカテゴリー」のフィールドを表示させる

function selected_category_callback()

上と同様にadd_settings_fieldで指定されたもの

設定オプションを取得

get_categories()ですべてのカテゴリーを取得

<div>タグの生成

echo'<div>'でチェックボックス全体を囲む<div>タグを出力

各カテゴリーをループしカテゴリーが選択されているかどうかを確認

issetで配列内にselected_categoryが存在するかを確認
is_arrayでselected_categoryの値が配列であるかの確認、複数選択の可能性があるため
in_arrayでカテゴリーIDがselected_category'配列内に含まれているかを確認

in_array()

指定した値が配列に存在するかどうかを確認する関数

in_array($category->term_id, $options['selected_category'])

$category->term_idは現在のループで取得したカテゴリーのID
$options['selected_category']は選択されたカテゴリーのIDが格納されている配列

三項演算子を使用して、カテゴリーが選択されているかどうか(上の条件が真かどうか)を確認し、選択されていれば'checked'、そうでなければ空の文字列を代入

それぞれのカテゴリーに対して、チェックボックスを生成

esc_attrとesc_htmlの違い

どちらもエスケープをしてくれる関数

esc_attr

HTMLのタグの属性値をエスケープ
タグの属性値とは<div style="color:red;">本文</div>color:red;の部分のこと
今回の場合はvalue=""""の中身をエスケープしている

esc_html

HTML本文内のテキストをエスケープ
今回の場合はカテゴリー名をエスケープして表示している

functions.php
function selected_category_callback() {
    $options = get_option('frontpage_options');
    $categories = get_categories();

    echo '<div>';
    foreach ($categories as $category) {
        $checked = isset($options['selected_category']) && is_array($options['selected_category']) && in_array($category->term_id, $options['selected_category']) ? 'checked' : '';
        echo '<label><input type="checkbox" name="frontpage_options[selected_category][]" value="' . esc_attr($category->term_id) . '" ' . $checked . ' />' . esc_html($category->name) . '</label><br>';
    }
    echo '</div>';
}

セクションの説明を表示させる

add_settings_sectionでセクションのコールバック関数に指定したものを使用する

functions.php
function front_page_settings_section_callback() {
    echo '<p>フロントページに表示する記事数とカテゴリーを選択してください</p>';
}

入力されたデータを安全な形に変換

frontpage_options_sanitize

サニタイズしたあとのデータを入れる空の配列を作成

$sanitized_input = array();

input配列内に'posts_per_page'が存在する場合、整数に変換し$sanitized_input配列に格納

absint

引数を絶対値の整数に変換するための関数
与えられた値を整数に変換
数値でないものはデフォルトで0になる

if (isset($input['posts_per_page'])) {
    $sanitized_input['posts_per_page'] = absint($input['posts_per_page']);
}

input配列内に'selected_category'が存在する場合、整数に変換し$sanitized_input配列に格納

上の'posts_per_page'のときと異なり、'selected_category'が複数のカテゴリーIDの配列である可能性があるためarray_mapを使用する

array_map

与えられた配列のすべての要素に適用して新しい配列を生成する
今回は全てのカテゴリーIDにabsint関数を適用して新しい配列を生成している

if (isset($input['selected_category'])) {
    $sanitized_input['selected_category'] = array_map('absint', $input['selected_category']);
}

サニタイズされたデータが格納された$sanitized_input配列を返す

functions.php
function frontpage_options_sanitize($input) {
    $sanitized_input = array();

    if (isset($input['posts_per_page'])) {
        $sanitized_input['posts_per_page'] = absint($input['posts_per_page']);
    }

    if (isset($input['selected_category'])) {
        $sanitized_input['selected_category'] = array_map('absint', $input['selected_category']);
    }

    return $sanitized_input;
}

メインクエリの変更

custom_front_page_query

メインクエリかつフロントページの場合にオプションを取得

get_option('frontpage_options');

オプションが設定されていればクエリオブジェクトのsetメソッドを使って「表示する記事数」と「カテゴリー」を設定

クエリオブジェクトのsetメソッド
$query->set( $parameter, $value );

parameterは設定したいクエリのパラメータ
valueはそのパラメータに対する値

表示する記事数を10件に設定したいときは

$query->set( 'posts_per_page', 10 );
if (isset($frontpage_options['posts_per_page'])) {
    $query->set('posts_per_page', $frontpage_options['posts_per_page']);
}

if (isset($frontpage_options['selected_category'])) {
    $query->set('category__in', $frontpage_options['selected_category']);
}

メインクエリが実行する前にクエリに変更を加える

add_action('pre_get_posts', 'custom_front_page_query');
functions.php
function custom_front_page_query($query) {
    if ($query->is_main_query() && is_front_page()) {
        $frontpage_options = get_option('frontpage_options');

        if (isset($frontpage_options['posts_per_page'])) {
            $query->set('posts_per_page', $frontpage_options['posts_per_page']);
        }

        if (isset($frontpage_options['selected_category'])) {
            $query->set('category__in', $frontpage_options['selected_category']);
        }
    }
}
add_action('pre_get_posts', 'custom_front_page_query');
じゃーにゃりすとじゃーにゃりすと

フロントページを固定ページにして最新投稿一覧を表示させる

固定ページを作成する

WPの管理画面の固定ページから新規の固定ページを作成
名前を「フロントページ」にする

最新の投稿一覧を表示させる

ブロックエディターから「最新の投稿」を追加
設定パネルで設定も可

フロントページの表示設定を固定ページに変更する

設定→表示設定→ホームページの表示
固定ページにチェックをつけ、ホームページに「フロントページ」を選択する

ブロックエディターとクラシックエディターで作り方が違うので注意!

クラシックエディターの場合はテンプレートファイルを作成しそこにコードを書く

じゃーにゃりすとじゃーにゃりすと

固定ページにコメント欄を表示させる

投稿へのコメントを許可する

設定→ディスカッション→デフォルトの投稿設定
新しい投稿へのコメントを許可にチェックをつける

固定ページでもコメントを許可する

固定ページの編集画面の右にある「ディスカッション」の「コメントを許可」にチェックがついてあるかを確認する
設定画面で許可している場合はデフォルトでチェックがついてある

コメント欄を表示

ブロックエディターから「投稿コメントフォーム」を選択
右にある「ブロック」でテキストの色や大きさなどを変更可

じゃーにゃりすとじゃーにゃりすと

管理画面に作ったメニューに「表示するタグ」を選べる機能を追加する

全体の流れ

「表示するカテゴリー」を作ったときと同じ流れで作る
今回は複数選択前提で作り始める

フィールドを追加

ページスラッグを上と同じ「frontpage-options」に設定

functions.php
add_settings_field(
    'selected_tags',
    '選択したタグ',
    'selected_tags_callback',
    'frontpage-options',
    'front_page_tags_section'
);

コールバック関数追加

get_option()get_tags()で情報を取得
タグが存在すればforeachでタグを回しつつ出力していく
エスケープも忘れずに!

functions.php
function selected_tags_callback() {
    $options = get_option('frontpage_options');
    $tags = get_tags();

    echo '<div>';
    foreach ($tags as $tag) {
        $checked = isset($options['selected_tags']) && is_array($options['selected_tags']) && in_array($tag->term_id, $options['selected_tags']) ? 'checked' : '';
        echo '<label><input type="checkbox" name="frontpage_options[selected_tags][]" value="' . esc_attr($tag->term_id) . '" ' . $checked . ' />' . esc_html($tag->name) . '</label><br>';
    }
    echo '</div>';
}

サニタイズ関数も追加

サニタイズも忘れずに!

functions.php
if (isset($input['selected_tags'])) {
    $sanitized_input['selected_tags'] = array_map('absint', $input['selected_tags']);
}

クエリを更新して「表示するタグ」を表示させるようにする

これで設定画面に表示されるようになる

functions.php
if (isset($frontpage_options['selected_tags'])) {
    $query->set('tag__in', $frontpage_options['selected_tags']);
}
じゃーにゃりすとじゃーにゃりすと

「全てのカテゴリー」チェックボックスを作成

はじめは「全てのカテゴリー」用のセクションとコールバック関数を作成したが、UIがよくなかった(分かりにくかった)ので「表示するカテゴリー」欄の一番目のチェックボックスとして「全てのカテゴリー」チェックボックスを作成することに変更

コールバック関数にチェックボックスを追加

「全てのカテゴリー」チェックボックスが選択されているかで場合分け
選択されていればチェックボックスにチェックをつける
echoでチェックボックスとラベルを生成する

functions.php
$all_checked = isset($options['select_all_categories']) && $options['select_all_categories'] ? 'checked' : '';
echo '<label><input type="checkbox" id="select_all_categories" name="frontpage_options[select_all_categories]" ' . $all_checked . ' /> 全てのカテゴリー</label><br>';

サニタイズ追加と他のチェックボックスのチェックを外す

「全てのカテゴリー」と他のチェックボックスどちらもチェックがついているときにどちらが優先かわかりにくいので、「全てのカテゴリー」のチェックボックスが選択されたときに他のカテゴリーチェックボックスの選択を外すようにする
これで「全てのカテゴリー」が優先されていることがわかる

functions.php
 if (isset($input['select_all_categories'])) {
    $sanitized_input['select_all_categories'] = (bool) $input['select_all_categories'];
    if ($sanitized_input['select_all_categories']) {
        $sanitized_input['selected_category'] = array();
    }
}
じゃーにゃりすとじゃーにゃりすと

「全てのタグ」チェックボックス作成

「全てのカテゴリー」と同じ要領で進めていく

コールバック関数にチェックボックスを追加

「全てのタグ」チェックボックスにチェックが選択されているかを確認し、チェックをつける
echoでチェックボックスとラベルを生成

functions.php
$all_checked = isset($options['select_all_tags']) && $options['select_all_tags'] ? 'checked' : '';
echo '<label><input type="checkbox" name="frontpage_options[select_all_tags]" ' . $all_checked . ' /> 全てのタグ</label><br>';

サニタイズの追加と他のチェックボックスのチェックを外す

「全てのタグ」が選択されたとき他のチェックボックスの選択を外し、同時にチェックも外す

functions.php
if (isset($input['select_all_tags'])) {
    $sanitized_input['select_all_tags'] = (bool) $input['select_all_tags'];
    if ($sanitized_input['select_all_tags']) {
        $sanitized_input['selected_tags'] = array();
    }
}
じゃーにゃりすとじゃーにゃりすと

フロントページの記事の順番を変更する

記事の順番を昇順の更新日時順にする

管理画面でなくメインクエリであるかの確認

functions.php
function sort_postdata( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
}

クエリの変更

$query->set()を使用してクエリのパラメータを変更

$query->set( 'order', 'ASC' )は記事の並び順を昇順にしている
降順は'DESC'

$query->set( 'orderby', 'modified' )は記事を更新日時に並び替えている
デフォルトは'date'の日付順
'comment_count'がコメント数
'rand'がランダム など

$query->set( 'order', 'ASC' );
$query->set( 'orderby', 'modified' );

メインクエリが実行する前に変更

add_action( 'pre_get_posts', 'sort_postdata' )

まとめると

functions.php
function sort_postdata( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}
	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'modified' );
}

add_action( 'pre_get_posts', 'sort_postdata' );
じゃーにゃりすとじゃーにゃりすと

設定画面から投稿記事の順番を選択できる機能を実装する

複数選択ではないのでチェックボックスは適さず
ラジオボタンとプルダウンどちらにしようか考えたが、まずは4種類だけなのでラジオボタンで実装することにした
増える可能性も考えて今後はプルダウンにするかも

コールバック関数に並び順を選択するラジオボタンを追加する

オプション設定を取得

$options = get_option('frontpage_options');

配列を作る

今回は「投稿日時」「更新日時」「コメント数」「ランダム」の4つの並び順を選択できるようにする

$order_options = [
    'post_date'     => '投稿日時',
    'post_modified' => '更新日時',
    'comment_count' => 'コメント数',
    'rand'          => 'ランダム',
];

foreachでループを回してラジオボタンを生成し、選択されている場合はチェックをつける

$order_options配列に対してループさせる

foreach ($order_options as $value => $label)

選択されたものと$valueが一致している場合'checked' を設定する
これによって以前に選択されたものが選択された状態になる

$checked = isset($options['front_page_order']) && $options['front_page_order'] === $value ? 'checked' : '';

ラジオボタンとラベルの名前を表示させる

echo '<label><input type="radio" name="frontpage_options[front_page_order]" value="' . esc_attr($value) . '" ' . $checked . ' />' . esc_html($label) . '</label><br>';

全体

foreach ($order_options as $value => $label) {
    $checked = isset($options['front_page_order']) && $options['front_page_order'] === $value ? 'checked' : '';
    echo '<label><input type="radio" name="frontpage_options[front_page_order]" value="' . esc_attr($value) . '" ' . $checked . ' />' . esc_html($label) . '</label><br>';
}

コールバック関数に並び方向を選択するラジオボタンを追加する

デフォルトが「降順」なので「降順」を上に配置する

並び順と同じ流れで作る

配列を作り、foreachでループを回して、ラジオボタンとラベルの名前を表示させる

function front_page_order_direction_callback() {
    $options = get_option('frontpage_options');
    $direction_options = [
        'desc' => '降順',
        'asc'  => '昇順',
    ];

    foreach ($direction_options as $value => $label) {
        $checked = isset($options['front_page_order_direction']) && $options['front_page_order_direction'] === $value ? 'checked' : '';
        echo '<label><input type="radio" name="frontpage_options[front_page_order_direction]" value="' . esc_attr($value) . '" ' . $checked . ' />' . esc_html($label) . '</label><br>';
    }
}

サニタイズを追加する

並び替えオプションのサニタイズ

$inputオプションが送信かつ値が許可されたものの場合sanitize_text_field()関数を使って配列に格納する

if (isset($input['front_page_order']) && in_array($input['front_page_order'], ['post_date', 'post_modified', 'comment_count', 'rand'])) {
    $sanitized_input['front_page_order'] = sanitize_text_field($input['front_page_order']);
}

並び替え方向オプションのサニタイズ

値が'desc'または'asc'の場合のみ並び替えオプションのサニタイズと同じようにする

if (isset($input['front_page_order_direction']) && in_array($input['front_page_order_direction'], ['desc', 'asc'])) {
    $sanitized_input['front_page_order_direction'] = sanitize_text_field($input['front_page_order_direction']);
}

クエリを変更する

並び順のクエリの変更

フロントページの記事の並び順が選択されている場合'orderby'を選択されている値に設定する

if (isset($frontpage_options['front_page_order'])) {
    $query->set('orderby', $frontpage_options['front_page_order']);
}

並び方向順のクエリの変更

並び順と同様にフロントページの記事の並び方向順が選択されている場合'order'を選択されている値に設定する

if (isset($frontpage_options['front_page_order_direction'])) {
    $query->set('order', $frontpage_options['front_page_order_direction']);
}
じゃーにゃりすとじゃーにゃりすと

記事の並び順をラジオボタンからプルダウンに変更する

プルダウンメニューを作る

function front_page_order_callback()のラジオボタンの部分をプルダウンに変更

<select>タグをechoする

echo '<select name="frontpage_options[front_page_order]">';

foreachでループを回して<option>タグで囲み、オプションを追加していく

このとき現在の選択と$options['front_page_order']が同じ場合、選択されてある状態にする
エスケープも行う

foreach ($order_options as $value => $label) {
    $selected = isset($options['front_page_order']) && $options['front_page_order'] === $value ? 'selected' : '';
    echo '<option value="' . esc_attr($value) . '" ' . $selected . '>' . esc_html($label) . '</option>';
}

</select>をechoして閉じる

echo '</select>';
じゃーにゃりすとじゃーにゃりすと

コメントがある記事のみフロントページに表示する

設定画面にチェックボックスを作り、チェックがついてある場合コメントがある記事のみを表示させる

チェックボックスのフィールドを追加

カテゴリーとタグのチェックボックスの下にフィールドを生成

add_settings_field(
    'show_commented_posts_only',
    'コメントがある記事のみ表示',
    'show_commented_posts_only_callback',
    'frontpage-options',
    'front_page_settings_section'
);

チェックボックスのコールバック関数を作る

show_commented_posts_onlyが存在し、trueの場合$checkedtrueにする

function show_commented_posts_only_callback() {
    $options = get_option('frontpage_options');
    $checked = isset($options['show_commented_posts_only']) && $options['show_commented_posts_only'] ? 'checked' : '';
    echo '<input type="checkbox" name="frontpage_options[show_commented_posts_only]" value="1" ' . $checked . ' />コメントがある記事を表示する';
}

サニタイズを行う

if (isset($input['show_commented_posts_only'])) {
    $sanitized_input['show_commented_posts_only'] = (bool) $input['show_commented_posts_only'];
}

クエリの変更

get_posts()関数でコメントがある記事のIDを取得する

post_type

取得する投稿タイプを指定
記事のIDを取得したいのでpostを指定

post

普通の記事

page

固定ページ

attachment

画像、音声、動画などのメディアファイルの添付ファイル

revision

記事やページのリビジョン
リビジョンとは変更内容を記録したもの

ナビゲーションメニューの項目

custom_css

カスタムCSS

wp_block

Gutenbergのブロック

posts_per_page

取得する投稿の数を指定
-1にすることですべての投稿を取得することができる

fields

取得するフィールドを指定
ここでIDを指定する

post_status

取得する投稿の状態を指定
公開しているものだけを取得していのでpublishを指定

publish

公開されているもの

pending

公開待ち、保留中のもの

draft

下書き

future

予約投稿されているもの

private

ログインしているユーザーのみが見れる非公開のもの

trash

削除されたが、ゴミ箱に残っているもの

has_password

パスワード保護された投稿を含めるかどうかを指定

suppress_filters

フィルターを抑制するかどうかを指定
フィルターフックを一時的に無効にするためのパラメータ
クエリの結果に影響を与えるすべてのフィルタリングが無効化され、コメントがある記事のみを表示するためのクエリの構築を邪魔しないようにしている

no_found_rows

ページネーションに関連するSQLクエリを実行しないようにするもの

WPはクエリの実行時にページネーションの計算を行い、結果の総数を取得するためにSQLクエリを2回実行
1回目は実際のデータの取得
2回目はページネーションに関連する情報を取得
trueにすることで2回目を実行しなくなる

大量のデータがある場合やページネーションを使用しない場合はtrueにすることで時間短縮につながる

comment_count

コメントの数を表す
1にすることでコメントがある記事のみを抽出する

if (isset($frontpage_options['show_commented_posts_only']) && $frontpage_options['show_commented_posts_only']) {
    $posts_with_comments = get_posts([
        'post_type'      => 'post',
        'posts_per_page' => -1,
        'fields'         => 'ids',
        'post_status'    => 'publish',
        'has_password'   => false,
        'suppress_filters' => true,
        'no_found_rows' => true,
        'comment_count' => 1,
    ]);
    $query->set('post__in', $posts_with_comments);
}
じゃーにゃりすとじゃーにゃりすと

入力した数値以上のコメント数がある記事のみをフロントページに表示させる機能を作る

フィールドを追加

add_settings_field(
    'min_comment_count',
    '記事のコメント数',
    'min_comment_count_callback',
    'frontpage-options',
    'front_page_settings_section'
);

コールバック関数を追加

最低コメント数を入力する欄を生成
数値を入力していない場合はデフォルトの0件になるように(つまり全部表示されるように)した

function min_comment_count_callback() {
    $options = get_option('frontpage_options');

    if (isset($options['min_comment_count'])) {
        echo '<input type="number" name="frontpage_options[min_comment_count]" value="' . esc_attr($options['min_comment_count']) . '" />件以上';
    } else {
        echo '<input type="number" name="frontpage_options[min_comment_count]" value="0" />件以上';
    }
}

サニタイズを行う

absint()を使ってサニタイズをする
絶対値にして整数に変換されるため安全にデータを扱えるようになる

if (isset($input['min_comment_count'])) {
    $sanitized_input['min_comment_count'] = absint($input['min_comment_count']);
}

クエリの変更

intval()を使って入力されたデータを整数に変換する

文字列として取得されても整数に変換するため安心

$min_comment_count = intval($frontpage_options['min_comment_count']);

get_posts()を使ってすべての投稿を取得する

$all_posts = get_posts(['post_type' => 'post', 'posts_per_page' => -1]);

wp_count_comments()を使って各投稿のコメント数を取得する

approvedプロパティを使うことで承認済みコメントの数のみ取得できる

$comment_count = wp_count_comments($post->ID)->approved;

入力された数値以上の場合IDを配列に格納していく

if ($comment_count >= $min_comment_count) {
    $posts_with_enough_comments[] = $post->ID;
}

配列が空でない場合はそのIDを使ってクエリを変更

配列が空の場合は[0]をセットして何も表示しないようにする

0をセットしないと通常のコンテンツが表示されてしまうので注意が必要

if (!empty($posts_with_enough_comments)) {
    $query->set('post__in', $posts_with_enough_comments);
} else {
    $query->set('post__in', [0]);
}

全体のコード

if (isset($frontpage_options['min_comment_count'])) {
    $min_comment_count = intval($frontpage_options['min_comment_count']);
    $posts_with_enough_comments = [];

    $all_posts = get_posts(['post_type' => 'post', 'posts_per_page' => -1]);
    foreach ($all_posts as $post) {
        $comment_count = wp_count_comments($post->ID)->approved;
        if ($comment_count >= $min_comment_count) {
            $posts_with_enough_comments[] = $post->ID;
        }
    }

    if (!empty($posts_with_enough_comments)) {
        $query->set('post__in', $posts_with_enough_comments);
    } else {
        $query->set('post__in', [0]);
    }
}
じゃーにゃりすとじゃーにゃりすと

サムネイルが設定されている記事のみをフロントページに表示させる機能を追加する

チェックボックスのフィールドを追加

add_settings_field(
    'show_thumbnail',
    'サムネイルがある記事のみ',
    'show_thumbnail_callback',
    'frontpage-options',
    'front_page_settings_section'
);  

コールバック関数を追加

'show_thumbnail'が空、つまり設定されていないときはチェックがつかないようにした
さらにチェックボックスの生成も行った

function show_thumbnail_callback() {
    $options = get_option('frontpage_options');
    $checked = empty($options['show_thumbnail']) ? '' : 'checked';

    echo '<label><input type="checkbox" name="frontpage_options[show_thumbnail]" ' . $checked . ' /> サムネイルがある記事のみ表示する</label><br>';
}

サニタイズを行う

チェックボックスなので(bool)で値をtrue,falseのみにする

if (isset($input['show_thumbnail'])) {
    $sanitized_input['show_thumbnail'] = (bool) $input['show_thumbnail'];
}   

クエリの追加

サムネイルを表示するチェックボックスが有効になっている場合、クエリにmeta_queryを追加
サムネイルが存在するかをキーで確認している

if (!empty($frontpage_options['show_thumbnail'])) {
    $query->set('meta_query', [
        [
            'key'     => '_thumbnail_id',
            'compare' => 'EXISTS',
        ],
    ]);
}
じゃーにゃりすとじゃーにゃりすと

表示する記事を投稿者で選択できるようにする

投稿者を選択して、その投稿者が書いた記事のみをフロントページに表示する機能を追加する
セレクトボックスで実装
セレクトボックスの1番目(デフォルト)は「すべての投稿者」にする

セレクトボックスのフィールドを追加

add_settings_field(
    'selected_author_field',
    '投稿者',
    'selected_author_callback',
    'frontpage-options',
    'front_page_settings_section'
);

コールバック関数を追加

get_users()を使って投稿者一覧を取得

get_users()

whoのパラメーター

  • authors 投稿者
  • editors 編集者
  • contributors 投稿者または編集者
  • subscribers 購読者

投稿者一覧をforeachでループしながらセレクトボックスをechoする

選択した投稿者とループして表示させた投稿者と一致する場合、"selected"属性を追加する

function selected_author_callback() {
    $options = get_option('frontpage_options');
    $authors = get_users(['who' => 'authors']);

    echo '<select name="frontpage_options[selected_author]">';
    echo '<option value="">すべての投稿者</option>';
    foreach ($authors as $author) {
        $selected = isset($options['selected_author']) && $options['selected_author'] == $author->ID ? 'selected' : '';
        echo '<option value="' . esc_attr($author->ID) . '" ' . $selected . '>' . esc_html($author->display_name) . '</option>';
    }
    echo '</select><br>';
}

サニタイズを追加

absint()で整数に変換

if (isset($input['selected_author'])) {
    $sanitized_input['selected_author'] = absint($input['selected_author']);
}

クエリの変更

author__inで投稿者のクエリを変更

if (isset($frontpage_options['selected_author'])) {
    $query->set('author__in', $frontpage_options['selected_author']);
}
じゃーにゃりすとじゃーにゃりすと

投稿者を複数選択できるようにする

セレクトボックスからチェックボックスに変更して複数選択できるようにする
チェックボックスの1つ目を「全ての投稿者」にして、選択した場合他のチェックボックスからチェックを外すようにする

コールバック関数を変更

はじめに「全ての投稿者」のチェックボックスを生成
その後、foreachで投稿者を回しチェックボックスを生成する
全ての投稿者とその他のチェックボックスは別にしておく

function selected_author_callback() {
    $options = get_option('frontpage_options');
    $authors = get_users(['who' => 'authors']);

    $all_checked = isset($options['select_all_authors']) && $options['select_all_authors'] ? 'checked' : '';
    echo '<label><input type="checkbox" id="select_all_authors" name="frontpage_options[select_all_authors]" ' . $all_checked . ' />全ての投稿者</label><br>';
    echo '<div>';

    foreach ($authors as $author) {
        $checked = isset($options['selected_author']) && in_array($author->ID, $options['selected_author']) ? 'checked' : '';
        echo '<label><input type="checkbox" name="frontpage_options[selected_author][]" value="' . esc_attr($author->ID) . '" ' . $checked . ' />' . esc_html($author->display_name) . '</label><br>';
    }
    echo '</div>';
}

サニタイズを変更

投稿者が選択されているときは整数に変更し、「全ての投稿者」が選択されているときはboolに変更
さらに投稿者の配列を空にし、チェックを外す

if (isset($input['selected_author'])) {
    $sanitized_input['selected_author'] = array_map('intval', $input['selected_author']);
}

if (isset($input['select_all_authors'])) {
    $sanitized_input['select_all_authors'] = (bool) $input['select_all_authors'];

    if ($sanitized_input['select_all_authors']) {
        $sanitized_input['selected_author'] = [];
    }
}
じゃーにゃりすとじゃーにゃりすと

デフォルトに戻す機能を追加する

リセットチェックボックスを作成し、チェックがついてある状態で変更ボタンを押すと他の項目を初期値に戻すようにする

チェックボックスを設置するフィールドを追加

add_settings_field(
    'reset',
    '初期値に戻す',
    'reset_callback',
    'frontpage-options',
    'front_page_settings_section'
);

チェックボックスを生成するコールバック関数を追加

function reset_callback() {
    echo '<label><input type="checkbox" name="frontpage_options[reset]" value="1"> デフォルトにリセットする</label>';
}

サニタイズで条件分岐

リセットチェックボックスにチェックがついてある場合には配列を空にして返す
以下のように1つ1つの項目に初期値を設定し直す事も考えたが、項目が増えるたびに追加していくのは管理が大変なのでやめた

$sanitized_input = [
            'posts_per_page' => 5,
            'selected_category' => [],
            'selected_tags' => [],
            'front_page_order' => 'post_date',
            'front_page_order_direction' => 'desc',
            'start_date' => '',
            'end_date' => '',
            'min_comment_count' => 0,
            'show_thumbnail' => false,
            'selected_author' => [],
            'select_all_authors' => false
            'select_all_categories' => false,
            'select_all_tags' => false,
        ];
        return $sanitized_input;

元々デフォルト値を設定してあるのもあって今回は以下のようにした
チェックボックスにチェックがついていない場合には今まで通りのサニタイズを行う

function frontpage_options_sanitize($input) {

    if (isset($input['reset'])) {
        $sanitized_input = [];
		return $sanitized_input;
	} else {
		$sanitized_input = [];
		
		if (isset($input['posts_per_page'])) {
			$sanitized_input['posts_per_page'] = absint($input['posts_per_page']);
		}

                // 以下同様にサニタイズ
じゃーにゃりすとじゃーにゃりすと

デフォルトに戻したあと(デフォルト)がついてあるチェックボックスにチェックが付く状態にする

デフォルトに戻すをしたあとにすべてのチェックボックスからチェックが消えているとリセットされたかが分かりにくいので、デフォルトにチェックがつくように修正する

サニタイズを修正

今後新たな項目を増やすたびにここの項目も追加していかないといけないが、ユーザーにとってのわかりやすさのほうが大事

if (isset($input['reset'])) {
    $sanitized_input = [
        'posts_per_page' => 5,
        'select_all_categories' => true,
        'selected_category' => [],
        'select_all_tags' => true,
        'selected_tags' => [],
        'front_page_order' => 'post_date',
        'front_page_order_direction' => 'desc',
        'start_date' => '',
        'end_date' => '',
        'min_comment_count' => 0,
        'show_thumbnail' => false,
        'select_all_authors' => true,
        'selected_author' => [],
    ];
    return $sanitized_input;
} else {
じゃーにゃりすとじゃーにゃりすと

投稿タイプを選択できる機能を作る

はじめは全部の投稿タイプをforeachで回して選択できるようにしようと思ったが、メディアなどはフロントページに表示することはないと思ったので、まずは「投稿」と「固定ページ」を選択できるようにする

固定ページを選んだ場合はどの固定ページを表示するか選べるようにする

フィールドを2つ追加する

1つ目が「投稿」か「固定ページ」を選べるセレクトボックス(プルダウン)
2つ目がどの固定ページを表示するかのセレクトボックス

add_settings_field(
    'front_page_display',
    '投稿か固定ページか',
    'front_page_display_callback',
    'frontpage-options',
    'front_page_settings_section'
);
add_settings_field(
    'selected_page',
    '表示する固定ページ',
    'selected_page_callback',
    'frontpage-options',
    'front_page_settings_section'
);

投稿か固定ページかを選ぶコールバック関数追加

postsに「投稿」pageに「固定ページ」を定義
foreachで回しながらセレクトボックスを生成
エスケープ処理も行う

function front_page_display_callback() {
    $options = get_option('frontpage_options');
    $display_options = [
        'posts' => '投稿',
        'page'  => '固定ページ',
	];
    $current_value = isset($options['front_page_display']) ? $options['front_page_display'] : '';
    echo '<select name="frontpage_options[front_page_display]">';
    foreach ($display_options as $value => $label) {
        $selected = selected($current_value, $value, false);
        echo '<option value="' . esc_attr($value) . '" ' . $selected . '>' . esc_html($label) . '</option>';
    }
    echo '</select>';
}

どの固定ページを表示するかを選ぶコールバック関数追加

すべての固定ページを取得し、IDで識別
上と同じようにforeachで回しながらセレクトボックスを生成し、エスケープ処理も行う

function selected_page_callback() {
    $options = get_option('frontpage_options');
    $pages = get_pages();
    $selected_page = isset($options['selected_page']) ? $options['selected_page'] : '';
    echo '<select name="frontpage_options[selected_page]">';
    echo '<option value="">選択してください</option>';
    foreach ($pages as $page) {
        $selected = selected($selected_page, $page->ID, false);
        echo '<option value="' . esc_attr($page->ID) . '" ' . $selected . '>' . esc_html($page->post_title) . '</option>';
    }
    echo '</select>';
}

リセット時の挙動追加

デフォルトにリセットしたときに「投稿」を選択
固定ページはどれも選んでない状態にしたいので0にしておく

if (isset($input['reset'])) {
    $sanitized_input = [
        'front_page_display' => 'posts',
        'selected_page' => 0,
    ];
     return $sanitized_input;

サニタイズの追加

if (isset($input['front_page_display'])) {
    $sanitized_input['front_page_display'] = sanitize_text_field($input['front_page_display']);
}
	
if (isset($input['selected_page'])) {
    $sanitized_input['selected_page'] = absint($input['selected_page']);
}

クエリの変更

「投稿」を選択している場合は投稿一覧を表示するようにクエリ変更
「固定ページ」を選択している場合は直接固定ページを表示したいので、クエリを固定ページに変更し、IDから表示したい固定ページを表示させるようにした

if (isset($frontpage_options['front_page_display'])) {
    if ($frontpage_options['front_page_display'] === 'posts') {
        $query->set('post_type', 'post');
    } elseif ($frontpage_options['front_page_display'] === 'page' && isset($frontpage_options['selected_page'])) {
        $query->is_home = false;
        $query->is_page = true;
        $query->set('post_type', 'page');
        $query->set('page_id', $frontpage_options['selected_page']);
    }
}
じゃーにゃりすとじゃーにゃりすと

並び順の方向をセレクトボックスにする

見た目の統一感を出すためにラジオボタンからセレクトボックスに変更する

コールバック関数を変更

ラジオボタンになっていたところをセレクトボックスを生成するように変更
セレクトボックスでは最初に何が選択されているかわかりやすいため、降順のところの(デフォルト)の文字は消す

function front_page_order_direction_callback() {
    $options = get_option('frontpage_options');
    $direction_options = [
        'desc' => '降順',
        'asc'  => '昇順',
    ];
    echo '<select name="frontpage_options[front_page_order_direction]">';
    foreach ($direction_options as $value => $label) {
        $selected = isset($options['front_page_order_direction']) && $options['front_page_order_direction'] === $value ? 'selected' : '';
        echo '<option value="' . esc_attr($value) . '" ' . $selected . '>' . esc_html($label) . '</option>';
    }
    echo '</select><br>';
}
じゃーにゃりすとじゃーにゃりすと

「投稿か固定ページか」で「固定ページ」を選択したときのみ「表示する固定ページ」を選択できるようにする

「投稿」を選択したときにどの固定ページを表示するかの欄があるのは変なので、固定ページを選択したときのみ表示するようにする

JSファイルを追加

「投稿か固定ページか」「表示する固定ページ」2つのセレクトボックスを取得

const selectedPageSelect = document.querySelector('select[name="frontpage_options[selected_page]"]').closest('tr');
const frontPageDisplaySelect = document.querySelector('select[name="frontpage_options[front_page_display]"]');

「投稿か固定ページか」が変更(chenge)されたときに実行される関数を追加

frontPageDisplaySelect.addEventListener('change', function()

セレクトボックスの現在の値を取得

const selectedOption = this.value;

セレクトボックスの値が固定ページであれば「 表示する固定ページ」を表示

if (selectedOption === 'page') {
    selectedPageSelect.style.display = 'table-row';
} else {
    selectedPageSelect.style.display = 'none';
}

ページを読み込んだときにもセレクトボックスの値が固定ページかどうかを判断し、表示するかを決める

const initialDisplayOption = frontPageDisplaySelect.value;
if (initialDisplayOption === 'page') {
    selectedPageSelect.style.display = 'table-row';
    } else {
    selectedPageSelect.style.display = 'none';
}

全体

document.addEventListener('DOMContentLoaded', function() {
    const selectedPageSelect = document.querySelector('select[name="frontpage_options[selected_page]"]').closest('tr');
    const frontPageDisplaySelect = document.querySelector('select[name="frontpage_options[front_page_display]"]');

    frontPageDisplaySelect.addEventListener('change', function() {
        const selectedOption = this.value;
        if (selectedOption === 'page') {
            selectedPageSelect.style.display = 'table-row';
        } else {
            selectedPageSelect.style.display = 'none';
        }
    });

    const initialDisplayOption = frontPageDisplaySelect.value;
    if (initialDisplayOption === 'page') {
        selectedPageSelect.style.display = 'table-row';
    } else {
        selectedPageSelect.style.display = 'none';
    }
});

JSファイルの読み込み

今回はjQueryを使わないので第3引数の依存関係の配列は空にしておいた

function enqueue_custom_admin_script() {
    wp_enqueue_script('custom-admin-script', get_template_directory_uri() . '/js/custom-admin-script.js', [], '', true);
}
add_action('admin_enqueue_scripts', 'enqueue_custom_admin_script');
じゃーにゃりすとじゃーにゃりすと

投稿を選んでいる場合は「表示する固定ページ」のセレクトボックスを半透明にする

投稿が選ばれている(固定ページが選ばれていない)ときは「表示する固定ページ」のセレクトボックスを非表示にしていたが、それでは固定ページを選んだときに固定ページを選択できるのかどうかが初めて使う人にはわからないと感じたので、非表示ではなく半透明にしておくことにした

セレクトボックスを非表示から半透明にする

半透明になっているときにセレクトボックスを選択・変更できるのはよくないのでselectedPageSelect.style.pointerEvents = 'none';でクリックできないようにした

frontPageDisplaySelect.addEventListener('change', function() {
    const selectedOption = this.value;
    if (selectedOption === 'page') {
        selectedPageSelect.style.opacity = '1';
        selectedPageSelect.style.pointerEvents = 'auto';
    } else {
        selectedPageSelect.style.opacity = '0.5';
        selectedPageSelect.style.pointerEvents = 'none';
    }
});

初めて読み込みをしたときも同様にコード変更

const initialDisplayOption = frontPageDisplaySelect.value;
if (initialDisplayOption === 'page') {
    selectedPageSelect.style.opacity = '1';
    selectedPageSelect.style.pointerEvents = 'auto';
} else {
    selectedPageSelect.style.opacity = '0.5';
    selectedPageSelect.style.pointerEvents = 'none';
}
じゃーにゃりすとじゃーにゃりすと

「投稿か固定ページか」のセレクトボックスをラジオボタンに変更

次のセレクトボックスを半透明にしてることもあり、選択肢が2つなのでラジオボタンのほうが見やすいと思った

セレクトボックスをラジオボタンに変更

function front_page_display_callback() {
    $options = get_option('frontpage_options');
    $display_value = isset($options['front_page_display']) ? $options['front_page_display'] : 'posts';
    ?>
    <label><input type="radio" name="frontpage_options[front_page_display]" value="posts" <?php checked($display_value, 'posts'); ?>>投稿</label><br>
    <label><input type="radio" name="frontpage_options[front_page_display]" value="page" <?php checked($display_value, 'page'); ?>>固定ページ</label>
    <?php
}

JSもラジオボタン仕様に変更

document.addEventListener('DOMContentLoaded', function() {
    const selectedPageSelect = document.querySelector('select[name="frontpage_options[selected_page]"]').closest('tr');
    const frontPageDisplayRadios = document.querySelectorAll('input[name="frontpage_options[front_page_display]"]');

    frontPageDisplayRadios.forEach(function(radio) {
        radio.addEventListener('change', function() {
            const selectedOption = this.value;
            if (selectedOption === 'page') {
                selectedPageSelect.style.opacity = '1';
                selectedPageSelect.style.pointerEvents = 'auto';
            } else {
                selectedPageSelect.style.opacity = '0.5';
                selectedPageSelect.style.pointerEvents = 'none';
            }
        });
    });

    const initialDisplayOption = document.querySelector('input[name="frontpage_options[front_page_display]"]:checked').value;
    if (initialDisplayOption === 'page') {
        selectedPageSelect.style.opacity = '1';
        selectedPageSelect.style.pointerEvents = 'auto';
    } else {
        selectedPageSelect.style.opacity = '0.5';
        selectedPageSelect.style.pointerEvents = 'none';
    }
});
じゃーにゃりすとじゃーにゃりすと

保存ボタンを押したときにトップにスクロールしないようにする

はじめはトップにスクロールするイベントをリセットする方法を試したが、うまくいかず保存ボタンを押して時の位置を保存しておいて保存の処理後にその位置までスクロールすることにした

ボタンを変更

保存ボタンを押したときに今のスクロール位置を保存するsaveScrollPosition()関数を実行するように変更

<input type="submit" value="保存" onclick="saveScrollPosition()">

保存ボタンを押したときに今のスクロール位置を保存する関数を追加

スクロール位置を取得し、ローカルストレージに保存

ローカルストレージはページが閉じられても保存されたまま

function saveScrollPosition() {
    const scrollPosition = window.scrollY;
    localStorage.setItem('scrollPosition', scrollPosition);
}

ローカルストレージからスクロール位置を取得し、スクロール位置が保存されてる場合にスクロールする

const scrollPosition = localStorage.getItem('scrollPosition');
if (scrollPosition) {
    window.scrollTo(0, scrollPosition);
}

保存ボタンが押されたときにsaveScrollPosition()関数を呼び出す

const submitButton = document.querySelector('input[type="submit"]');
submitButton.addEventListener('click', saveScrollPosition);
じゃーにゃりすとじゃーにゃりすと

保存ボタンにCSSを追加

機能を追加したことでボタンに対するデフォルトのCSSがなくなったので、新たにCSSを追加する

ボタンを青色にし、大きくする

button-primaryを追加することでボタンは青色に文字は白色になる

プライマリは「最も重要な」という意味
メインがプライマリでサブがセカンダリ

button-largeを追加することでボタンが大きめになる

  • button-small 小さめ
  • button-mideum 中くらい
  • button-large 大きめ
<... class="button button-primary button-large">