見出し画像

【WordPress】静的サイト+WordPressで小説サイトを構築する③

3回目はカスタムフィールドの追加方法と、作品を投稿する際に使えるテンプレート機能の使い方や、プレビュー機能が使えないときの対処法についてになります。
前回までの内容は以下の記事を御覧ください。


1. カスタムフィールドの追加

1-1. カスタムフィールドの設定

カスタム投稿タイプでデフォルトのカスタムフィールドを使うには、投稿タイプの設定時に 'supports' => array() で以下のように設定します。

'supports' => array(
  'title',          // タイトル
  'editor',         // 内容の編集
  'revisions',      // リビジョンを保存する
  'custom-fields',  // カスタムフィールドの使用
  'page-attributes' // メニューの順序(hierarchicalと併せて記述)
)

これによってカスタムフィールド自体は使えるようになりますが、決まったフォーマットのカスタムフィールドを使用する場合、デフォルトのままでは使いにくいため、独自のカスタムフィールドを functions.php に追加していきます。

以下は実際に使用しているカスタムフィールドの例です。

独自カスタムフィールド

内訳については以下のような感じです。

  • シリーズ:

    • シリーズ名を表示するのに使用。

    • カスタムタクソノミー novels_cat でチェックしたカテゴリー(子カテゴリー)を引用し、自動入力に対応。親カテゴリーと、name が「短編」の場合は除外。

    • カスタムタクソノミーを ジャンル > シリーズ のみで運用する場合は直接カスタムタクソノミーを呼び出せばいいため、そちらでも大丈夫です。それ以外の作品区分を含む場合も条件分岐で対応は可能なため、あくまで好みとテンプレート効率の可。

  • ステータス:

    • 作品内にて完結状態を表示するのに使用。

    • fin. / to be continued... の二択のため、ラジオボタンにて選択。チェックボックスでも可。

  • 更新日:

    • 改訂日として記載。WordPressの保持する最終更新日を取得する the_modified_date() では誤字脱字修正まで拾ってしまうため、あくまで大幅な改稿時にのみ設定したいため、手動で入力。

  • 前話(URL):

  • 次話(URL):

    • 静的HTMLページと跨ぐことがあるため、手動でリンクを設定。テンプレート上では条件分岐にて値が空のときのレイアウトも制御。

自分の場合は作品INDEXを静的HTMLにて構築しているため、基本的には使用するのはこの程度になります。
プラグイン関係についてはテンプレートファイルにて直接呼び出していますが、例えばapply_filters()関数を使用すれば、ショートコードをカスタムフィールドにて使用することも可能です。
これについては 1-5. を参照してください。

他には専用のあとがきを挿入するとか。
キャプションは抜粋を使用してもいいと思います。
一次創作の場合はキャラ紹介や設定、世界観など、作品の補完に使う方法もあります。

シリーズについてはカスタムタクソノミーを呼び出せばいい話ではあるのですが、自分の場合、すべてのシリーズ名をカスタムタクソノミーに設定しているわけではないのと、シリーズ名の存在しない中編、及び短編区分もカスタムタクソノミー(カテゴリー)にて分類しているため、なんとなくカスタムフィールドで出力するようにしています。
代わりに多少、入力の手間を省くため、カスタムタクソノミー novels_cat でチェックした子カテゴリーを自動反映させるようにしました。
select要素でも良かったのですが、事前にカテゴリーを設定しておく必要性を考えると、こちらに落ち着きました。

以上、こんな感じでカスタムフィールドを設定していきます。

カスタムフィールドを設定するのに最も簡単なのはプラグインを使うことです。
有名なところだと Advanced Custom Fields でしょうか。
ですがここでは functions.php に追加していく方法を書いていこうと思います。
極力、プラグインを使用しなくても済む部分については使用しない方向で実装していきます。

1-2. カスタムフィールドを追加する(基本)

今回は custom_fields.php というファイルを作り、functions.php にて読み込みます。このあたりについては前回の記事の 3-1. functions.php を参照してみてください。

まずはカスタムフィールドの作成です。

// カスタムフィールドの作成
add_action('admin_menu', 'create_custom_fields');
function create_custom_fields() {
  add_meta_box(
    'custom_field_1',        // セクションID
    'カスタムフィールド',      // 編集画面に表示されるタイトル
    'insert_custom_fields',  // フォーム部分を指定する関数名
    'post'                   // 対象の投稿タイプ
  );
}

カスタムフィールドを管理画面で表示する際のIDとタイトル(デフォルトでは「カスタムフィールド」と表示されている部分)、関数名と投稿タイプを指定します。
他には編集画面のセクションが表示される場所、優先度などを設定できます。
関数リファレンス/add meta box - WordPress Codex 日本語版

次に、編集画面でカスタムフィールドを表示するためのフォーマットを書いていきます。

基本としては以下のように書きます。

function insert_custom_fields() {
  global $post;

?>

<table id="list-table" style="width:100%">
  <thead>
    <tr>
      <th class="left">名前</th>
      <th>値</th>
    </tr>
  </thead>
  <tbody id="the-list" data-wp-lists="list:meta">
    <tr>
      <td class="left"><label for="カスタムフィールド名1">sample:</label></td>
      <td><input type="text" name="カスタムフィールド名1" style="width:100%" value="<?= esc_attr(get_post_meta($post->ID, "カスタムフィールド名1", true)) ?>"></td>
    </tr>
    <tr>
      <td class="left"><label for="カスタムフィールド名2">sample:</label></td>
      <td><input type="text" name="カスタムフィールド名2" style="width:100%" value="<?= esc_attr(get_post_meta($post->ID, "カスタムフィールド名2", true)) ?>"></td>
    </tr>
  </tbody>
</table>

<?php
}

数が多くなるとコードが長くなるので、echo文は使わずに書いていきます。
追加するカスタムフィールド名をそれぞれ、for属性とinputのname属性、及びvalue属性に指定します。
value属性は以下のように書きます。

value="<?= esc_attr(get_post_meta($post->ID, "カスタムフィールド名", true)) ?>"

続けて保存するための設定を最後に追加します。

// カスタムフィールドを保存
function saveExtraField($post_id) {
  $fields = array(
    'カスタムフィールド名1',
    'カスタムフィールド名2',
  );

  foreach ($fields as $field) {
    $value = isset($_POST[$field]) ? sanitize_text_field($_POST[$field]) : '';
    update_post_meta($post_id, $field, $value);
  }
}
add_action('save_post', 'saveExtraField');

以上が基本のやり方です。

1-3.カスタムフィールドを追加する(応用)

今度は実際にカスタムタクソノミーから子カテゴリーを引っ張ってくるためのコードと、ラジオボタンを追加して実際に書いてみます。
追加するカスタムフィールド名は以下のとおりです。

  • txt_series   … シリーズ

  • txt_status     … ステータス

  • txt_revised   … 更新日

  • txt_prevlink … 前話(URL)

  • txt_nextlink … 次話(URL)

カスタムタクソノミーは引き続き novels_cat を使用します。

// カスタムフィールドの作成
add_action('admin_menu', 'create_custom_fields');
function create_custom_fields() {
  add_meta_box(
    'custom_field_1',        // セクションID
    '小説投稿設定',           // 編集画面に表示されるタイトル
    'insert_custom_fields',  // フォーム部分を指定する関数名
    'novels'                 // 対象の投稿タイプ
  );
}

// カスタムフィールドの入力形式
function insert_custom_fields() {
  global $post;

  // カテゴリーの自動入力
  $selected_cats = array();
  $terms = get_the_terms($post->ID, 'novels_cat');
  if ($terms && !is_wp_error($terms)) {
    foreach ($terms as $term) {
      if ($term->name !== '短編' && $term->parent !== 0 && is_object_in_term($post->ID, 'novels_cat', $term->term_id)) { // 子カテゴリーのみ取得
        $selected_cats[] = $term->name;
      }
    }
  }
?>

<table id="list-table" style="width:100%">
  <thead>
    <tr>
      <th class="left">名前</th>
      <th>値</th>
    </tr>
  </thead>
  <tbody id="the-list" data-wp-lists="list:meta">
    <tr>
      <td class="left"><label for="txt_series">シリーズ:</label></td>
      <td><input type="text" name="txt_series" style="width:100%" value="<?= esc_attr(join(', ', $selected_cats)) ?>"></td>
    </tr>
    <tr>
      <td class="left"><label for="txt_status">ステータス:</label></td>
      <td>
        <label><input type="radio" name="txt_status" value="fin." <?= (get_post_meta($post->ID, 'txt_status', true) === 'fin.') ? 'checked' : ''; ?>>fin.</label><br>
        <label><input type="radio" name="txt_status" value="to be continued..." <?= (get_post_meta($post->ID, 'txt_status', true) === 'to be continued...') ? 'checked' : ''; ?>>to be continued...</label>
      </td>
    </tr>
    <tr>
      <td class="left"><label for="txt_revised">更新日:</label></td>
      <td><input type="text" name="txt_revised" style="width:100%" value="<?= esc_attr(get_post_meta($post->ID, "txt_revised", true)) ?>"></td>
    </tr>
    <tr>
      <td class="left"><label for="txt_prevlink">前話:</label></td>
      <td><input type="text" name="txt_prevlink" style="width:100%" value="<?= esc_attr(get_post_meta($post->ID, "txt_prevlink", true)) ?>"></td>
    </tr>
    <tr>
      <td class="left"><label for="txt_nextlink">次話:</label></td>
      <td><input type="text" name="txt_nextlink" style="width:100%" value="<?= esc_attr(get_post_meta($post->ID, "txt_nextlink", true)) ?>"></td>
    </tr>
  </tbody>
</table>

<?php
}

// カスタムフィールドを保存
function saveExtraField($post_id) {
  $fields = array(
    'txt_series',
    'txt_status',
    'txt_revised',
    'txt_prevlink',
    'txt_nextlink',
  );

  foreach ($fields as $field) {
    $value = isset($_POST[$field]) ? sanitize_text_field($_POST[$field]) : '';
    update_post_meta($post_id, $field, $value);
  }
}
add_action('save_post', 'saveExtraField');

以上です。
カスタムタクソノミーから子カテゴリーを取得するコードは以下の部分になります。

  // カテゴリーの自動入力
  $selected_cats = array();
  $terms = get_the_terms($post->ID, 'novels_cat');
  if ($terms && !is_wp_error($terms)) {
    foreach ($terms as $term) {
      if ($term->name !== '短編' && $term->parent !== 0 && is_object_in_term($post->ID, 'novels_cat', $term->term_id)) { // 子カテゴリーのみ取得
        $selected_cats[] = $term->name;
      }
    }
  }

これにより、カスタムタクソノミーで分類したシリーズにチェックを入れ、下書き保存か公開・非公開を問わず保存を行えば、シリーズ欄のところに子カテゴリーから引用されたシリーズ名が入るはずです。
下記の画像でいうと、シリーズ1とシリーズ2にチェックが入った場合に引用されます。

カスタムタクソノミーから引用して自動で入力する

親カテゴリーはジャンル名を指定していますので、除外しました。
また、短編という分類についても除外するように指定してあります。

$term->name !== '短編' && $term->parent !== 0

上記の部分がターム名「短編」と親カテゴリーを除外する指定です。
今回は手打ちでの入力を前提としてtype="text"を使用していますが、select要素にすることも可能です。
その場合は事前にカテゴリーを登録しておく必要があります。

また、value属性の記述が他とは違うため、注意してください。

value="<?= esc_attr(join(', ', $selected_cats)) ?>"

1-4. テンプレートに記述する

カスタムフィールドを任意の場所でテンプレートに読み込ませるには以下のように書きます。

<?php get_post_meta(get_the_ID(), 'カスタムフィールド名', true); ?>

// または

<?php echo post_custom('カスタムフィールド名'); ?>

また、条件分岐が必要な場合は以下のように書きます。

<?php if ( get_post_meta( get_the_ID(), 'カスタムフィールド名', true ) ) : ?>
  <?php echo post_custom('カスタムフィールド名'); ?>
<?php else : ?>
  //カスタムフィールドの値が空のとき
<?php endif; ?>

上記はすべてメインループ内での出力方法になります。
ループ外では以下のようにpos_idを指定するか取得する必要があります。

<?php
	global $wp_query;
	$postID = $wp_query->post->ID;
	echo get_post_meta($postID, 'カスタムフィールド名', true);
?>
<?php
  global $wp_query;
  $postID = $wp_query->post->ID;
  $custom_field_value = get_post_meta($postID, 'カスタムフィールド名', true);
?>

<p>カスタムフィールドの値は <?php echo $custom_field_value; ?> です。</p>

また、直接post_idを指定することで、別のページのカスタムフィールドを呼び出すことも可能です。

1-5. カスタムフィールドでショートコードを使う

カスタムフィールドでショートコードを使う際は、apply_filters()関数を使ってテンプレートに以下のように記述します。

<?php echo apply_filters('the_content', get_post_meta( get_the_ID(), 'カスタムフィールド名', true));?>

また、以下のようにfunctions.phpに記述することでも使えるようになります。

// カスタムフィールドにショートコードを使用する
function custom_field_shortcode( $value ) {
  // ショートコードを解析する
  $value = do_shortcode( $value );
  return $value;
}
add_filter( 'get_post_metadata', 'カスタムフィールド名', 10, 4 );
// テンプレートに記述
<?php echo get_post_meta( get_the_ID(), 'カスタムフィールド名', true ); ?>


2. テンプレートを作る

WordPressのテーマには以下のようなテンプレートファイルが含まれています。(最小要件)

  • index.php:トップページのテンプレート

  • single.php:投稿ページのテンプレート

  • page.php:固定ページのテンプレート

  • archive.php:アーカイブページのテンプレート

  • 404.php:404エラーページのテンプレート

それぞれ表示順などの優先順位があるのですが、テンプレート階層 や詳しい作り方については割愛します。
基本的には既存のテーマを参考に独自テーマを作るか、子テーマとして作っていくことになると思います。
無料で有名なところだと、やはり Cocoon でしょうか。
自分の場合はどうせデザインを考えるなら自作したほうが早い派なのと、共存運用を行っているので、旧来の方法でゴリゴリ書いています。

カスタム投稿タイプで専用のテンプレートを使うには以下のようなテンプレートファイルを用意します。
また、固定ページ、及びシングルページにて個別のデザインを使いたい場合は投稿タイプ名の代わりにslugを指定します。

  • single-{投稿タイプ名}.php

  • page-{投稿タイプ名}.php

  • archive-{投稿タイプ名}.php

カスタム投稿タイプで階層機能(親子ページ)をtrueにしている場合、固定ページ扱いになりますので、使用するテンプレートは page-{投稿タイプ名}.php です。
また、これとは別にWordPressの固定ページには編集画面から使用できるテンプレート機能があります。

<?php
/*
* Template Name: [テンプレート名]
* Template Post Type: [投稿タイプ]
*/
?>

テンプレートの冒頭で上記のように記述すると、編集画面のページ属性からテンプレートを選ぶことができるようになります。

もしも作品別、ジャンル別に使い分けをしたい時は条件分岐で記述を分けるよりも、上記のようなテンプレートを用意して切り替える方法をオススメします。
作品INDEXもカスタム投稿タイプで作っていく場合はなおのこと、テンプレート機能を使うことをオススメします。
また、このとき使用するのは WP_Query を使用したサブループではなく、メインループで大丈夫です。


3. パーマリンクを書き換える

サブディレクトリにWordPressをインストールすると一生ついてまわるのがパーマリンク問題です。
ここではプレビューが機能しないときの対処法と、/category/ をURLから削除する方法をまず書いていきたいと思います。

3-1.プレビューのパーマリンクを書き換える

WordPressをサブディレクトリにインストールしてルート表示していると、下書きのプレビューが機能しないことがあります。
そのため、RewriteRuleを書き換えて対応することになります。
.htaccessを使っても良いのですが、今回はフィルターフックを利用して書き換えます。
インストールフォルダは /wp です。

function custom_permalink_filter( $permalink, $post ) {
  // 下書きの場合はパーマリンクにカテゴリーを含めないようにする
  if ( $post->post_status === 'draft' ) {
    $permalink = str_replace( '%category%/', '', $permalink );
  }
  // プレビューの場合のみパーマリンクを書き換える
  if ( is_preview() ) {
    $permalink = str_replace( home_url('/'), home_url('/wp/') . 'index.php/', $permalink );
  }

  return $permalink;
}
add_filter( 'post_link', 'custom_permalink_filter', 10, 2 );
add_filter( 'post_type_link', 'custom_permalink_filter', 10, 2 );

上記のコードはパーマリンクの設定でカスタム構造に /%category%/ を利用している場合のやり方です。
これを functions.php に書きます。
自分の場合は rewrite_rule.php を作り、その中に書いて functions.php で読み込んでいます。

3-2. カテゴリー一覧のURLから /category/ を削除する

これはよく見かけるやり方ですが、URLに含まれる /category/ を削除する方法です。

// カテゴリーページのURLからcategoryを削除する
add_filter( 'user_trailingslashit', 'remove_category_from_url' );
function remove_category_from_url( $link ) {
  return str_replace( '/category/', '/', $link );
}

// ルールをフラッシュする
add_action( 'init', 'flush_rules' );
function flush_rules() {
  global $wp_rewrite;
  $wp_rewrite->flush_rules();
}

// ルールを生成する
add_filter( 'generate_rewrite_rules', 'add_new_rules' );
function add_new_rules( $wp_rewrite ) {
  $new_rules = array(
    '(.+)/page/(.+)/?' => 'index.php?category_name=' . $wp_rewrite->preg_index(1) . '&paged=' . $wp_rewrite->preg_index(2),
  );
  $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}

ただ、一回目の 2-4. Rewrite設定 でも触れましたが、これをやるとどうも固定ページやカスタム投稿タイプがカテゴリーと誤認識され、ページ送りなどで404エラーを出すことがあるようです。
2ページ目以降のURLを ?page=ページ番号 に変えてもエラーが出る場合は、カテゴリーとして認識されている可能性があります。




以上でした。今回はここまでになります。
次は更新情報の出力方法や運用方法、ブログ周りについて少し書いていこうと思います。


この記事が気に入ったらサポートをしてみませんか?