WordPressを静的ページビルダーにしたい（未完成）

以下の内容は、WordPressの正しい使い方ではないので、その程度のものとしてお読みください。
また、単に静的サイトを作るなら、[Simply Static](https://ja.wordpress.org/plugins/simply-static/)というプラグインが使いやすいようです。

## やりたいこと

- WordPressで静的サイトを管理したい
- ブロックエディタで記事を書きたい
- 外部からのサイト閲覧者には、`wp-admin/` ディレクトリ等にアクセスしてほしくない
- 一括生成（SSG）のような、時間のかかることはやりたくない

## 基本的なアイディア

- save_postフックを利用して、記事が保存される度にfile_put_contents()する

## 現時点で静的化できていないこと

以下のページには、動的処理が必要。

- archiveページ（ブログ投稿の一覧ページ）
- sitemap.xml
- 記事検索
- メールフォーム等

## 不安要素

WordPressの本来の使い方ではないので、将来的なバージョンアップによって、htmlの構造やcssが変化した場合、追従できないかもしれません。

## 作業手順1：サブドメインを3つ用意する

サブドメインを3つ用意します。

1. www\.example\.com -> 外部からアクセスされるドメイン
1. img\.example\.com -> アップロードしたメディアファイル用のドメイン
1. admin\.example\.com -> 管理画面用のドメイン

## 作業手順2：フォルダ構成を考える

以下のようなフォルダ構成を作っていきます。

```
~/
└── admin_example_com/
    ├── wp-admin/
    └── wp-content/
        ├── uploads/
        └── plugins/
            └── my_plugin/
                ├── www/
                └── page_build.php
```

私は、この記事を書くためにロリポップレンタルサーバーで試作しましたが、他のレンタルサーバーでも同様のことができるはずです。
たとえば、ConoHa wingの場合は、サブドメインの割り当てができない代わりに、シンボリックリンクが張れます。

admin_example_com/ ディレクトリに、admin\.example\.comのサブドメインを割り当てます。（サブドメインの設定方法は、[ロリポップレンタルサーバーのヘルプ](https://lolipop.jp/manual/user/doku-subsett/)を参照してください）

admin_example_com/wp-content/plugins/my_plugin/www/ ディレクトリに、www\.example\.comのサブドメインを割り当てます。

admin_example_com/wp-content/uploads/ ディレクトリに、img\.example\.comのサブドメインを割り当てます。

## 作業手順3：WordPressのテーマは外部から隠す

WordPressのテーマは、何も表示させないテーマにしておくと良いです。

なお、表示の無いテーマにしたとしても、管理画面用のサブドメインには、外部からアクセス可能なので、WordPressのセキュリティプラグインを入れて対策しておいた方が良いです。

```php
<?php
// index.php

if ( ! defined( 'ABSPATH' ) ) exit;
if ( ! current_user_can( 'edit_posts' ) ) exit;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>確認用</title>
  <?php wp_head(); ?>
</head>
<body>
<?php
if ( have_posts() ) {  // wpの内容表示
	while ( have_posts() ) {
  	the_post();
    if(is_page() || is_single()) {
    ?>
      <main style="max-width:30rem;">
      <h1><?php the_title(); ?></h1>
      <p><?php the_time('Y-m-d G:i'); ?></p>
      <?php the_content(); ?>
      </main>
    <?php
    }else {
    ?>
      <section style="max-width:30rem;">
      <p><?php the_time('Y-m-d'); ?></p>
      <h3>タイトル:<a href="<?php the_permalink(); ?>">
      <?php the_title(); ?></a></h3>
      <p>要約:<br/><?php the_excerpt(); ?></p>
      </section>
      <hr/>
    <?php
    }
  }
} else {  // wpの内容が無いとき
  echo '記事がありませんでした';
}
?>
<?php wp_footer(); ?>
</body>
</html>
```

## 作業手順4：HTMLファイルの出力用プラグインを作る

[save_postというフック](https://developer.wordpress.org/reference/hooks/save_post/)を使います。
独自プラグインとして、以下のような関数を登録します。

```php
<?php

/*
* Plugin Name: static page builder plugin
*/

if ( ! defined( 'ABSPATH' ) ) exit;

function my_save_post ($post_id) {
  // auto saveのとき何もしない
  if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
    return;
  }
  // publish でないとき（下書きなど）何もしない
  $post_status = get_post_status($post_id);
  if ($post_status !== 'publish') {
    return;
  }

  // 保存先
  $file_name = ABSPATH . 'wp-content/plugins/my_plugin/www/' . $post_id . '.html';

  $post = get_post($post_id);

	$html_src = '<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . esc_html($post->post_title) . '</title>
</head>
<body>' . PHP_EOL . wp_kses_post($post->post_content) . PHP_EOL . '</body>';

  file_put_contents($file_name, $html_src);

}

add_action('save_post', 'my_save_post');
```

ここでのポイントは、`body` タグ内をすべて`$post->post_content`にしてしまうことです。
WordPressは、ブロックエディタの出現によって、表現力が大きく向上しました。
このことを最大限有効に使いたいです。

共通ヘッダーやフッター、サイドメニューなどをブロックにしてしまえば、使いまわしできます。

※ただし、静的ファイルに出力してしまうので、後から動的に変更させることはできません。
もし後から一括変更のようなことがしたい場合は、HTMX等を検討したほうが良いと思います。

なお、file_put_contents()は、WordPress的には非推奨のようで、[WP_Filesystem](https://developer.wordpress.org/reference/functions/wp_filesystem/)を利用した方が良いようです。

上記の例では、記事IDを名前にしてファイル保存していますが、slug等を利用しても良いと思います。
slugが、`home`のときはindex.htmlとして保存するなど、ルーティング設定のようなものを書いていくと、メンテナンスが楽になりそうです。

投稿記事の保存時には`save_post_post` 、固定ページの保存時には`save_post_page` 等のフックが使えます。
また、ゴミ箱に移動するときのフックは、`trashed_post`があり、記事が削除されたときには`before_delete_post`があります。

またcomposer等が使える環境なら、Twig等のテンプレートエンジンを使うと、HTMLの出力が便利になるはずです。

## 作業手順5：画像やその他メディアの取扱い

WordPressでは、アップロードした画像データは、`wp-content/uploads/` ディレクトリに保存されます。
そして、記事中の画像のsrcも、admin\.example\.com/wp-content/uploads/ディレクトリにリンクされてしまいます。

これを変更するためには、`wp-admin/options.php` にアクセスし、`upload_url_path` を設定変更します。

先ほど用意した、img\.example\.com/ を、`upload_url_path` に設定しておけば、画像がリンクされるはずです。

## （未完成部分）ブログ投稿の一覧ページや、検索ページをどうするか

ここまでで、投稿ページや固定ページ等の出力はできるはずです。

問題は、一覧ページや、検索機能の作り方です。
ネット上を調べると、以下のような方法が見つかります。

- /wp-json/を使って、JavaScriptのfetch()等で取得する
- /wp-json/を使って、phpのcurl()等で取得する
- wp-load.phpで、WordPressを呼び出して、wp-query()等で取得する
- phpのPDOで、直接データベースに問い合わせる

いずれも、動的処理をすることになるので、メンテナンスが面倒そうです。

wp-jsonを使う方法が、比較的安全かなとは思いますが、結局はadmin\.example\.comにアクセスすることになるので、あまりやりたくありません。

理想は、完全に静的化することなので、記事一覧に必要な分（タイトルやURL、要約文やアイキャッチ画像等）だけの静的なjsonファイルを投稿記事全体について出力することなのですが、記事数が多くなると、一括出力も大変になっていきます。
それに、この方法では本文の検索機能が追加できません。

さらに、その一括出力を実行するフックを何にするかが、また悩ましいです。
記事を保存するたびに、複数のファイルを保存するのは、やや過剰のような気もします。

管理画面のメニューボタンを設定し、「記事一覧jsonをビルド」というボタンを作ってみても良いのかもしれません。
しかし、記事更新のたびにビルドしなおさないといけないようなら、AstroなどのSSGで十分ということになってしまいます。

いくつか方法を試してみて、ちょうど良い方法が見つかったら、追記したいと思います。