🤤

WordPressのテーマを作る羽目になったWebエンジニアへ

2022/04/27に公開

はじめに

Webエンジニアの皆さん、日々の業務お疲れ様です。皆さんは、さぞや楽しいエンジニアライフを送っていることでしょう。最近は技術の進歩も落ち着いてきましたので、Rustなんかに手を出して、先行者利益を目論んだりしているのではないでしょうか。

さて、そんな楽しい中、たまにやってくる何とも言い難い案件というのが「WordPressのテーマ作成」です。普段、大規模開発や自社サービスに携わっている方は、WordPressに関わることはないかもしれませんが、請負業務やフリーランスの方には、ふと湧いてくる案件でもあります。そして、何かと言い訳を付け、できる限りWordPress関連の案件は避けているのではないでしょうか。

なぜ、エンジニアはWordPressを嫌うのか

エンジニアというのはWordPressを嫌う傾向にありますが、それは何故でしょうか。

  • プライドがゆるさない
    WordPress界隈は、初心者が非常に多いです。初心者というか、プログラミングができない人も沢山います。一方、エンジニアと呼ばれる人は、膨大な(本当に膨大な)時間を技術習得に費やして今があると思います。WordPressがよかろうが悪かろうが、初心者と同じような仕事をするというのは、やはりプライドが邪魔をするのではないでしょうか。
  • やることがつまらない
    WordPressのテーマ作成は、予めWordPressが用意している関数を設置していく作業が中心となります。エンジニアにとって、これはプログラミングと言えないのかもしれません。
  • セキュリティ的に不安
    WordPressは、度たび脆弱性の問題が発生します。クライアントに納品する限り、そのようなシステムはできる限り使いたくはありません。
  • PHPが嫌い
    PHPも初心者が非常に多いプログラム言語です。PHPの仕様自体が嫌いな人も多いかもしれませんが、これもプライドが邪魔をすることがあるのではないでしょうか。

他にも色々あると思いますが、要はWordPressとPHPに悪い印象を持っているということです。

WordPressとPHPに対しての誤解

僕がPHPを書いていてよく思うのが、節操のない言語だなということです。思想などは感じられず、流行っていたり便利なものは取り入れるスタンスです。この考え方は、何故かPHPで作られたシステムにも受け継がれ、Laravel(← Rails)やcomposer(← npm)、そしてWordPress(← Movable Type)なども、やはり節操のない感じがします。同じく僕が節操のないフレームワークと感じるVue(← React)がLaravelに標準搭載されていたのも、そのような相性がいいからではないでしょうか。

ですがこれは、悪い事ではありません。「いいものは取り入れる」、これ自体が素晴らしい思想になっています。技術者特有の思想やこだわり、またはプライドは、時には業務やシェア争いの妨げとなります。最初にいいものを作って後で追いこされる、どこかの国のビジネスのようですね。そもそも、何のためにシステム化をしているかというと、効率化です。したがって、とことんまで効率化を考えることは、とてもいいことです。
※ 効率化だけでいいとは言っていません。思想やこだわりは大事です。

つまり、WordPressで済むならWordPressでいいということです。わざわざシステムを組んだり、フロントエンドに分けたりする必要はありません。そして、WordPressとPHPを毛嫌いしているエンジニアの皆さんは、恐らく一昔前のWordPressとPHPを見ています。両者とも、現在ではセキュリティも改善され、機能的にも使いやすく、洗練されてきています。

人生は長いようで短いです。余計なことに時間をかけず、最短ルートで突っ走り、本当にやらなければいけないことに時間をかけましょう!

WordPressのテーマを作る羽目になったWebエンジニアへ

しかし、だからといって、今からWordPressのお作法や関数の仕様を学んでいくのも気が引けると思います。そこで今回は、テーマ作成の一連の流れを通じて、これさえ知っておけば何とかなる箇所を説明しています。そして、順番通りに進めて行けばテーマが完成するような構成になっています。 多少、荒削りではありますが、これを通じてWordPressのテーマ構築を体験してください。

今回は膨大な量となりましたが、記事として無料で公開しました。そして、JavaScriptのBookを有料で公開しております(DOM編は無料)ので、もしよければ、気持ち程度でJavaScriptのBookを読んでやってくださいませ。WordPressでもJavaScriptを使用するシーンが出てきますので。
https://zenn.dev/antez/books/568dd4d86562a1

WordPressのインストール

Local

まずは、ローカル環境でWordPressを操作する際、仮想環境でWordPressの環境を整える、ローカルPC内に直接環境を構築するなどの方法があります。どれも面倒くさそうですが、Flywheelという会社が提供しているLocalというソフトウェアを使用すると、とても簡単にWordPressの環境を準備でき、複数のページを簡単に追加、削除できます。

Localは以下からダウンロードして、インストールを行ってください。
https://localwp.com/

Localを起動したら、中央にCREATE A NEW SITEというボタンが表示されていますのでクリックします。次に、制作するウェブサイトの名前を入力してCONTINUEをクリックします。すると、環境の内容を設定する画面に移りますので、特に問題がなければPreferredを選択してCONTINUEをクリックしてください。最後に管理者のユーザー名、パスワード、メールアドレスを入力してADD SITEをクリックすると、次の場所に各環境ごとのディレクトリが構築されます。

PC パス
Mac /ユーザー/ユーザー名/Local Sites
Win C:\Users\ユーザー名\Local Sites

構築が完了したら、右上のADMINボタンで管理ページ、OPEN SITEボタンでウェブサイトが立ち上がります。その後は、左下の+ボタンで環境を追加することが可能です。

さて、このLocalですが、各ディレクトリごとに開発環境を作るとはどういう事でしょうか。Webエンジニアなら大体想像がつくと思いますが、一応説明しておきます。

まず、このLocalを提供しているFlywheelですが、WordPress専用のホスティングサービスを提供しています。これは、開発サーバーで編集作業などを行い、完成したら簡単に本番サーバーに移行できるというものです。この仕組みをローカルで実現しているのがLocalとなります。

Localは各ディレクトリごとにVirtualBoxで仮想環境を構築しています。したがって、、Localをインストールすると、一緒にVirtualboxもインストールされます。そして、この仮想環境はDockerで構築されます。この仕組みにより、各ディレクトリごとにApache、Nginxなど別環境を構築することができます。また、Localは仮想環境を構築すると、PC内のhostsファイルを書き換えます。これにより、sample.localなどのURLでアクセス可能となります。

サーバー

本番化の際に、サーバーにインストールする方法も説明しておきます。サーバーにnginxかapache、PHPとMySQLがインストールされていることを前提として、説明します。また、ここでは割愛しますが、WordPressで使用するデータベースとユーザーをMySQLで作っておいてください。

まずは、WordPressを公式サイトからダウンロードしましょう。

$ wget https://ja.wordpress.org/latest-ja.tar.gz

ダウンロードした圧縮ファイルを解凍します。

$ tar zxvf latest-ja.tar.gz

圧縮ファイルを解凍するとwordpressというディレクトリが作成されるので、公開するディレクトリに移動します。

$ mv wordpress/ /var/www/html/wordpress/

圧縮ファイルは必要ないので削除します。

$ rm latest-ja.tar.gz

wordpressディレクトリの所有者とグループをnginx(もしくはapache)に変更し、nginxユーザーに対して、wordpressディレクトリの読み書き権限を与えます。

$ chown nginx:nginx /var/www/html/wordpress -R &&\
$ chmod -R u=rwX,g=rX,o=rX /var/www/html/wordpress

そのディレクトリに移動します。

$ cd /var/www/html/wordpress

wp-config-sample.phpwp-config.phpに変更します。

$ mv wp-config-sample.php wp-config.php

wp-config.phpを編集します。

$ vi wp-config.php

# [追加]pluginインストールなどをFTP不要にする
define('FS_METHOD', 'direct');

# データベース名
define('DB_NAME', 'データベース名');

# データベースユーザー名
define('DB_USER', 'ユーザー名');

# データベースパスワード
define('DB_PASSWORD', 'パスワード');

# セキュリティ設定。以下で生成されるシークレットキーに置き換え
# https://api.wordpress.org/secret-key/1.1/salt/
define('AUTH_KEY',         'put your unique phrase here');
define('SECURE_AUTH_KEY',  'put your unique phrase here');
define('LOGGED_IN_KEY',    'put your unique phrase here');
define('NONCE_KEY',        'put your unique phrase here');
define('AUTH_SALT',        'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT',   'put your unique phrase here');
define('NONCE_SALT',       'put your unique phrase here');

# データベース内のテーブルプレフィックス(接頭辞)
$table_prefix  = 'wp_';

http(s)://ドメイン名/wp-admin/install.phpにアクセスをして、インストールを行ってください。

テンプレート階層

WordPressでは、そのページごとに表示されるテンプレートファイルが決まっています。そして、そのページに紐づくテンプレートファイルは1つではなく、複数のテンプレートファイルが優先順位の下で紐づくようになっています。つまり、優先順位の高いテンプレートが存在しなければ、次のテンプレートが指定されます。これは、ページに対してテンプレートが階層になっていることから、テンプレート階層と呼ばれ、WordPressのテーマ開発には非常に重要な仕組みとなります。

https://wpdocs.osdn.jp/テンプレート階層

例えば、トップページではfont-page.php優先順位が最も高くなり、次はWordPressの設定 > 表示設定で設定した値によってテンプレートファイル名が異なります。そして次にhome.phpが映され、最後にindex.phpが反映されます。

なお、どのページも最終的にはindex.phpが反映されます。つまり、WordPressは極端に言うとindex.phpさえあれば、全てのページを表示させることができます。

個別ページ

WordPressで内容が表示されるページを個別ページといい、個別投稿ページ固定ページに分かれます。ブログの記事のようなページが個別投稿ページで、会社概要などの固定されたページが固定ページとなり、標準では以下のような違いがあります。

個別投稿ページ 固定ページ
一覧ページ 作られる 作れない
カテゴリーやタグ 使える 使えない
ページの親子関係 作れない 作れる

個別投稿ページは、ブログ記事や新着情報などのように、次々とページを追加していく傾向があり、固定ページは、会社概要やお問い合わせページなどのように、ページを固定して使い続けるという傾向があります。

テーマ作成の準備

テストデータのインポート

テーマの表示を確認するためには、ある程度の数の投稿が必要になるのですが、これはマユニットテストデータを使用すると便利です。まずは、以下のGithubからデータをダウンロードしましょう。下が日本語のデータとなります。
https://github.com/WPTRT/theme-unit-test
https://github.com/jawordpressorg/theme-test-data-ja

wordpress-theme-test-data-ja.xmlがダウンロードされますので、ツール > インポート > WordPressから今すぐインストールをクリックしてインポーターをインストールしてください。インストール後、インポーターの実行をクリックして、wordpress-theme-test-data-ja.xmlをインストールしてください。すると、投稿者の割り当て設定の画面が出てきますので、とりあえずはそのまま実行をクリックしてください。テストデータがインポートされるのですが、メディアのインポートに失敗しましたと多く表示されます。これは画像のインポートができていないという意味ですので、特に問題はありません。投稿を確認すると、数多くの投稿がインポートされているのが確認できます。これらの投稿は、タイトルの文字数が極端に長いものなど、表示ミスに繋がりやすい投稿となっております。

テーマの配置

管理画面の外観 > テーマを見てみると、既にいくつかのテーマがインストールされています。そして、これらのテーマはLocalを使用している場合、標準では以下のディレクトリ内に格納されています。

PC パス
Mac /ユーザー/ユーザー名/Local Sites/作成したWebサイト名/app/public/wp-content/themes
Win C:\Users\ユーザー名\Local Sites\作成したWebサイト名\app\public\wp-content\themes

ようは、wp-content/themesに格納されているということです。themesディレクトリ内を確認すると、インストールされているテーマらしいディレクトリが確認できます。では、themesディレクトリ内に例えばsampleなどと適当なディレクトリを設置してみましょう。そして、管理画面の外観 > テーマを見てみると、左下に壊れているテーマと表示されています。つまり、WordPressは themesディレクトリにディレクトリを設置するだけでテーマとして認識します。 ただし、壊れているテーマとなっています。そして、その下にはスタイルシートが見つかりません。という忠告が表示されています。

では、忠告通りに先ほど作成したsampleディレクトリに、style.cssを追加しましょう。すると忠告が次のように変更されています。

テンプレートが不足しています。独立したテーマには index.php テンプレートファイルが必要です。
子テーマでは style.css スタイルシートにテンプレートヘッダーが必要です。

index.phpテンプレートファイルが必要だとのことですので、同じくsampleディレクトリに、index.phpを追加しましょう。すると、インストールされているテーマの一覧に、ディレクトリ名のテーマとして同じように表示されています。そして、有効化ボタンも設置されていますので、クリックしてみましょう。有効化されるのですが、index.phpにはなにも記述していませんので、ブラウザー上では何も表示されません。

つまり、WordPressはthemesディレクトリにディレクトリを設置し、その中にindex.phpとstyle.cssが存在すればテーマとして成り立つということです。

index.php

index.phpはテンプレート階層の最下層のテンプレートとなり、PHPですのでここに記載したHTMLはそのまま表示されます。つまり、通常のPHPとして作成するのですが、WordPress独自の関数が使用できるという特徴があります。例えば、次のように記述すると最新記事のタイトルが取得され表示されます。

<h1><?php the_title(); ?></h1>

style.css

style.cssには、もちろんCSSを設定してテンプレートから読み込むことができます。ただし、CSSの設定は特にstyle.cssでなければいけないという決まりはなく、別のCSSファイルで設定しても問題はありません。では、なぜstyle.cssが必要かといいますと、style.cssはテーマの設定も担っているからです。例えば、次のようにコメントとしてTheme Nameを設定してください。

/*
Theme Name: Sample Theme
*/

管理画面でテーマの名前が変更されます。このように、style.cssはテーマの各設定を行うことが可能です。設定できる項目は以下となります。全て設定する必要はなく、必要なものだけ設定すればいいです。ただし、配布する場合は日本語を使用できませんので、英語で設定してください。

Theme Name テーマの名前
Theme URI テーマの配布元
Author 作成者
Author URI 作成者のURL
Description 説明
Version バージョン
License ライセンス
License URI ライセンスのURI
Tags タグ
Text Domain テーマのドメイン名。

screenshot.png

カレントディレクトリにscreenshot.pngという画像ファイルを設置すると、テーマ一覧に画像が表示されます。なお、スクリーンショットの推奨サイズは、幅1200px・高さ900pxとなっております。

子テーマ

今回は使用しませんが、子テーマとは親テーマから設定を引き継ぐテーマです。子テーマは、style.cssTemplateを設定します。

/*
Theme Name: Sample Theme
Author: ANTEZ
Version: 1.0.0
Template: twentytwenty
*/

Templateは、親テーマとなるテーマのディレクトリ名を設定します。これにより、それが親テーマとして設定されます。そのため、index.phpが存在しなくてもエラーにはなりません。そして、テーマを開くと「これはxxxxxxの子テーマです。」とのメッセージが表示されています。

子テーマを有効化すると、親テーマを引き継いでいますので、その内容が表示されます。ただし、スタイルシートは引き継がれませんので、何らかの方法で引き継ぐ必要があります。例えば、親テーマのstyle.cssの冒頭の設定コメント以外を子テーマのstyle.cssにコピペします。また、子テーマは親テーマを上書きしたいテーマを新規作成することにより、カスタマイズします。

テンプレートの準備

さて、テーマの準備が整いましたので、ここからindex.phpを作成し、必要ならば他のファイルも作成していくのですが、今回はHTMLとCSSの説明はできるだけ省きたいです。そこで、WordPressのテーマ作成によく使用される、Clean Blogというテンプレートをベースに構築していきたいと思います。これは、Start BootstrapというBootstrapのテンプレートを扱うサイトからダウンロードできます。
https://startbootstrap.com/theme/clean-blog

ダウンロードをしたら、現在のsampleディレクトリは削除して、Clean Blogのディレクトリ名をsampleに変更して、themesディレクトリ内に設置してください。

style.cssを、例えば以下のように記述して設置します。

/*
Theme Name: Sample Theme
Author: TEST
Description: This is a my theme.
Version: 1.0
*/

index.htmlindex.phpに変更すると、テーマとして認識されるようになりますので、有効化しましょう。ただし、相対パスの指定が合っていないため、スタイルが効いていません。とりあえずは次のように絶対パスで指定してください。後ほど、適切な設定に変更します。

<link href="//mysite.local/wp-content/themes/sample/css/styles.css" rel="stylesheet" />

なお、https://fonts.googleapis.com/などとURL指定している箇所は、httpsが変更される可能性があります。このような場合、//から指定するとその時のプロトコルで読み込まれます。

<link href='//fonts.googleapis.com/css?...' rel='stylesheet' type='text/css'>

スターターテーマ

先ほどダウンロードをしたテンプレートは、あくまでもBootstrapのテンプレートですので、WordPressとは関係のないものです。したがって、テーマ作成的には、ゼロベースから構築するのと変わりません。ただし、毎回ゼロベースから構築するのは大変です。そこで、今回は使用しませんが、スターターテーマという基本的な構造が予め用意されたテーマをもとに作成すると便利です。これは、時間の短縮になるのは勿論ですが、テーマの構成の学習にも繋がります。

スターターテーマは沢山存在しますが、s(UNDERSCORES)というWordPress開発元であるAutomatticによるスターターテーマがお勧めです。シンプルなスターターテーマとなっており、Twenty NineteenやBusinessPressもこれをもとに作成されています。

公式サイトにアクセスして、Theme Nameに作成したいテーマ名を入力しGENERATEをクリックすると、テーマをZIPファイルをダウンロードできます。ダウンロードが完了したら、テーマ > 新規追加からテーマのアップロードボタンをクリックして、ダウンロードしたZIPファイルを選択してください。すると、スターターテーマがインストールされますので、有効化をしてください。

テンプレートパーツ

index.phpを、更にテンプレートパーツとして分けて行きましょう。head要素の内容のみをheader.phpfooter要素の内容のみをfooter.phpに切り分けてください。

header.php
  <meta charset="utf-8" />
  ……
  <!-- Core theme CSS (includes Bootstrap)-->
  <link href="//sample.local/wp-content/themes/sample/css/styles.css" rel="stylesheet" />
footer.php
    <div class="container px-4 px-lg-5">
      <div class="row gx-4 gx-lg-5 justify-content-center">
      ……
      </div>
    </div>

この、header.phpfooter.phpは特別なファイルで、これを呼び出す関数が、予めWordPressでは用意されています。

なお、テンプレートパーツとしてどの様に分けるかは、様々な考え方があります。この場合、</header>から上をheader.phpとする人も多いのではないでしょうか。今回は、head要素の内容のみをテンプレートとしていますが、これはページによってはhead要素内に別の要素を追加する可能性を想定しています。その場合、テンプレート内で条件分岐させることもできますが、冗長的になる恐れもあるため、このようにしています。また、タグを跨がないようにテンプレート作成し、HTMLの関連性が分かりにくくなるのを防いでいます。

関数とテンプレートタグ

WordPressには、専用の関数が多数用意されています。その中でも、テンプレートの中で使用する物をテンプレートタグといいます。関数とテンプレートタグは数多く存在し、次のウェブサイトから調べることができます。
https://wpdocs.osdn.jp/テンプレートタグ
https://wpdocs.osdn.jp/関数リファレンス

では、この関数とテンプレートタグを使用して、header.phpfooter.phpを仕上げて行きましょう。

get_header()、get_footer()

get_header()header.phpを、get_footer()footer.phpを読み込むことがでる関数です。パラメーターを指定するとheader-パラメーター.phpfooter-パラメーター.phpを読み込みます。例えば、次はheader-foo.phpfooter-bar.phpを読み込んでいます。

get_header("foo"); 
get_footer("bar");

では、index.phpを次のように更新してください。

index.php
<head>
  <?php get_header(); ?>
</head>
……
  <footer class="border-top">
    <?php get_footer(); ?>
  </footer>

get_template_part()

get_template_part() はヘッダー、サイドバー、フッターを除いたテンプレートパーツを読み込む関数です。テンプレートパーツは自由に作成することが可能で、第1パラメータにはテンプレートパーツへのパスを指定します。なお、各拡張子は省略可能です。では、nav要素をincludes/nav.phpに、header要素をincludes/header-main.phpに切り分けて下さい。

includes/nav.php
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
……
</nav>
includes/header-main.php
<header class="masthead" style="background-image: url('assets/img/home-bg.jpg')">
……
</header>

では、includes/nav.phpを次のように読み込んでください。

index.php
<!-- Navigation-->
<?php get_template_part("includes/nav"); ?>

次にincludes/header-main.phpを読み込みます。第2パラメータに指定した文字列は、第1パラメータのパスに-で繋げられ、それがパスとなります。

<!-- Page Header-->
<?php get_template_part("includes/header", "main"); ?>

wp_head()、wp_footer()

wp_head() は、wp_headアクションというものをスタートさせる関数で、</head>タグの直前で使用します。

index.php
  <?php wp_head(); ?>
</head>

wp_footer() もwp_headアクションをスタートさせる関数で、</footer>タグの直前で使用します。

index.php
  <?php wp_footer(); ?>
</footer>

この2つのタグを設置することにより、プラグインなどを適用することが可能となります。したがって、この2つのタグの設置はほぼ必須となります。

また、ログインをしている状態だと、ページ上部にアドミンバーが表示されます。ただし、このアドミンバーは確認する際に邪魔になる場合もあります。その場合、管理画面のユーザーからユーザー名をクリックして、サイトを見るときにツールバーを表示するのチェックを外してください。その他にも、WordPressやウェブブラウザのプラグインで非表示などにすることができます。

get_template_directory_uri()

get_template_directory_uri() は、有効化しているテーマのディレクトリのURIを取得する関数です。末尾にスラッシュは付きません。

例えば、CSSの読み込みを絶対パスで指定しましたが、URLは変更される可能性があります。そのような個所はget_template_directory_uri()で指定したほうがいいでしょう。

header.php
<link href="<?php echo get_template_directory_uri(); ?>/css/clean-blog.min.css" rel="stylesheet">

home_url()

home_url() は、利用中のWordPressのホームURLを返すテンプレートタグです。第1パラメータにパスを設定すると、ホームURLからなる絶対パスを返します。なお、ホームURLとは一般設定のサイトアドレスで設定しているURLです。ちなみに、同じ一般設定にあるWordPressアドレスとは、WordPressをインストールした場所です。

includes/nav.php
<a class="navbar-brand" href="<?php echo home_url('/'); ?>">Start Bootstrap</a>

esc_url()

esc_url() は属性をエスケープ処理する関数です。セキュリティの面からデータベースなどから取得した内容を直接出力するのは、好ましくありません。そこで、エスケープ処理を行ってから出力するようにします。この他にも、HTMLをエスケープ処理するesc_html()や属性をエスケープ処理するesc_attr()など、いくつかのエスケープ処理を行う関数が存在しますので、次のページから確認してください。
https://wpdocs.osdn.jp/関数リファレンス/esc_html
例えば、先ほどのURLは次のように出力します。

includes/nav.php
<?php echo esc_url(home_url('/')); ?>">

bloginfo()

bloginfo() は、利用中のWordPressの情報を表示するテンプレートタグです。表示させたい項目をパラメータに設定し、例えばnameはタイトル、descriptionはキャッチフレーズとなります。
https://wpdocs.osdn.jp/テンプレートタグ/bloginfo
以下は、左上のサイト名と、TOPページのタイトルと説明をbloginfo()で表示しています。

includes/nav.php
<a class="navbar-brand" href="<?php esc_url(home_url('/')); ?>"><?php bloginfo('name'); ?></a>
includes/header-main.php
<h1><?php bloginfo('name'); ?></h1>
<span class="subheading"><?php bloginfo('description'); ?></span>

functions.php

functions.phpはテーマの中の全てのファイルが参照してるファイルです。したがって、テーマのPHP関数をまとめて管理することができます。

また、WordPressにはさまざまな段階で発火するイベントトリガーである、アクションフックとフィルターフックという2種類のフックと呼ばれるものが用意されており、functions.phpで設定することが可能です。両方とも第1パラメータに発火のタイミング、第2パラメータに処理をする関数を設定します。

では、カレントディレクトリにfunctions.phpを作成してください。

add_action()

アクションフックは、特定のポイントやイベント発生時に起動され、何らかのアクションを起こします。

次は、Codexで公開されているアクションフックの一覧ですが、ほぼ実行順に並んでいます。
https://wpdocs.osdn.jp/プラグイン_API/アクションフック一覧
例えば、ローディング完了後のヘッダが送信される前に発火するiniがあり、次のように設定します。関数を別で定義してもいいですが、大抵は無名関数として設定します。

functions.php
add_action('init', function() {
  echo '<!-- This is a comment. -->';
});

他にも、ヘッダーテンプレートファイルがロードされる前に実行されるget_headerなどがあります。

add_filter()

フィルターフックは、何らかのタイミングで値を受け取り、更新して返します。
https://wpdocs.osdn.jp/プラグイン_API/フィルターフック一覧
第2パラメータの関数は、大抵はパラメータで値を受け取り、その値を加工して返します。例えば、タイトルが表示された際にフィルターをかけるにはthe_titleを設定します。以下は、そのタイミングでタイトルの文字列の前にを追加しています。

functions.php
add_filter('the_title', function($title) {
  return '★' . $title;
});

このように、フィルターを通すと値が加工されて返ってくることになります。

次は、全てのユーザーでアドミンバー(ページ確認時に上部に表示されるバー)を非表示にします。

add_filter('show_admin_bar', '__return_false');

TOPページ

では、TOPページを完成させて行きましょう。今回は、index.phpをTOPページのテンプレートとします。まずは、Post previewDividerの箇所を1つ分だけ残して、後は消去しておいてください。

index.php
<!-- Post preview-->
<div class="post-preview">
  <a href="post.html">
    <h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
    <h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
  </a>
  <p class="post-meta">
    Posted by
    <a href="#!">Start Bootstrap</a>
    on September 24, 2022
  </p>
</div>
<!-- Divider-->
<hr class="my-4" />

language_attributes()

language_attributes() は、WordPressの設定をもとに言語属性を表示する関数です。

index.php
<html <?php language_attributes() ?>>

body_class()

body_class() は、その状態によってbody要素にクラスを付与してくれる関数です。例えば、管理画面にログインをしている状態ですとlogged-inというクラスが追加され、記事ページですとpostid-123などと追加されます。これにより、その状態によってスタイルシートを設定することが可能となります。どのようなクラスが追加されるかは、次を確認してください。
https://wpdocs.osdn.jp/テンプレートタグ/body_class
では、以下のようにbodyタグの中に設置してください。

index.php
<body <?php body_class() ?>>

wp_body_open()

wp_body_open() は、bodyタグ直下に設定をして、そこにスクリプトなどを表示することができる関数です。出力する内容を後述するfunctions.phpなどに記述すると、wp_body_open()にフックすることができます。

index.php
<body <?php body_class() ?>>
<?php wp_body_open() ?>
functions.php
add_action('wp_body_open', function() {
  echo '<!--wp_body_open action hook-->';
});

the_title()

the_title() は、現在の投稿のタイトルを表示するテンプレートタグであり、ループ内で使用します。単体で使用すると、最新の投稿のタイトルを表示しますが、ブログのトップに固定に設定している投稿があると、そのタイトルが表示されます。

なお、表示ではなく取得するにはget_the_title()を使用します。

index.php
<h2 class="post-title"><?php the_title(); ?></h2>

また、第1パラメータ、第2パラメータで前後の表示を設定でき、第3パラメータをfalseにすることで値を返すことができます。例えば、次のようにすることも可能です。

index.php
<?php $title = the_title('<h2 class="post-title">', '</h2>', false); ?>
<?php echo $title; ?>

文字数の制限

このままでは、設定したタイトルの文字数全てが表示されてしまいます。この場合、タイトルを表示ではなく取得するget_the_title()を使用し、PHPのmb_substr()で出力文字の制限を行いましょう。

index.php
<h2 class="post-title"><?php echo mb_substr(get_the_title(), 0, 25) . '...'; ?></h2>

これで、先頭から25文字までの文字列が摘出され、末尾に...が付与されます。ただし、これでは25文字以内の文字列でも...が付与されてしまいます。しががって、以下のように文字数で条件分岐を行いましょう。

index.php
<h2 class="post-title">
<?php
if (mb_strlen( $title = get_the_title() ) > 25) {
  $title = mb_substr($title, 0, 25);
  echo $title . '...';
} else {
  echo $title;
}
?>
</h2>

the_excerpt()

the_excerpt() は、投稿の抜粋を表示するテンプレートタグであり、ループ内で使用します。単体で使用すると、最新の投稿の抜粋を表示します。抜粋が入力されていなければ、本文が最初の55文字(英数字)に省略されて表示されます。その場合、HTMLタグは取り除かれます。なお、表示ではなく取得するにはget_the_excerpt()を使用します。

index.php
<h3 class="post-subtitle"><?php the_excerpt(); ?></h3>

the_author()

the_author() は、投稿者を表示するテンプレートタグであり、ループ内で使用します。なお、表示ではなく取得するにはget_the_author()を使用します。

単体での使用はできませんので、以下の設定は今のところ表示されません。

index.php
<a href="#!"><?php the_author(); ?></a>

get_the_author_meta()

get_the_author_meta() は、ユーザーのメタデーターを返えす関数です。第1パラメータに、取得したいデーター項目を設定します。また、ループ内で使用すると、現在の投稿者のデーターを返しますが、ループ外では使用する場合、第2パラメータにユーザーIDを指定する必要があります。

以下は、ループ内で投稿者のIDを取得しています。

get_the_author_meta( 'ID' )

get_author_posts_url()

get_author_posts_url() は、指定した作成者のアーカイブページのURLを取得します。第1パラメータは、取得する作成者のIDを設定します。第1パラメータは、取得する作成者のスラッグとなり、省略可能です。

以下は、get_the_author_meta()でIDを指定しています。

index.php
<a href="<?php echo esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ); ?>"><?php the_author(); ?></a>

the_permalink()

the_permalink() は、投稿のパーマリンクを表示するテンプレートタグであり、ループ内で使用します。単体で使用すると、最新の投稿のパーマリンクを表示します。なお、表示ではなく取得するにはget_permalink()を使用します。

index.php
<div class="post-preview">
  <a href="<?php the_permalink(); ?>">

このURI はWordPressのパーマリンクの設定によって、取得する形式が決まります。投稿名で設定している場合、日本語のパーマリンクは好ましくありません。その場合、投稿画面のパーマリンクで適切なURLスラッグを設定してください。どちらにせよ、投稿名での設定は固定ページと被る恐れがあるので、別の設定がいいでしょう。なお、URLスラッグは公開、または下書き保存しないと設定できません。ちなみに、固定ページはページ属性で親ページを設定すると、URLスラッグを階層にできます。

パーマリンクの設定を行うと、Apacheの場合.htaccessが更新されます。この.htaccessを手動で間違った更新をしたり、削除したりするとアクセスができなります。その場合は、再度パーマリンクの設定を行うと、.htaccessが更新されます。

the_date()

the_date() は、記事の投稿・更新日を表示し、同じ日に複数の投稿がある場合は最初の投稿にのみ表示するテンプレートタグであり、ループ内で使用します。

単体での使用はできませんので、次の設定は今のところ表示されません。

index.php
on <?php the_date(); ?>

表示書式はWordPressの設定で決まるのですが、第1引数で指定することも可能です。また、第2引数と第3引数は前後の表示、第4引数はfalseで値を返します。なお、get_the_date()でも時刻を返すことができ、かつ全ての投稿で返します。例えば、次のようにすることも可能です。

index.php
<?php $date= the_date('Y-m-d', '<h2>', '</h2>', false); ?>
<?php echo $date; ?>

the_time()

the_time() は、投稿の公開時刻を表示するテンプレートタグであり、ループ内で使用します。単体で使用すると、最新の投稿の公開時刻を表示します。また、the_date()と違い全ての投稿に表示されます。かつ、引数で書式指定ができ、年月日も出力先可能ですので、the_date()の代わりとしても使用できます。なお、時刻を返すにはget_the_time()を使用します。

index.php
on <?php the_time('Y年n月j日 g:i a'); ?>

get_option()

get_option() は、一般設定を取得する関数です。例えば、次は日付のフォーマットを取得しています。

index.php
on <?php the_time(get_option('date_format')); ?>

その他にも、様々なパラメーターが存在しますので、以下で確認してください。
https://wpdocs.osdn.jp/関数リファレンス/get_option

the_post()

the_post() は、次の投稿を取得する関数です。例えば、the_post()で投稿を取得した後でthe_title()を使用すると、その投稿のタイトルを取得することができます。そして、再びthe_post()を使用すると、2つ目の投稿を取得します。

次は、最新の投稿から2つ目の投稿までのタイトルを、取得しています。

index.php
<?php
  the_post();
  the_title();

  the_post();
  the_title();
 ?>

このように使用すると投稿を表示していくことができるのですが、投稿の数だけ記述するのは現実的ではありません。そこで、次に紹介するhave_posts()と併用して、投稿を表示させます。

have_posts()

have_posts() は、投稿記事があればtrueを、なければfalseを返す関数です。

次は、記事がある場合はタイトルを表示、ない場合はメッセージを表示しています。

index.php
<?php if(have_posts()): ?>
  <!-- Post preview-->
  <div class="post-preview">
  ……
  <hr class="my-4" />
<?php else: ?>
  <p>Article not found.</p>
<?php endif; ?>

また、have_posts()は次のようにwhileでループで使用することが可能です。

index.php
<?php if(have_posts()): ?>
  <?php while (have_posts()): ?>
    <?php the_post(); ?>
    <!-- Post preview-->
    <div class="post-preview">
    ……
  <hr class="my-4" />
  <?php endwhile; ?>
<?php else: ?>
  <p>Article not found.</p>
<?php endif; ?>

投稿記事があればtrueを返し、the_post()が使用されると残りの記事に関して判定を行います。つまり、the_post()で全ての記事を取得するとfalseとなりループは終了します。したがって、the_post()で記事を取得しないと無限ループとなりますので注意してください。

get_the_ID()

get_the_ID() は、現在の投稿のIDを取得する関数です。ここでは使用しませんが、よく使う関数ですので覚えておきましょう。

get_the_ID();

個別投稿ページ

では、個別投稿ページを作成しましょう。テンプレート階層を確認すると、固定ページは個別投稿ページと固定ページの共通のテンプレートはsingular.phpとなっており、個別投稿ページ類の共通のテンプレートはsingle.phpとなっています。したがって、今回はsingle.phpを作成します。既にあるpost.htmlsingle.phpにリネームして、Post Content以外をindex.phpに合わせてください。

add_theme_support()

add_theme_support() は、テーマやプラグインに特定の機能を使用可能とする関数です。functions.php内で設定します。

個別ページではアイキャッチ画像を使用しますので、add_theme_support()でアイキャッチ画像が使用できるようにしましょう。

functions.php
add_action('init', function() {
  add_theme_support('post-thumbnails');
});

initで初期化のタイミングを指定しています。add_theme_support()の引数にpost-thumbnailsと設定していますが、これがアイキャッチ画像を指定しています。他の値は以下を確認してください。
https://wpdocs.osdn.jp/関数リファレンス/add_theme_support

これで、管理画面にアイキャッチ画像の項目が追加されます。

また、引数にtitle-tagを渡すことで、titleタグを表示します。TOPページならタイトル名-サブタイトル、その他のページなら、ページタイトル-タイトルとなります。

functions.php
add_action('after_setup_theme', function() {
  add_theme_support( 'title-tag' );
});

add_actionafter_setup_themeは、テーマが読み込まれた後にフックする、一番タイミングの早いフックとなります。なお、title-tagtitleタグを表示しますので、

ループ

WordPressのループとは、投稿の情報を取得している間という意味なので、投稿を1つだけ取得する場合にもループは使用することになります。今回の場合だと、<header>タグから</article>タグの終わりまでをループさせます。

single.php
<?php if (have_posts()): ?>
  <?php while (have_posts()): the_post(); ?>
    <!-- Page Header-->
    <?php get_template_part("includes/header"); ?>
    <!-- Post Content-->
    <article class="mb-4">
    ……
    </article>
  <?php endwhile; ?>
<?php endif; ?>
<!-- Footer-->

なお、single.phpは記事が存在しないということはあまりなく、なかった場合は404を返す仕様となっております。ただし、念のためif (have_posts()):内に記述していった方がいいでしょう。その場合、仕様上elseの設置は必要ありません。

the_content()

athe_content() は、投稿の本文を出力するテンプレートタグです。ループ内で使用します。

single.php
<div class="col-md-10 col-lg-8 col-xl-7">
  <?php the_content(); ?>
</div>

the_post_thumbnail()

the_post_thumbnail() は、アイキャッチ画像を表示するテンプレートタグです。ループ内で使用し、サイズと属性を設定することが可能です。

第1引数はサイズの設定で、thumbnailmediumlargefullを指定でき、これらは管理画面の設定 > メディアで設定できます。またはarray( 横, 縦 )で指定できます。

第2引数は属性の設定となり、array('属性名'=>'値')で複数設定可能です。

the_post_thumbnail(array(800, 300), array('alt'=>'アイキャッチ画像'));

wp_get_attachment_image_src()

wp_get_attachment_image_src() は、第1引数でID指定した画像のurlwidthheight属性とリサイズされているかどうかを配列として返す関数です。配列は次のようになります。

  • [0] => url
  • [1] => width
  • [2] => height
  • [3] => 真偽値
    また、第2引数にthumbnailmediumlargefullとサイズを指定でき、初期値はthumbnailです。またはarray( 横, 縦 )でも指定できます。

次は、画像のURLを表示しています。

$img = wp_get_attachment_image_src(15, "large");
echo $img[0];

なお、画像IDとは管理画面のメディア > ライブラリで画面を選択した際に表示されるURLのitemパラメーターで確認できます。

get_post_thumbnail_id()

get_post_thumbnail_id() は、設定されているアイキャッチ画像のIDを返すテンプレートタグです。次は、取得したIDをwp_get_attachment_image_src()で使用して、アイキャッチ画像の情報を取得しています。

$id  = get_post_thumbnail_id();
$img = wp_get_attachment_image_src($id);
echo $img[0]; // URLを出力

has_post_thumbnail()

has_post_thumbnail() は、その投稿にアイキャッチ画像が設定されているかのチェックを行い、設定されていればtrueを返す関数です。

次は、アイキャッチ画像が設定されていなければ、指定の画像を表示するようにしています。

<?php
if (has_post_thumbnail()) {
  $id  = get_post_thumbnail_id();
  $img = wp_get_attachment_image_src($id, "large");
} else {
  $img = array(get_template_directory_uri() . '/assets/img/about-bg.jpg');
}
?>
  <header class="masthead" style="background-image: url('<?php echo $img[0]; ?>')">

なお、このような他のテンプレートでも使用するような処理は、functions.phpで関数としてまとめた方がいいでしょう。

functions.php
function getEyecatchUrl() {
  if (has_post_thumbnail()) {
    $id  = get_post_thumbnail_id();
    $img = wp_get_attachment_image_src($id, "large");
  } else {
    $img = array(get_template_directory_uri() . '/assets/img/about-bg.jpg');
  }

  return $img[0];
}

header-single.phpを作成して、投稿詳細が表示されるようにし、functions.phpで定義した関数で背景画像を表示しましょう。

header-single.php
<header class="masthead" style="background-image: url('<?php echo getEyecatchUrl(); ?>')">
  ……
          <h1><?php the_title(); ?></h1>
          <p class="subheading">
            Posted by
            <a href="#!"><?php the_author(); ?></a>
            on <?php the_time('Y年n月j日 g:i a'); ?>
          </p>
  ……
</header>

コメント

コメントのテンプレートは、comments.phpとなりますので作成してください。

omments.php
<div class="my-5">
  <p>コメントフォーム</p>
</div>

comments_template()

comments_template()は、コメントテンプレートを読み込みます。第1パラメータはロードするファイルの指定で、デフォルト値はcomments.phpとなります。第2パラメータはtrueでコメントとトラックバックを分け、デフォルト値はfalseとなります。

single.php
<div class="col-md-10 col-lg-8 col-xl-7">
  <?php the_content(); ?>
  <?php comments_template(); ?>
</div>

comments_open()

comments_open()は、現在のページにコメントが許可されていればtrueを返す関数です。コメントの許可は、管理画面の投稿ページのディスカッションにあるコメントを許可で行えます。

single.php
<?php 
if (comments_open()) {
  comments_template();
}
?>

get_comments_number()

get_comments_number()は、現在のページのコメント、トラックバック、ピンバックの合計数を取得する関数です。パラメータには投稿のID、または投稿オブジェクトを指定することもでき、デフォルト値は現在の投稿である0となります。

次は、現在のページにコメントが許可されいる、または、何らかのコメントがある場合にコメントのテンプレートを取得しています。

single.php
<?php 
if (comments_open() || get_comments_number()) {
  comments_template();
}
?>

have_comments()

have_comments()は、次のコメントデータが存在するかを判定する関数です。存在するればtrueを、存在しなければfalseを返します。

comments.php
<div class="my-5">
  <?php if( have_comments() ): ?>
    <p>コメント</p>
  <?php endif; ?>
</div>

wp_list_comments()

wp_list_comments()は、コメントを表示するテンプレートタグです。第1パラメータには、各設定を配列で設定し、それらには初期値が設定されています。内容に関しては次を確認してください。
https://wpdocs.osdn.jp/テンプレートタグ/wp_list_comments

comments.php
<?php if( have_comments() ): ?>
  <ol>
    <?php
      wp_list_comments(array(
        'style' => 'ol',
        'reply_text' => '返信!',
      ));
    ?>
  </ol>
<?php endif; ?>

comment_form()

comment_form()は、コメントフォームを出力する関数です。第1パラメータには、各設定を配列で設定し、それらには初期値が設定されています。内容に関しては次を確認してください。
https://wpdocs.osdn.jp/関数リファレンス/comment_form

comments.php
<div class="my-5">
  <?php if( have_comments() ): ?>
    ……
  <?php endif; ?>

  <?php
    comment_form(array(
      'title_reply'  => 'コメントを書く',
      'label_submit' => 'コメントを送信!',
    ));
  ?>
</div>

固定ページ

固定ページは基本的にpage.phpが呼び出されますので、今回はsingle.phpをコピーしてpage.phpを作成します。そして、header-single.phpをコピーしてheader-page.phpを作成し、投稿詳細を削除します。

includes/header-page.php
<div class="site-heading">
  <h1><?php the_title(); ?></h1>
</div>
page.php
<?php get_template_part("includes/header", "page"); ?>

カスタムページテンプレート

固定ページでは、page.phpよりも優先度が高いpage-テンプレートID.phpがあり、更に優先度が高いpage-スラッグ.phpがあります。これらを使用して、それぞれの固定ページごとにテンプレートを作成してもいいのですが、同じテンプレートを使用する場合でもテンプレート作成するため冗長的になります。その場合、管理画面から固定ページに設定できるカスタムページテンプレートを使用します。

カスタムページテンプレートとは、固定ページの中で一番優先度の高いテンプレートとなります。ファイル名は何でもいいのですが、必ず次のコメントをファイル内に設置する必要があります。

/*
Template Name: テンプレート名
*/

例えば、page-custom-sample.phpというファイルを作成して、次のコメントを設置してください。

page-custom-sample.php
/*
Template Name: サンプルテンプレート
*/

すると、固定ページの編集ページにテンプレートという項目が追加され、作成したテンプレートを選択できるようになっています。

TOPページへの反映と投稿ページ一覧

標準では、TOPページには投稿ページの一覧が最新の投稿順位表示される設定になっています。これを個別ページに変えるには、管理ページの設定 > 表示設定 > ホームページの表示で固定ページを選択して、ホームページを表示したい固定ページにしてください。

また、固定ページに投稿ページの一覧を表示させることもできます。同じく、ホームページの表示で固定ページを選択して、投稿ページでどの固定ページ反映させるかを選択してください。

カテゴリーとタグ

カテゴリーは、管理画面のカテゴリ-から作成することが可能で、タグと違い親カテゴリーを設定することができます。また、投稿でカテゴリーを設定しないと、標準ではUncategorizedというカテゴリーが設定されます。この初期設定は、設定 > 投稿設定 > 投稿用カテゴリーの初期設定から設定できます。何も設定しない場合は、一度投稿してから設定を外す必要があります。一方タグの場合は、最初から何も設定しないことが可能です。

投稿には、何らかのカテゴリーが設定されていることが望ましいです。したがって、Uncategorizedを一番頻繁に設定するカテゴリーに変えておけばいいでしょう。タグは、カテゴリーでは分類しにくい細分化として使用することが多いです。つまり、明確なルールはありませんが、カテゴリーを大分類とし、そこからさらにタグで分けるイメージとなります。カテゴリーは必要最低限の数にして、あとはタグを設定すればいいでしょう。

なお、固定ページはカテゴリーとタグを設定することができませんが、WordPressには別で紹介するカスタム分類(custom taxonomy) という機能があり、それで分類することが可能です。なお、カスタム分類は投稿でも使用可能です。

カテゴリーページは/category/カテゴリーのスラッグ/でアクセスができます。この/category/の箇所は、設定 > パーマリンク設定 > オプション > カテゴリーベースで設定できます。

カテゴリーとタグは、それぞれ個別のテンプレートが用意されていますが、今回は共通のテンプレートであるarchive.phpを使用します。投稿の一覧表示をするためindex.phpをベースとして作成しますので、index.phpをコピーしてarchive.phpにしてください。そして、header-main.phpをコピーしてheader-archive.phpを作成し、読み込んでください。

archive.php
 <!-- Page Header-->
 <?php get_template_part("includes/header", "archive"); ?>

wp_title()

wp_title() は、現在のページのタイトルを表示または取得するテンプレートタグです。第1パラメータは、タイトルの区切り文字の設定となり、デフォルトでは&raquo;となっています。区切り文字が必要ないのなら、''と設定してください。第2パラメータはtrueで表示(デフォルト)、falseで取得となります。第3パラメータは、区切り文字の位置指定となり、rightならタイトルの右端、それ以外で左端(デフォルト)に表示します。

header-archive.php
<h1>Category</h1>
<span class="subheading"><?php wp_title(''); ?></span>

is_category()

is_category() は、カテゴリーアーカイブが表示されていればtrueを返します。このように、is_*** 形式のテンプレートタグは条件分岐タグと呼ばれており、他にも沢山の条件分岐タグが存在します。
https://wpdocs.osdn.jp/条件分岐タグ
以下は、カテゴリーアーカイブならCategoryを表示しています。また、同じ条件分岐タグであるis_date()を使用して、DateアーカイブならDateを、is_author()を使用してAuthorアーカイブならAuthorを表示します。そして、どれにも当てはまらないのならTagを表示しています。

header-archive.php
<div class="site-heading">
  <?php if(is_category()): ?>
    <h1>Category</h1>
  <?php elseif(is_date()): ?>
    <h1>Date</h1>
 <?php elseif(is_author()): ?>
    <h1>Author</h1>
  <?php else: ?>
    <h1>Tag</h1>
  <?php endif; ?>
  <span class="subheading"><?php wp_title(''); ?></span>
</div>

メニュー

register_nav_menu()

register_nav_menu() は、ナビゲーションメニューを登録します。この設定を行うと、カスタムメニューエディタが使用できるようになります。functions.phpに次のように設定をして下さい。

functions.php
add_action('init', function() {
  register_nav_menus([
    'globalNav' => 'グローバルナビゲーション',
    'footerNav' => 'フッターナビゲーション',
  ]);
});

管理画面の外観にメニューが追加されています。

メニューを編集タブからメニューを作成できますので、メニュー構造のメニュー名を入力してください。例えば、グローバルナビゲーションなどと入力して、メニューを作成ボタンをクリックします。

メニュー項目を追加から、メニューを追加できます。例えば、よくあるホームを追加する場合、カスタムリンクのURLを/、リンク文字列をホームにしてメニューに追加ボタンをクリックします。すると、メニュー構造にメニューが追加されますので、いくつか追加してください。

メニュー構造にメニューはマウス操作で移動でき、右にずらすと、ロールオーバーで表示する階層構造にすることができます。また、メニューの位置には設定したナビゲーションが表示されていますので、どこに表示するかを選択してください。今回はグロナビしか使用しません。すべて設定できたら、メニューを保存をクリックして登録してください。

メニューの編集が完了したら、位置を管理タブでテーマの位置に指定されたメニューが設定されているのを確認してください。

wp_nav_menu() は、ナビゲーションメニューをリスト表示するテンプレートタグです。パラメータなしで使用すると、ulタブ構成でクラスが付与されたHTMLが出力されますので、これに合わせてスタイルシートを設定してもいいです。また、パラメータに配列を設定でき、各項目を設定することが可能です。
https://wpdocs.osdn.jp/テンプレートタグ/wp_nav_menu
次は、出力されたHTMLです。

<ul id="menu-%e3%82%b0%e3%83%ad%e3%83%bc%e3%83%90%e3%83%ab%e3%83%8a%e3%83%93%e3%82%b2%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3" class="menu">
  <li id="menu-item-2118" class="menu-item menu-item-type-custom menu-item-object-custom current-menu-item current_page_item menu-item-2118"><a href="/" aria-current="page">ホーム</a></li>
  <li id="menu-item-2119" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-2119"><a href="http://test.local/hello-world/">Hello world!</a></li>
  <li id="menu-item-2120" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2120"><a href="http://test.local/sample-page/">Sample Page</a></li>
</ul>

get_nav_menu_locations()

get_nav_menu_locations() は、登録されているすべてのナビゲーションメニューと、それらに割り当てられているメニューIDを取得する関数です。

var_dump(get_nav_menu_locations());
/*
array (size=2)
  'globalNav' => int 155
  'footerNav' => int 0
*/

この場合、globalNavfooterNavという2つのナビゲーションメニューが存在し、globalNavにはID155のメニューが割り当てられています。

wp_get_nav_menu_object()

wp_get_nav_menu_object() は、指定したメニューIDのデーターを配列で返し、見つからなかった場合はfalseを返す関数です。配列の要素は、次の内容となっています。

プロパティ名 データ型 内容
term_id int ID
name string ナビゲーション名
slug string スラッグ
term_group int グループID
term_taxonomy_id int タクソノミーID
taxonomy string タクソノミー名。カテゴリーの場合はcategoryとなる
description string 説明
parent int 親カテゴリーID。なければ0となる
count int メニュー項目数
$menuName  = "globalNav";
$locations = get_nav_menu_locations();
$menu      = wp_get_nav_menu_object($locations[$menuName]);
var_dump($menu);
/*
object(WP_Term)[4862]
  public 'term_id' => int 155
  public 'name' => string 'グローバルナビゲーション' (length=36)
  ……
*/

wp_get_nav_menu_items()

wp_get_nav_menu_items() は、指定したメニューIDの各アイテムの情報を配列で返す関数です。次は、wp_get_nav_menu_object()でメニューIDを取得し、それを元に情報を取得しています。

$menuName  = "globalNav";
$locations = get_nav_menu_locations();
$menu      = wp_get_nav_menu_object($locations[$menuName]);
$menuItems = wp_get_nav_menu_items($menu->term_id);
var_dump($menuItems);
/*
array (size=2)
  0 => 
    object(WP_Post)[4865]
      public 'ID' => int 2112
      public 'post_author' => string '1' (length=1)
      ……
	1 => 
    object(WP_Post)[4905]
      public 'ID' => int 2113
      ……
*/

つまり、各メニューを配列で取得できましたので、これをforeachなどで要素を取得していき、HTMLを付与することで、ナビゲーションメニューを作成することができます。なお、各要素を表示する際、エスケープ処理を忘れないようにしましょう。

includes/nav.php
<?php
  $menuName  = "globalNav";
  $locations = get_nav_menu_locations();
  $menu      = wp_get_nav_menu_object($locations[$menuName]);
  $menuItems = wp_get_nav_menu_items($menu->term_id);
?>
……
<?php foreach($menuItems as $menuItem): ?>
  <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="<?php echo esc_attr($menuItem->url); ?>"><?php echo esc_html($menuItem->title); ?></a></li>
<?php endforeach; ?>

ページネーション

1ページにおける投稿の表示数は、設定 > 表示設定 > 1ページに表示する最大投稿数で設定します。これを超える投稿はページネーションにより管理されることになります。

next_posts_link()

next_posts_link() は、次の投稿の一覧へのリンクを表示するテンプレートタグです。パラメータを設定せずに使用すると、次ページへ »というリンクが表示され、この表示テキストは第1パラメータで変更が可能です。第2パラメータで表示するページ数の上限が設定でき、初期値は上限なしとなっています。

index.php
<!-- Pager-->
<div class="d-flex justify-content-end mb-4">
  <?php next_posts_link('古い投稿へ →'); ?>
</div>

get_next_posts_link()

get_next_posts_link() は、次の投稿の一覧へのリンク要素を取得する関数です。パラメータはnext_posts_link()と同じです。

index.php
$linkElm = get_next_posts_link('古い投稿へ →');
$linkElm = str_replace('<a', '<a class="btn btn-primary float-right"', $linkElm);
echo $linkElm;

get_previous_posts_link()

get_previous_posts_link() は、前の投稿の一覧へのリンク要素を取得する関数です。パラメータや使い方は、get_next_posts_link()と同じです。なお、表示はprevious_posts_link()となります。

クラスなども修正して、以下のようにしてください。

index.php
<!-- Pager-->
<?php
  $linkElm = get_previous_posts_link('← 新しい投稿へ');
  if ($linkElm) {
    $linkElm = str_replace('<a', '<a class="btn btn-primary float-left"', $linkElm);
    echo '<div class="d-flex justify-content-between mb-4">';
    echo $linkElm;
  } else {
    echo '<div class="d-flex justify-content-end mb-4">';
  }

  $linkElm = get_next_posts_link('古い投稿へ →');
  if ($linkElm) {
    $linkElm = str_replace('<a', '<a class="btn btn-primary float-right"', $linkElm);
    echo $linkElm;
  }
  echo "</div>";
?>

条件分岐をしているのは、古い投稿がない場合に処理を行わないためです。

サイト内検索

WordPressは検索機能も兼ね備えていますので、それを実装するだけでサイト内検索を行うことが可能です。

get_search_form()

get_search_form() は、検索フォームを表示する関数です。searchform.phpを表示し、なければ標準で用意されている検索フォームを表示します。第1パラメータをfalseにするとフォームを文字列として返し、デフォルト値はtrueです。

では、ナビゲーションの箇所に設置してみましょう。

includes/nav.php
……
</ul>
<?php get_search_form(); ?>

これだけで検索フォームを表示されますが、searchform.phpを作成して、それを表示させましょう。

searchform.php
<form method="get" id="searchform" action="<?php bloginfo('url'); ?>">
  <input type="text" name="s" id="s" placeholder="SEARCH" />
  <button type="submit">検索する</button>
</form>

検索結果のページはsearch.phpとなります。archive.phpをコピーしてsearch.phpを作成してください。

WP_Query

WP_QueryはWordPressでwp-includes/class-wp-query.phpに定義されているクラスで、ブログの投稿やページのリクエストを取り扱います。クラスですのでインスタンス化を行い使用するのですが、WP_Queryのオブジェクトであるグローバル変数の$wp_queryを使用することも可能です。
https://wpdocs.osdn.jp/関数リファレンス/WP_Query
https://wpdocs.osdn.jp/グローバル変数

では、検索結果数を表示してみましょう。

search.php
<?php if(have_posts()): ?>
  <?php
    if (isset($_GET['s']) && empty($_GET['s'])) {
      echo '検索キーワードが確認できません。';
    } else {
      echo '「' . $_GET['s'] . '」の検索結果: ' . $wp_query->found_posts . '件';
    }
  ?>
  <?php while (have_posts()): ?>
    ……
  <?php endwhile; ?>
<?php else: ?>
  <p>キーワードに一致しませんでした。</p>
<?php endif; ?>

カスタム投稿タイプ

WordPressには個別投稿ページと固定ページがありますが、これは投稿タイプと呼ばれ、個別投稿ページと固定ページはWordPressが標準で用意している投稿タイプとなります。この、投稿タイプは自作することが可能で、それをカスタム投稿タイプといいます。

register_post_type()

register_post_type() は、投稿タイプを作成または変更する関数です。initアクション内から呼び出してください。第1パラメータは投稿タイプを設定します。これは、URLにも関わってくる投稿タイプを識別するキーワードで、最大20文字、大文字や空白は禁止となります。第2パラメータは、配列で様々な設定を行うことができます。例えば、次のような設定値があります。

  • label
    カスタム投稿タイプの表示名で、初期値は「投稿」となります。
  • public
    trueで投稿タイプがパブリックになり、デフォルト値はfalseとなります。falseだと管理画面から消えますが、使用できないわけではありません。
  • menu_position
    管理画面での表示位置を設定します。
  • menu_icon
    管理画面で表示する際のアイコンを指定でき、Dashiconsから選択することもできます。
  • supports
    投稿の際に入力などができる項目を設定できます。標準ではタイトルと本文が設定されていますが、追加する場合はタイトルと本文を含めて設定する必要があります。
  • has_archive
    trueでアーカイブを有効にします。つまり、投稿の一覧が表示されます。デフォルト値はfalseです。
  • hierarchical
    trueで親を指定で切るようになり、デフォルト値はfalseです。
  • show_in_rest
    trueでREST APIを有効にし、編集画面で新しいエディターが使用可能となります。

その他の項目や設定値に関しては、公式ドキュメントを確認してください。
https://wpdocs.osdn.jp/関数リファレンス/register_post_type
以下は、musicという投稿タイプを定義しています。音楽と表示され、投稿画面ではタイトル、本文、アイキャッチ画像が登録できるように設定しています。

functions.php
add_action( 'init', function() {
  register_post_type('music', [
    'label' => '音楽',
    'supports' => ['title', 'editor', 'thumbnail'],
    'public' => true,
  ]);
});

なお、次の投稿タイプはWordPressが使用しているため、予約済みとなっております。

  • post - 投稿
  • page - 固定ページ
  • attachment - 添付ファイル
  • revision - リビジョン
  • nav_menu_item - ナビゲーションメニュー

次の投稿タイプは、WordPressの関数と動作を妨害するため、使用してはいけません。

  • action
  • order
  • theme

管理画面に音楽が追加されていますので、適当に新規追加をしてください。

テンプレート

カスタム投稿タイプのページが表示される一番優先度の高いテンプレートは、single-$posttype.phpです。この$posttypeは投稿タイプ、つまりregister_post_type()の第1パラメータで設定した値となります。

では、single.phpをコピーしてsingle-music.phpを作成してください。投稿したページを表示すると、投稿内容が反映されています。

get_post_type()

get_post_type() は、投稿タイプを取得します。パラメータにIDまたは投稿オブジェクトを指定することで、その投稿の投稿タイプが取得できます。デフォルト値はnullとなり、現在の投稿の投稿タイプを取得します。

var_dump(get_post_type());
// string 'music' (length=5)

get_template_part()の第2パラメータにget_post_type()を指定することで、例えば以下のようにincludes/header-music.phpのテンプレートを読み込むことができます

single-music.php
<!-- Page Header-->
<?php get_template_part( 'includes/header', get_post_type() ); ?>

では、includes/header-page.phpをコピーして、includes/header-music.phpを作成してください。

アーカイブ

カスタム投稿は、デフォルトでは固定ページと同じで一覧表示はされません。これを一覧表示させるためには、register_post_type()has_archiveを設定してアーカイブを有効にしてください。

functions.php
add_action( 'init', function() {
  register_post_type('music', [
    ……
    'has_archive' => true,
  ]);
});

http://xxx/投稿タイプ/にアクセスすると、archive.phpをもとにタイトルが投稿分だけ表示されます。カスタム投稿専用のテンプレートとして、archive-music.phpなどと作成すればそれが優先されますが、今回はarchive.phpを使用し、includes/header-archive.phpを編集していきます。

is_post_type_archive()

is_post_type_archive() は、指定された投稿タイプのアーカイブならtrueを返す関数です。引数には投稿タイプを指定することもできます。これは、is_category()で紹介した、条件分岐タグの一種となります。

次のように、条件分岐を付け加えてください。

includes/header-archive.php
<?php elseif(is_post_type_archive()): ?>
  <?php $custumFlg = 1; ?>
  <h1>カスタム投稿</h1>
<?php else: ?>
  <h1>Tab</h1>
<?php endif; ?>
<?php if($custumFlg != 1): ?>
  <span class="subheading"><?php wp_title(''); ?></span>
<?php endif; ?>

今回は該当すれば$custumFlgを定義するようにしています。これは、下部でwp_title('')の出しわけをするためです。

なお、次のように配列で複数指定することも可能です。

is_post_type_archive(array('music', 'AAA', 'BBB');

get_post_type_object()

get_post_type_object() は、投稿タイプに設定されているオブジェクトを返す関数です。これはカスタム投稿タイプだけでなくpost(投稿)やpage(固定)なども対象とします。

次は、投稿タイプのラベルを取得して表示しています。なお、今回は説明のためにget_post_type_object()を使用しましたが、wp_title('')で取得してもかまいません。

includes/header-archive.php
<h1><?php echo get_post_type_object(get_post_type())->label; ?></h1>

階層構造

個別投稿ページは時系列に並べられ親子関係を持ちませんが、固定ページは並べるような一覧がなく、親子関係の階層構造が作れます。カスタム投稿タイプは標準では個別投稿ページと同じで、時系列に並べられ親子関係を持ちません。これを、親子関係の階層構造が作れるようにするには、register_post_type()で、hierarchicalを設定します。また、supportsエレメントにpage-attributesを追加します。

functions.php
add_action( 'init', function() {
  register_post_type('music', [
    ……
    'supports' => ['title', 'editor', 'thumbnail', 'page-attributes'],
  'hierarchical' => true,
  ]);
});

これで、固定ページの編集画面にページ属性の設定が追加されます。

カスタムタクソノミー(カスタム分類)

カテゴリーやタグなどのように、分類を行うものをタクソノミー(taxonomy) といいます。そして、固定ページやカスタム投稿にはタクソノミーが存在しませんので、標準ではタクソノミーを設定できません。この、固定ページやカスタム投稿にタクソノミーを追加することをカスタムタクソノミー(カスタム分類) いいます。

register_taxonomy()

register_taxonomy() は、カスタムタクソノミーとしてタクソノミーを追加したり上書きをする関数です。initアクション内から呼び出してください。第1パラメータにはタクソノミーの名前を設定します。第2パラメータには対象を設定します。第3パラメータには配列で各オプションを設定します。各設定値などは、以下のページでご確認ください。
https://wpdocs.osdn.jp/関数リファレンス/register_taxonomy

functions.php
register_taxonomy('genre', 'music', [
  'label' => '音楽ジャンル',
  'hierarchical' => true,
  'show_in_rest' => true,
]);

hierarchicaltrueにすることで階層を設定でき、設定ではカテゴリのように選択できます。falseだと階層を設定できず、設定ではタグのように設定します。つまり、カテゴリとタグはhierarchicalの値で分類されます。また、register_post_type()show_in_resttrueにして新しいエディターを使用している場合、こちらもshow_in_resttrueにしなければ、追加したタクソノミーが選択できません。

これで、管理画面の音楽に音楽ジャンルというタクソノミーが追加されます。では、カスタムタクソノミーの一覧ページを作成しましょう。archive.phpをコピーしてtaxonomy-genre.phpを作成してもいいですし、より優先順位が高いtaxonomy-タクソノミー名-スラッグ.phpでもいいです。今回は、archive.phpを使用します。

何らかのジャンルを作成して、カスタム投稿に設定しておいてください。

is_tax()

is_tax() は、カスタムタクソノミーならtrueを返す関数で、条件分岐タグの一種となります。第1パラメータにはタクソノミー名を指定し、配列で複数指定することも可能です。第2パラメータにはタームのID、名前、スラッグ、またはそれらの配列を指定できます。

includes\header-archive.phpに条件分岐を追加してください。

includes/header-archive.php
<?php elseif(is_tax()): ?>
  <h1><?php wp_title(''); ?></h1>

wp_title('')でタイトルを取得していますが、このままでは「カスタムタクソノミー ターム」と表示されます。

get_queried_object()

get_queried_object() は、現在リクエストされているクエリのオブジェクトを取得する関数です。投稿ページやアーカイブページなどによって、取得できるオブジェクトが代わり、もちろん、カスタムタクソノミーでも取得可能です。

var_dump(get_queried_object());
/*
object(WP_Term)[4505]
  public 'term_id' => int 8
  public 'name' => string 'EDM' (length=3)
  public 'slug' => string 'edm' (length=3)
  public 'term_group' => int 0
  public 'term_taxonomy_id' => int 8
  public 'taxonomy' => string 'genre' (length=5)
  public 'description' => string '' (length=0)
  public 'parent' => int 0
  public 'count' => int 0
  public 'filter' => string 'raw' (length=3)]
*/

get_taxonomy()

get_taxonomy() は、指定したタクソノミーのオブジェクトを取得します。パラメータにはタクソノミー名を設定します。

次は、get_queried_object()で取得したタクソノミー名を設定してオブジェクトを取得し、labelプロパティを表示しています。

includes/header-archive.php
<?php elseif(is_tax()): ?>
  <?php
    $query_object = get_queried_object();
    $taxonomy     = get_taxonomy($query_object->taxonomy);
  ?>
  <h1><?php echo $taxonomy->label; ?></h1>

これで、タクソノミーのラベルだけを表示できます。

the_archive_title()

the_archive_title() は、アーカイブタイトルを表示する関数です。

includes/header-archive.php
<span class="subheading"><?php the_archive_title(); ?></span>

ただし、「カスタムタクソノミー: ターム」と表示されます。

single_term_title()

single_term_title() は、現在のページのタームタイトルを表示または取得する関数です。したがって、通常はタクソノミーで使用します。第1パラメータは、タイトルの前に出力するテキストを設定します。第2パラメータをfalseにすると、値を返します。

includes/header-archive.php
<span class="subheading"><?php single_term_title(); ?></span>

single_***_title() は、ターム以外も準備されていますので、その他の箇所も変更して、以下のように更新しましょう。各条件ごとに、サブタイトルを決定しています。

includes/header-archive.php
<div class="site-heading">
  <?php
    $query_object = get_queried_object();
    $taxonomy     = get_taxonomy($query_object->taxonomy);
  ?>
  <?php if(is_category()): ?>
    <?php $sub_title = single_cat_title(``, false); ?>
    <h1><?php echo $taxonomy->label; ?></h1>
  <?php elseif(is_tag()): ?>
    <?php $sub_title = single_tag_title(``, false); ?>
    <h1><?php echo $taxonomy->label; ?></h1>
  <?php elseif(is_author()): ?>
    <?php $sub_title = get_the_author(); ?>
    <h1>Author</h1>
  <?php elseif(is_tax()): ?>
    <?php $sub_title = single_term_title(``, false); ?>
    <h1><?php echo $taxonomy->label; ?></h1>
  <?php elseif(is_post_type_archive()): ?>
    <?php $custumFlg = 1; ?>
    <h1><?php wp_title(''); ?></h1>
  <?php elseif(is_search()): ?>
    <?php $sub_title = esc_html(get_search_query(false)); ?>
    <h1>検索結果</h1>
  <?php elseif(is_404()): ?>
    <?php $sub_title = 'ページが見つかりません'; ?>
    <h1>404</h1>
  <?php else: ?>
  <?php endif; ?>
  <?php if($custumFlg != 1): ?>
    <span class="subheading"><?php echo $sub_title; ?></span>
  <?php endif; ?>
</div>

the_taxonomies()

the_taxonomies() は、投稿に割り当てられているタクソノミーの各タームを表示する関数です。
では、header-music.phpを以下のように更新して、カスタム投稿のページを確認して下さい。

includes/header-music.php
<h1><?php the_title(); ?></h1>
<p class="post-meta"><?php the_taxonomies(); ?></p>

タクソノミー名: <a href="http://***/***/">ターム名。</a>という形式で表示され、ターム名は設定した分だけで区切られて表示されます。また、配列でオプションを渡すことで、前後の表示をカスタマイズすることができます。

includes/header-music.php
<h1><?php the_title(); ?></h1>
 <?php the_taxonomies(array('before' => '<p class="post-meta">', 'after' => '</p>')); ?>

get_the_terms()

get_the_terms() は、投稿に割り当てられているタクソノミーの各タームを取得する関数です。第1パラメータに投稿のIDを、第2パラメータにタクソノミー名を指定します。各タームが配列として取得され、各タームごとに配列としていくつかの値が設定されています。

includes/header-music.php
<h1><?php the_title(); ?></h1>
<?php $terms = get_the_terms(get_the_ID(),'genre'); ?>
<?php if ($terms): ?>
  <ul class="list-inline text-center">
    <?php foreach ($terms as $term): ?>
      <li class="list-inline-item"><?php echo esc_html($term->name); ?></li>
    <?php endforeach; ?>
  </ul>
<?php endif; ?>

カスタムタクソノミーは選択されていない場合もありますので、if ($terms)で判定しています。

get_term_link()

get_term_link() は、指定したタームのアーカイブページへのパーマリンクを返す関数です。第1パラメータにタームのオブジェクト、ID 、スラッグのどれかを指定します。第2パラメータは第1パラメータにオブジェクトを指定した際にタクソノミーのスラッグを指定します。

includes/header-music.php
<li class="list-inline-item"><a href="<?php echo get_term_link($term); ?>"><?php echo esc_html($term->name); ?></a></li>

get_terms()

get_terms() は、指定したタクソノミーに含まれるタームを取得する関数です。第1パラメータにタクソノミー名を指定します。各タームが配列として取得され、各タームごとに配列としていくつかの値が設定されています。

header-archive.phpに、カスタムタクソノミーの場合は各タームが表示されるようにしましょう。

includes/header-archive.php
<?php if($custumFlg === 1): ?>
  <?php $terms = get_terms('genre'); ?>
  <ul class="list-inline text-center">
    <?php foreach ($terms as $term): ?>
      <li class="list-inline-item"><a href="<?php echo get_term_link($term); ?>"><?php echo esc_html($term->name); ?></a></li>
    <?php endforeach; ?>
  </ul>
<?php else: ?>
  <span class="subheading"><?php echo $sub_title; ?></span>
<?php endif; ?>

WP_Query

WP_Querywp-includes/class-wp-query.phpに定義されているクラスで、ブログの投稿やページの情報を扱います。

これまでは、リクエストされたURLによって投稿を表示してきました。これをメインクエリといい、テンプレートを読み込む前にデーターを取得しています。そして、メインクエリをループ処理することをメインループといいます。メインクエリ以外で取得するデーターをサブクエリといい、サブクエリをループ処理することをサブループといいます。WP_Queryは、このサブクエリを扱うクラスとなります。

サブループはメインループとは別にループ処理を行えます。したがって、1ページ内に各種記事のループを表示させるなどが可能になります。

Wp_Queryは、インスタンス化を行い利用するのですが、その際に引数を利用することにより特定のインスタンスを作成できます。

$args = array(
  'post_type' => 'music',
  'posts_per_page' => 5,
);

$wp_query = new WP_Query($args);

if ( $wp_query->have_posts() ) {
    while ( $wp_query->have_posts() ) {
        $wp_query->the_post();
        the_post();
    }
} else {
  print "no no!";
}

wp_reset_postdata();

WP_Queryの引数に配列を設定して、インスタンス化をしています。この場合、musicカスタム投稿タイプから、1ページの最大取得数を5ページで取得しており、これが投稿ページだと、postと設定します。生成したインスタンスの使い方は、通常のメインクエリと同じようにループで使用します。

主に、以下のような要素を設定できます。

  • post_type
    投稿タイプを選択します。postで投稿ページ、pageで固定ページとなり、カスタム投稿タイプなどを指定できます。
  • posts_per_page
    1ページあたりの表示件数を指定します。デフォルト値は10件となり、全件表示するには-1を指定します。
  • order
    昇順か降順かを指定します。デフォルト値は降順DESCで、昇順はASCとなります。
  • orderby
    ソートの項目を選択します。IDauthortitledateなどを設定でます。

それ以外の設定に関しては、以下を確認してください。

https://wpdocs.osdn.jp/関数リファレンス/WP_Query

WP_Queryは、様々な値の組み合わせにより細かな設定が可能ですので、積極的に使用しましょう。

カスタムフィールド

カスタムフィールドとは、登録ページに標準で入力できる項目以外の入力項目のことです。例えば、投稿の登録ページの右上のオプションから設定を選択し、パネルのカスタムフィールドをONにして有効化してリロードボタンをクリックします。すると、登録ページの下部にカスタムフィールドの項目が表示され、名前(キー)と値をのセットで複数登録することができ、一度作成すると次からは名前(キー)の選択エリアからそれらを選択できるようになります。また、同じ名前のものを複数登録することもできます。

カスタム投稿タイプでカスタムフィールドを使用するには、functions.phpregister_post_type()supportscustom-fieldsを追加する必要があります。

functions.php
add_action( 'init', function() {
  register_post_type('music', [
    ……
    'supports' => ['title', 'editor', 'thumbnail', 'page-attributes', 'custom-fields'],
  ]);
    ……
});

これで、カスタム投稿タイプの登録ページにカスタムフィールドが表示されますので、以下のように設定してください。

  • 価格: 10000
  • レーベル: EMI
  • アーティスト: The WP
  • アーティスト: PHPer

get_post_meta()

get_post_meta() は、カスタムフィールドの値を取得する関数です。第1パラメータに投稿のID、第2パラメータに取得したい値のキー、第2パラメータはtrueだと文字列を返し、初期値のfalseだとカスタムフィールドの配列を返します。

では、single-music.phpを次のように更新しましょう。

single-music.php
<?php
  $price     = get_post_meta(get_the_ID(), '価格', true);
  $label     = get_post_meta(get_the_ID(), 'レーベル', true);
  $artists   = get_post_meta(get_the_ID(), 'アーティスト', false);
?>
<!DOCTYPE html>
……
<div class="col-md-10 col-lg-8 col-xl-7">
  <dl>
    <?php if ($price !== ''): ?>
      <dt>価格</dt>
      <dd><?php echo esc_html(number_format($price)); ?></dd>
    <?php endif; ?>
    <?php if ($label !== ''): ?>
      <dt>レーベル</dt>
      <dd><?php echo esc_html($label); ?></dd>
    <?php endif; ?>
    <?php if ($artists): ?>
      <dt>アーティスト</dt>
      <?php foreach ($artists as $artist): ?>
        <dd><?php echo esc_html($artist); ?></dd>
      <?php endforeach; ?>
    <?php endif; ?>
  </dl>
  <?php the_content(); ?>
  ……

価格とレーベルは文字列で、複数設定しているアーティストは配列で取得しています。

ショートコード

WordPressはセキュリティ上、投稿画面の本文の中にPHPのコードを埋め込むことはできません。その場合、予めショートコードというコードを作成しておき、それを使用することになります。

add_shortcode()

add_shortcode() は、ショートコードタグ用のフックを追加する関数ですfunctions.php内で定義し、第1パラメータにショートコード名、第2パラメータに実行する関数を定義します。その際、必ずreturnで値を返さないと「更新に失敗しました。 返答が正しい JSON レスポンスではありません。」とエラーとなります。また、処理のタイミング上、echoなどで表示すると表示がずれてしまします。したがって、例えばthe_title()関数などではなく、get_the_title()関数などで取得して処理をしてください。

functions.php
add_shortcode('date', function() {
  return date('Y年 n月 j日');
});

投稿ページの本文で+ボタンをクリックし、ショートコードを選択して使用してもいいですし、そのまま本文に記載することもできます。その際、必ずブラケットで囲む必要があります。

[date]

また、第2パラメータの関数にはパラメータを設定することができ、パラメータは連想配列で受け取ることができます。

functions.php
add_shortcode('date', function($atts) {
  return $atts['before'] . date('Y年 n月 j日') . $atts['after'];
});
[date before="今日は" after="です。"]

shortcode_atts()

shortcode_atts() は、ショートコードのパラメータにデフォルト値を設定する関数です。第1パラメータにデフォルト値の連想配列、第2パラメータにショートコードタグに指定した属性、第3パラメータにショートコード名を設定します。

functions.php
add_shortcode('date', function($atts) {
  $atts = shortcode_atts([
    'before' => 'Today is ',
    'after' => ' !',
  ], $atts, 'date');

  return $atts['before'] . date('Y年 n月 j日') . $atts['after'];
});

プラグイン

テーマで設定したfunctions.phpなどは、そのテーマでしか使用できません。汎用的に使用したい機能などは、プラグインにすればいいでしょう。

プラグインは、wp-content > pluginsにファイル、もしくはディレクトリを設置します。では、例としてsampleディレクトリを追加して、その中にsample-plugin.phpを追加してください。

プラグインには、テーマと同じでコメントの設定が必要となります。いくつかの種類がありますので、次で確認してください。プラグインとして成立させるには、最低限Plugin Nameが必要となります。
https://wpdocs.osdn.jp/プラグインの作成

sample/sample-plugin.php
<?php
/*
  Plugin Name: サンプルプラグイン
  Description: サンプルのプラグインです。
  Version: 1.0
  Author: ANTEZ
*/

これで、プラグインがインストールされている状態になり、有効化も可能となります。

例えば、既に作成しているショートコードをsample-plugin.phpに追加してみます。

sample/sample-plugin.php
add_shortcode('date', function($atts) {
  $atts = shortcode_atts([
    'before' => 'Today is ',
    'after' => ' !',
  ], $atts, 'date');

  return $atts['before'] . date('Y年 n月 j日') . $atts['after'];
});

ブロックエディター

https://zenn.dev/antez/books/568dd4d86562a1

ブロックエディターとは、現在のWordPressの本文入力などで採用されている、新しいエディターのことです。ブロックを選択して、内容を入力することができるという特徴があります。

ブロックエディターは、JavaScriptやCSSなどでカスタマイズできるのですが、WordPressではアクションフックで読み込むことが推奨されています。これにより、functions.php内で一元管理でき、重複読み込みを回避できるなどのメリットが得られます。

wp_enqueue_script()

wp_enqueue_script() は、JavaScriptファイルをキューに入れ、適切な順番で処理をしてくれる関数です。第1パラメータに操作する際のハンドル名を指定し、この名前の末尾に-jsを付けたものが、読み込んだ際の<script>タブのid属性となります。第2パラメータでは読み込むスクリプトを指定します。第3パラメータでは、読み込むスクリプトを順番に配列で指定します。以下の場合だと、wp-blocksが先に読み込まれますので、wp-blocksに関するオブジェクトなどがJavaScriptで使用できるようになります。

では、ブロックエディターをフックとしたプラグイン内で使用してみます。ブロックエディターの素材ファイルを読み込む際のフックはenqueue_block_editor_assetsとなります。

sample-plugin/sample-plugin.php
add_action('enqueue_block_editor_assets', function($atts) {
  wp_enqueue_script(
    'sample-script',
    'http://sample.local/wp-content/plugins/sample/sample.js',
    ['wp-blocks'],
  );
});

指定したパスの通りに、sample.jsを作成します。

sample/sample.js
alert('Hello!');

プラグインを有効化して、投稿画面を開いてください。Hello!とアラートが表示されます。

plugins_url()

plugins_url() は、pluginsディレクトリーの絶対パスを末尾のスラッシュなしで取得する関数です。第1パラメータには、URLを取得したいプラグインファイルのパスを指定します。また、第2パラメータにパスを指定すると、第1パラメータはそれを基点とします。つまり、次のように設定すると、sample.jsが指定されます。

例えば、先ほどのスクリプトの指定は次のようにします。

sample-plugin/sample-plugin.php
add_action('enqueue_block_editor_assets', function($atts) {
  wp_enqueue_script(
    'sample-script',
    plugins_url('myeditor.js', __FILE__),
    ['wp-blocks'],
  );
});

add_editor_style()

add_editor_style() は、ビジュアルエディターにCSSを読み込みます。これにより、公開ページのデザインをそのままビジュアルエディターで表現できます。

sample-plugin/sample-plugin.php
add_action('after_setup_theme', function() {
  add_theme_support('editor-styles');
  add_editor_style('/css/editor.css');
});

after_setup_themeのタイミングで、add_theme_supporteditor-stylesをすることで、ビジュアルエディターにスタイル機能を追加し、add_editor_styleでCSSを読み込みます。

例えば、/css/editor.cssを次のように設定してください。

css/editor.css
p {
  color: blue;
}

管理画面、本番画面ともに、pタグのテキストが赤色で表示されます。

wp.blocks.registerBlockStyle()

wp.blocks.registerBlockStyle() は、既存のブロックの動作を変更するためのAPIで、JavaScriptのスクリプトとなります。第1パラメータで適用するブロックを指定し、第2パラメータではクラス名とラベルを指定します。

どのようなブロックが存在するかは、次で確認できます。
https://ja.wordpress.org/support/article/blocks/
https://wordpress.org/support/article/blocks/

これらブロックはcoreに格納されていますので、core/**と指定することができます。

では、sample.jsを次のように記述してください。

sample/sample.js
wp.blocks.registerBlockStyle('core/paragraph', {
  name: 'p text-danger',
  label: 'デンジャー'
});

paragraph(段落)を指定しています。nameはクラス名となり、先頭にis-style-が付与されます。この場合、is-style-p text-dangerがクラス属性に設定されます。labelは管理画面での表示名です。

カスタムブロック

ブロックは自分で作成することも可能で、自作したブロックをカスタムブロックといいます。なお、カスタムブロックに関しは公式からハンドブックが公開されており、チュートリアルなども公開されています。
https://ja.wordpress.org/team/handbook/block-editor/
カスタムブロックの説明は、これだけで1つのコンテンツになるくらいのボリュームですので、ここではこのチュートリアルを使用して、大まかに説明します。そして、ここからは多少フロントエンド開発っぽくなってきます。フロントエンドエンジニアにはおなじみの手法なのですが、そうでない方にはとっつきにくい所だと思います。

プロジェクトの作成

カスタムブロックにはいくつかの作成方法がありますが、pluginsディレクトリ内に作成するのが一般的です。pluginsディレクトリに移動してnpxでブロックのひな形である@wordpres/create-blockをインストールします。カスタムブロック名を指定しますが、これは任意の名前でいいです。これは、ReactのCreate React Appのようなもので、webpackやBabel、ESLintなどが自動で設定されています。

$ npx @wordpress/create-block gutenpride

なお、npxや次に出てくるnpmが分からない方は、次を参考にしてください。
https://zenn.dev/antez/articles/a9d9d12178b7b2

途中、It might take a couple of minutes...などと表示されますが、インストールが完了するまで待ってください。Code is Poetryが表示されたら完了です。gutenprideというディレクトリがインストールされますので、コマンドラインで移動してください。

いくつかのディレクトリとファイルが、インストールされています。srcディレクトリ内にはカスタムブロックの開発用ファイルが格納されています。これがビルドされて、本番化のソース群となります。また、プラグイン名.phpがプラグインの設定ファイルです。

gutenpride.php
function create_block_gutenpride_block_init() {
	register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_gutenpride_block_init' );

add_actioninitcreate_block_gutenpride_block_init()を実行しています。

register_block_type()

register_block_type() は、ブロックタイプを登録します。つまり、以下はPHPのマジカル定数を使用して、このディレクトリ内のbuildディレクトリを登録しています。

gutenpride.php
register_block_type( __DIR__ . '/build' );

ビルド

では、package.jsonscriptsに設定されているビルドを行いましょう。

$ npm run build

成功するとsrcディレクトリがビルドされ、buildディレクトリが作成されています。管理画面でインストール済みプラグインを確認すると、プラグインのインストールが確認できますので、有効化をしてください。投稿画面のウィジェットの項目に、追加したプラグインが表示されています。

block.json

ブロックタイプのディレクトリを指定すると、そこに存在するblock.jsonが読み込まれます。このファイルにはブロックタイプのメタデータを設定でき、例えば以下のような値が設定可能です。

  • apiVersion
    ブロックが使用するBlock APIのバージョンで、最新のバージョンは2となります。
  • name
    ブロックを識別する固有の文字列です。namespace/ブロック名となり、namespaceはプラグインやテーマの名前となります。
  • version
    ブロックのバージョンです。
  • title
    表示される際のタイトルです。
  • category
    ブロックのカテゴリを設定します。カテゴリはtext、media、design、widgets、theme、embedから選択します。
  • icon
    表示するアイコンを設定します。Dashiconsから選択できます。
  • description
    ブロックの説明文です。
  • supports
    使用できる機能を制御します。項目に関してはサポートを参照してください。
  • textdomain
    翻訳するテキストがどこにあるかを示す識別子。この識別子を使用して、各言語の翻訳ファイルが作成されます。
  • editorScript
    エディタースクリプトを定義します。
  • editorStyle
    エディタースタイルを定義します。
  • style
    スタイルを定義します。

その他の項目に関しては、次を参照してください。
https://ja.wordpress.org/team/handbook/block-editor/reference-guides/block-api/block-metadata/

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 2,
  "name": "create-block/gutenpride",
  "version": "0.1.0",
  "title": "Gutenpride",
  "category": "widgets",
  "icon": "smiley",
  "description": "Example static block scaffolded with Create Block tool.",
  "supports": {
    "html": false
  },
  "textdomain": "gutenpride",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css"
}

index.js

では、src/index.jsを見てみましょう。次のように、Editsaveを読み込んでいます。

src/index.js
import Edit from './edit';
import save from './save';

では、src/edit.jsEditの定義を見てみます。

src/edit.js
export default function Edit() {
  return (
    <p { ...useBlockProps() }>
      { __( 'Gutenpride – hello from the editor!', 'gutenpride' ) }
    </p>
  );
}

関数内に直接HTMLタグのようなものが設定されています。そう、これは紛れもなくJSXですね。どうやら、ここの文字列が実行結果に表示されていそうです。

index.jsに戻って、これが使用されている箇所を見てみましょう。

src/index.js
import Edit from './edit';
import save from './save';

では、src/edit.jsEditの定義を見てみます。

src/edit.js
registerBlockType('create-block/gutenpride', {
  edit: Edit,
  save,
});

wp.blocks.registerBlockType()

wp.blocks.registerBlockType() は、ブロックタイプの登録を行う関数です。第1引数にはブロックを識別する固有の文字列で、namespace/ブロック名を渡します。第2引数にブロックタイプの設定をオブジェクトで渡します。この設定はblock.jsonの設定より優先度が高いです。

では、次のように更新しましょう

src/index.js
registerBlockType('create-block/gutenpride', {
  apiVersion: 2,
  attributes: {
    message: {
      type:     'string',
      source:   'text',
      selector: 'div',
      default:  '',
    }
  },
  edit: Edit,
  save,
});

次に、編集画面を定義しているedit.jsを更新します。Edit関数のパラメータに、先ほど定義したattributesと属性を更新する関数であるsetAttributesを、オブジェクトとして設定します。また、TextControlコンポーネントを使用しますので、importで読み込んで次のように設定をしてください。

src/edit.js
import { __ } from'@wordpress/i18n';
import { useBlockProps } from'@wordpress/block-editor';
import { TextControl } from'@wordpress/components';
import'./editor.scss';

export default function Edit( { attributes, setAttributes } ) {
  return (
    <div { ...useBlockProps() }>
      <TextControl
        label={ __( 'Message', 'gutenpride' ) }
        value={ attributes.message }
        onChange={ ( val ) => setAttributes( { message: val } ) }
      />
    </div>
  );
}

これで、テキストフィールドが管理画面のカスタムフィールドに表示されます。ただし、まだ入力を保存する設定をしていないため、入力したテキストが保存できません。保存は、save.jsで設定します。

では、再ビルドを行ってください。管理画面を確認すると、それまで設置していたカスタムフィールドに「このブロックには、想定されていないか無効なコンテンツが含まれています」と表示されます。これは、カスタムフィールドの内容を変更したからですので、このカスタムフィールドは削除して、新たに設置しなおしてください。テキストが入力できますので、適当な文字列を入力して投稿してください。ページ上では、その文字列が表示されます。

save.jsも同じように更新しましょう。

src/save.js
export default function save( {attributes} ) {
  return (
    <div { ...useBlockProps.save() }>
      { attributes.message }
    </div>
  );
}

再ビルドを行い、新しくカスタムフィールドを設置してページを確認すると、入力したテキストが反映されています。

最後に

長い長い記事を読んでいただき、ありがとうございました。今回はひょっとしたらZennの記事の中で一番長いんじゃないかというくらい、長くなりました。Bookにする必要もないかなと始めたら、この有様です。

ここまでを知っていれば、後はエンジニアの方ならドキュメントを見ながら応用で進めて行けると思います。積極的にWordPressの仕様を学んでいこうとは思わないかもしれませんが、なぜ、この分野でWordPress一強が続いているのかがわかるような気がしませんか。何らかのサービスなどを作る際には、参考になると思います。

また、以下をカスタムブロックの環境構築のお供にどうぞ。
https://zenn.dev/antez/articles/58307946cf4f3e
https://zenn.dev/antez/articles/a9d9d12178b7b2

Discussion