Headless Wordpressの勉強〜プラグインの作成以降〜 2022.07.14
WordPressからGitHub Actionsをキックする(実装編)
前回の続き。
GitHub Actionsをキックするプラグインの作成
どういった操作でキックされたかわかるように細かくイベントを追加。
# frontend/.github/workflows/deploy.yml
# ...
- wp_github_actions_kicker_post_published
- wp_github_actions_kicker_post_trashed
- wp_github_actions_kicker_post_switched_to_draft
- wp_github_actions_kicker_page_published
- wp_github_actions_kicker_page_trashed
- wp_github_actions_kicker_page_switched_to_draft
# ...
プラグインを実装。記事および固定ページの追加・更新・削除に反応するようにしている。
<?php
// wordpress/plugins/github-actions-kicker/index.php
/*
Plugin Name: GitHub Actions Kicker
Plugin URI:
Description: GitHub Actionのトリガー
Version: 1.0.0
Author: Yuji Ito
Author URI:
License: MIT
*/
if ( ! defined( 'ABSPATH' ) ) exit;
require_once(__DIR__ . '/src/settings.php');
require_once(__DIR__ . '/src/settings-page.php');
require_once(__DIR__ . '/src/hooks.php');
<?php
// wordpress/plugins/github-actions-kicker/src/hooks.php
if ( ! defined( 'ABSPATH' ) ) exit;
function on_post_published($post_id, $post) {
error_log('---post published---');
dispatch_github_actions($post_id, $post, 'wp_github_actions_kicker_post_published');
}
function on_post_trashed($post_id, $post) {
error_log('---post trashed---');
dispatch_github_actions($post_id, $post, 'wp_github_actions_kicker_post_trashed');
}
function on_post_switched_to_draft($post_id, $post) {
error_log('---post switched to draft---');
dispatch_github_actions($post_id, $post, 'wp_github_actions_kicker_post_switched_to_draft');
}
function on_page_published($post_id, $post) {
error_log('---page published---');
dispatch_github_actions($post_id, $post, 'wp_github_actions_kicker_page_published');
}
function on_page_trashed($post_id, $post) {
error_log('---page trashed---');
dispatch_github_actions($post_id, $post, 'wp_github_actions_kicker_page_trashed');
}
function on_page_switched_to_draft($post_id, $post) {
error_log('---page switched to draft---');
dispatch_github_actions($post_id, $post, 'wp_github_actions_kicker_page_switched_to_draft');
}
function dispatch_github_actions($post_id, $post, $github_event_type) {
error_log('post send: ' . $post_id);
// error_log(print_r($post, true));
$header = [
'Authorization: token ' . esc_attr(get_option('github_token')),
'Accept: application/vnd.github.everest-preview+json',
'User-Agent: WordPress_webhook_post', // You must specify user-agent
];
$data = [
'event_type' => $github_event_type,
];
$url = curl_init('https://api.github.com/repos/' . esc_attr(get_option('github_account'))
. '/' . esc_attr(get_option('github_repository')) . '/dispatches');
if ($post->post_status === 'publish') {
curl_setopt($url, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($url, CURLOPT_RETURNTRANSFER, true);
curl_setopt($url, CURLOPT_HEADER, true);
curl_setopt($url, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($url, CURLOPT_HTTPHEADER, $header);
curl_exec($url);
}
if (!(defined( 'REST_REQUEST' ) && REST_REQUEST )) {
error_log(REST_REQUEST);
}
}
add_action('publish_post', 'on_post_published', 10, 2);
add_action('trash_post', 'on_post_trashed', 10, 2);
add_action('draft_post', 'on_post_switched_to_draft', 10, 2);
add_action('publish_page', 'on_page_published', 10, 2);
add_action('trash_page', 'on_page_trashed', 10, 2);
add_action('draft_page', 'on_page_switched_to_draft', 10, 2);
<?php
// wordpress/plugins/github-actions-kicker/src/settings-page.php
if ( ! defined( 'ABSPATH' ) ) exit;
function github_actions_settings_page()
{
if (true == $_GET['settings-updated']) { ?>
<div id="settings_updated" class="updated notice is-dismissible">
<p><strong>設定を保存しました。</strong></p>
</div>
<?php } ?>
<div class="wrap">
<h2>設定</h2>
<form method="post" action="options.php">
<?php settings_fields('github-actions-settings'); ?>
<?php do_settings_sections('github-actions-settings'); ?>
<table class="form-table">
<tr>
<th>Github アカウント</th>
<td>
<input
type="text"
name="github_account"
id="github_account"
value="<?= esc_attr(get_option('github_account')); ?>"
required="required"
/>
</td>
</tr>
<tr>
<th>リポジトリ名</th>
<td>
<input
type="text"
name="github_repository"
id="github_repository"
value="<?= esc_attr(get_option('github_repository')); ?>"
required="required"
/>
</td>
</tr>
<tr>
<th>Personal access tokens</th>
<td>
<input
type="password"
name="github_token"
id="github_token"
value="<?= esc_attr(get_option('github_token')); ?>"
required="required"
/>
</td>
</tr>
</table>
<?php submit_button('設定を保存', 'primary large', 'submit', true, array('tabindex' => '1')); ?>
</form>
<div id="pluginurl" data-pluginurl="<?= esc_attr(plugins_url()); ?>"></div>
</div>
<?php } ?>
<?php
// wordpress/plugins/github-actions-kicker/src/settings.php
if ( ! defined( 'ABSPATH' ) ) exit;
function create_menu() {
add_options_page(
'Github Actions',
'Github Actions',
'administrator',
'github_actions_setting',
'github_actions_settings_page'
);
add_action( 'admin_init', 'github_actions_register_settings' );
}
add_action( 'admin_menu', 'create_menu' );
function github_actions_register_settings() {
register_setting( 'github-actions-settings', 'github_account' );
register_setting( 'github-actions-settings', 'github_repository' );
register_setting( 'github-actions-settings', 'github_token' );
}
プラグインができたら、実際にWordPress管理画面の設定を見てみる。
アカウント名、リポジトリ名、Personal Access Tokenをそれぞれ入力して保存。記事および固定ページの追加・更新・削除に反応してGitHub Actionsがキックされる。
プラグインをレンタルサーバーに転送して再確認。今回はscp
で転送した。レンタルサーバーからもGitHub Actionsをキックすることができた。
WordPressのプレビューをNext.jsで見れるようにする
WordPress側はfunctions.php
でプレビューボタンを押したときの動作をフックする。今回は、記事のプレビューだけ行う。
カスタムテーマを追加
% mkdir -p themes/headless-wordpress-research && touch $_/{index.php,functions.php,style.css}
テーマは既存のプラグインとかぶらないようにする。Headlessプラグインはすでに存在するのでテーマ画面にアップデートボタンが表示されてしまう。
/*
Theme Name: Headless Wordpress Research
*/
functions.php
にプレビュー用のアクションを書く。localhostのときだけNext.jsのポートを見るように指定。
<?php
add_action('template_redirect', function () {
$is_https = isset($_SERVER['HTTPS']);
$is_localhost = preg_match('/^localhost/', $_SERVER['HTTP_HOST']);
$preview_protocol = $is_https ? 'https://' : 'http://';
$preview_host = $is_localhost ? 'localhost:3000' : $_SERVER['HTTP_HOST'];
if (!is_admin() && isset($_GET['preview']) && $_GET['preview'] == true) {
$redirect = add_query_arg(
[
'id' => $_GET['preview_id'] ? $_GET['preview_id'] : $_GET['p'],
'nonce' => wp_create_nonce( 'wp_rest' )
],
$preview_protocol . $preview_host . '/preview'
);
wp_redirect($redirect);
}
});
コンテナ側の修正
インストールスクリプトでwp-jsonを有効化するためにパーマリンクを変更し、独自テーマの有効化も行う。
# wp-install.sh
# ...
wp option update permalink_structure '/%postname%/'
# ...
wp theme activate headless-wordpress-research
独自テーマをボリュームマウントしておく。
# docker-compose.yml
# ...
- ./wordpress/themes/:/var/www/html/wp-content/themes/
# ...
コンテナを再ビルドして変更内容を反映しておく。
% docker-compose build
% docker-compose up -d
% docker exec -it --user 33:33 headless-wp-wordpress /bin/bash ./scripts/wp-install.sh
フロントエンドの修正
フロント側にプレビュー用のコンポーネントを追加。
% touch frontend/pages/preview.tsx
Cookie認証するのでfetchのオプションでcredentials: 'include'
として常にCookieを送信するようにしておく。
APIのURLは環境変数で渡せるようにしておく。
import { useEffect, useState } from "react";
import type { NextPage } from "next";
import { useRouter } from "next/router";
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL || "";
const Preview: NextPage = () => {
const router = useRouter();
const [post, changePost] = useState<any>(null);
const { id, nonce } = router.query;
useEffect(() => {
if (!id || !nonce) return;
const requestHeaders: HeadersInit = new Headers();
requestHeaders.set("X-WP-Nonce", `${nonce}`);
(async () => {
const response = await fetch(`${API_BASE}posts/${id}?_embed&status=draft`, {
credentials: "include",
headers: requestHeaders,
});
if (!response.ok) {
console.error("something wrong.");
return;
}
changePost(await response.json());
})();
}, [id, nonce]);
return (
<div>
{post ? (
<div>
<h1>{post.title.rendered}</h1>
<article dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
</div>
) : null}
</div>
);
};
export default Preview;
ローカル環境用に環境変数を追加。
# .env.local
NEXT_PUBLIC_API_BASE_URL="http://localhost:8000/wp-json/wp/v2/"
これでフロントエンドとWordPressを両方立ち上げればプレビューを確認できる。
リモートで確認
SSGの場合は、環境変数は.env.local
でいいようだが、.env.local
はバージョン管理しないので、GitHub Actionsのワークフローで使えるように設定で追加する必要がある。
リポジトリの(アカウントのではない)Settings→Secrets→ActionsにNEXT_PUBLIC_API_BASE_URL
を定義。
ビルドするステップの前に、secretsを書き加えた.env.local
を作成するステップを追加する。
# frontend/.github/workflows/deploy.yml
- name: Set env
run: |
touch .env.local
echo NEXT_PUBLIC_API_BASE_URL=${{ secrets.NEXT_PUBLIC_API_BASE_URL }} >> .env.local
テーマをリモートに送信。今回はscp
で転送した。そして、フロントエンドの変更をGitHubにプッシュすればレンタルサーバー側でもプレビューを確認できる。
おまけ
フロントエンドのコンテナ化
当初、いろいろ試していて、変にローカルのnode_modulesをコピーしてしまったのか、SWCが見つからないエラーでハマった。公式の例に沿っていくとちゃんとできた。
# frontend/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock* .
RUN yarn install
COPY pages ./pages
COPY styles ./styles
COPY public ./public
COPY .env*.local .
COPY next-env.d.ts .
COPY next.config.js .
COPY tsconfig.json .
CMD yarn dev
# docker-compose.yml
# ...
frontend:
container_name: headless-wp-frontend
build:
context: ./frontend
dockerfile: Dockerfile
volumes:
- ./frontend/pages:/app/pages
- ./frontend/styles:/app/styles
- ./frontend/public:/app/public
restart: always
ports:
- 3000:3000
# ...
ホットリロードも効いている。
カスタムプラグインとテーマもGitHub Actionsでデプロイする
% cd wordpress
% mkdir -p .github/workflows && touch $_/deploy.yml
name: Deploy custom WordPress plugin and theme
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-20.04
steps:
- name: 🚚 Checkout
uses: actions/checkout@v2
- name: 📂 Sync theme files
uses: SamKirkland/FTP-Deploy-Action@4.3.0
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USER }}
password: ${{ secrets.FTP_PASSWORD }}
server-dir: "<PATH TO WORDPRESS>/wp-content/themes/headless-wordpress-research/"
local-dir: "./themes/headless-wordpress-research/"
- name: 📂 Sync plugin files
uses: SamKirkland/FTP-Deploy-Action@4.3.0
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USER }}
password: ${{ secrets.FTP_PASSWORD }}
server-dir: "<PATH TO WORDPRESS>/wp-content/plugins/github-actions-kicker/"
local-dir: "./plugins/github-actions-kicker/"