wordpress-plugin-core について
wordpress-plugin-core は、WordPress プラグイン開発者向けのスキルで、構成パターン、実装方法、セキュリティ対策を扱います。権限、入出力、リクエスト検証、データベースクエリの処理を例とともに説明します。
主な機能
- 01シンプル、OOP、PSR-4 の構成を解説
- 02プラグインヘッダーとセキュリティ基礎
- 03権限チェックと nonce 検証を説明
- 04入力のサニタイズと出力のエスケープ
- 05安全なデータベースクエリの例
- 06よくある問題と予防策を整理
活用シーン
- 01WordPress プラグインのコード構成を計画
- 02フォームや AJAX リクエストを処理
- 03ユーザー入力とページ出力を保護
- 04ユーザー入力を含むデータベースクエリを作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill wordpress-plugin-coreインストール後、Claude Code で/wordpress-plugin-coreを入力して読み込まれたか確認します。
同梱ファイルREADME.mdSKILL.json
詳細
WordPress プラグイン開発(コア)
ステータス:本番環境対応 最終更新日:2025-11-06 依存関係:なし(WordPress 5.9+、PHP 7.4+) 最新バージョン:WordPress 6.7+、PHP 8.0+ を推奨
クイックスタート(10 分)
1. プラグインの構成を選ぶ
WordPress プラグインでは、次の 3 つのアーキテクチャパターンを利用できます。
- シンプル(関数のみ) - 関数が 5 個未満の小規模なプラグイン向け
- OOP(オブジェクト指向) - 関連する機能を持つ中規模プラグイン向け
- PSR-4(名前空間 + Composer オートロード) - 大規模または最新のプラグイン向け
重要な理由:
- シンプルなプラグインは最も手軽に始められますが、拡張性に欠けます
- OOP は最新の PHP 機能を使わずにコードを整理できます
- PSR-4 は 2025 年時点での最新標準であり、最も保守しやすい方法です
2. プラグインヘッダーを作成する
すべてのプラグインは、メインファイルにヘッダーコメントを記述する必要があります。
<?php
/**
* Plugin Name: My Awesome Plugin
* Plugin URI: https://example.com/my-plugin/
* Description: Brief description of what this plugin does.
* Version: 1.0.0
* Requires at least: 5.9
* Requires PHP: 7.4
* Author: Your Name
* Author URI: https://yoursite.com/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: my-plugin
* Domain Path: /languages
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
重要:
- 必須項目は Plugin Name のみです
- Text Domain は翻訳のため、プラグインのスラッグと完全に一致させる必要があります
- ファイルへの直接アクセスを防ぐため、必ず ABSPATH チェックを追加してください
3. セキュリティの基盤を実装する
機能を実装する前に、次の 5 つのセキュリティの基本を実装してください。
// 1. Unique Prefix (4-5 chars minimum)
define( 'MYPL_VERSION', '1.0.0' );
function mypl_init() {
// Your code
}
add_action( 'init', 'mypl_init' );
// 2. ABSPATH Check (every PHP file)
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// 3. Nonces for Forms
<input type="hidden" name="mypl_nonce" value="<?php echo wp_create_nonce( 'mypl_action' ); ?>" />
// 4. Sanitize Input, Escape Output
$clean = sanitize_text_field( $_POST['input'] );
echo esc_html( $output );
// 5. Prepared Statements for Database
global $wpdb;
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
$id
)
);
5 つのセキュリティの基本
WordPress プラグインのセキュリティには、必ずすべて備えるべき 3 つの要素があります。
ステップ 1:すべてに一意のプレフィックスを使う
理由:ほかのプラグインや WordPress コアとの名前の衝突を防ぎます。
ルール:
- 最低 4~5 文字にします
- 関数、クラス、定数、オプション、一時データ、メタキー、グローバル変数に適用します
- 次のものは避けてください:
wp_、__、_、"WordPress"
// GOOD
function mypl_function_name() {}
class MyPL_Class_Name {}
define( 'MYPL_CONSTANT', 'value' );
add_option( 'mypl_option', 'value' );
set_transient( 'mypl_cache', $data, HOUR_IN_SECONDS );
// BAD
function function_name() {} // No prefix, will conflict
class Settings {} // Too generic
ステップ 2:管理者かどうかだけでなく、権限を確認する
誤り:権限チェックに is_admin() を使うこと
// WRONG - Anyone can access admin area URLs
if ( is_admin() ) {
// Delete user data - SECURITY HOLE
}
// CORRECT - Check user capability
if ( current_user_can( 'manage_options' ) ) {
// Delete user data - Now secure
}
一般的な権限:
manage_options- 管理者edit_posts- 編集者/投稿者publish_posts- 投稿者edit_pages- 編集者read- 購読者
ステップ 3:セキュリティの三位一体
入力 → 処理 → 出力には、それぞれ異なる関数が必要です。
// SANITIZATION (Input) - Clean user data
$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
$url = esc_url_raw( $_POST['url'] );
$html = wp_kses_post( $_POST['content'] ); // Allow safe HTML
$key = sanitize_key( $_POST['option'] );
$ids = array_map( 'absint', $_POST['ids'] ); // Array of integers
// VALIDATION (Logic) - Verify it meets requirements
if ( ! is_email( $email ) ) {
wp_die( 'Invalid email' );
}
// ESCAPING (Output) - Make safe for display
echo esc_html( $name );
echo '<a href="' . esc_url( $url ) . '">';
echo '<div class="' . esc_attr( $class ) . '">';
echo '<textarea>' . esc_textarea( $content ) . '</textarea>';
重要なルール:入力時にサニタイズし、出力時にエスケープしてください。ユーザーデータを決して信用しないでください。
ステップ 4:ノンス(CSRF 対策)
概要:リクエストが自サイトから送信されたことを証明する使い捨てトークンです。
フォームのパターン:
// Generate nonce in form
<form method="post">
<?php wp_nonce_field( 'mypl_action', 'mypl_nonce' ); ?>
<input type="text" name="data" />
<button type="submit">Submit</button>
</form>
// Verify nonce in handler
if ( ! isset( $_POST['mypl_nonce'] ) || ! wp_verify_nonce( $_POST['mypl_nonce'], 'mypl_action' ) ) {
wp_die( 'Security check failed' );
}
// Now safe to proceed
$data = sanitize_text_field( $_POST['data'] );
AJAX のパターン:
// JavaScript
jQuery.ajax({
url: ajaxurl,
data: {
action: 'mypl_ajax_action',
nonce: mypl_ajax_object.nonce,
data: formData
}
});
// PHP Handler
function mypl_ajax_handler() {
check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );
// Safe to proceed
wp_send_json_success( array( 'message' => 'Success' ) );
}
add_action( 'wp_ajax_mypl_ajax_action', 'mypl_ajax_handler' );
// Localize script with nonce
wp_localize_script( 'mypl-script', 'mypl_ajax_object', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mypl-ajax-nonce' ),
) );
ステップ 5:データベースにはプリペアドステートメントを使う
重要:ユーザー入力を含むクエリでは、必ず $wpdb->prepare() を使ってください。
global $wpdb;
// WRONG - SQL Injection vulnerability
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}table WHERE id = {$_GET['id']}" );
// CORRECT - Prepared statement
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
$_GET['id']
)
);
プレースホルダー:
%s- 文字列%d- 整数%f- 浮動小数点数
LIKE クエリ(特殊なケース):
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}posts WHERE post_title LIKE %s",
$search
)
);
重要なルール
必ず行うこと
✅ 一意のプレフィックスを使う(4~5 文字)。すべてのグローバルコード(関数、クラス、オプション、一時データ)に適用します
✅ 各 PHP ファイルに ABSPATH チェックを追加する:if ( ! defined( 'ABSPATH' ) ) exit;
✅ is_admin() だけでなく、権限を確認する(current_user_can())
✅ すべてのフォームと AJAX リクエストでノンスを検証する
✅ ユーザー入力を含むすべてのデータベースクエリで $wpdb->prepare() を使う
✅ 保存する前に sanitize_*() 関数で入力をサニタイズする
✅ 表示する前に esc_*() 関数で出力をエスケープする
✅ カスタム投稿タイプを登録する際は、有効化時にリライトルールをフラッシュする
✅ 完全なクリーンアップにはアンインストールフックではなく uninstall.php を使う
✅ WordPress コーディング規約に従う(インデントにはタブを使い、ヨーダ記法を使用する)
決して行わないこと
❌ extract() は決して使わない - セキュリティ上の脆弱性を引き起こします
❌ サニタイズせずに $_POST/$_GET を信用しない
❌ ユーザー入力を SQL に連結しない - 必ず prepare() を使う
❌ 権限チェックに is_admin() だけを使わない
❌ サニタイズされていないデータを出力しない - 必ずエスケープする
❌ 一般的な関数名やクラス名を使わない - 必ずプレフィックスを付ける
❌ 短縮 PHP タグ <? や <?= を使わない - <?php のみを使う
❌ 無効化時にユーザーデータを削除しない - 削除はアンインストール時のみ行う
❌ アンインストールフックを繰り返し登録しない - 有効化時に一度だけ登録する
❌ メインの処理フローで register_uninstall_hook() を使わない - 代わりに uninstall.php を使う
既知の問題の防止
このスキルにより、記録されている 20 件の問題を防止できます。
Issue #1:SQL インジェクション
エラー:エスケープされていないユーザー入力によってデータベースが侵害されます
出典:https://patchstack.com/articles/sql-injection/ (すべての脆弱性の15%)
発生する理由:ユーザー入力を SQL クエリに直接連結しています
防止策:プレースホルダーを使用して、必ず $wpdb->prepare() を使用してください
// VULNERABLE
$wpdb->query( "DELETE FROM {$wpdb->prefix}table WHERE id = {$_GET['id']}" );
// SECURE
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}table WHERE id = %d", $_GET['id'] ) );
Issue #2:XSS(クロスサイトスクリプティング)
エラー:悪意のある JavaScript がユーザーのブラウザーで実行されます 出典:https://patchstack.com (すべての脆弱性の35%) 発生する理由:サニタイズされていないユーザーデータを HTML に出力しています 防止策:コンテキストに適した関数を使って、必ず出力をエスケープしてください
// VULNERABLE
echo $_POST['name'];
echo '<div class="' . $_POST['class'] . '">';
// SECURE
echo esc_html( $_POST['name'] );
echo '<div class="' . esc_attr( $_POST['class'] ) . '">';
Issue #3:CSRF(クロスサイトリクエストフォージェリ)
エラー:ユーザーに代わって不正な操作が実行されます
出典:https://blog.nintechnet.com/25-wordpress-plugins-vulnerable-to-csrf-attacks/
発生する理由:リクエストが自サイトから送信されたことを検証していません
防止策:wp_nonce_field() と wp_verify_nonce() を使ってノンスを使用してください
// VULNERABLE
if ( $_POST['action'] == 'delete' ) {
delete_user( $_POST['user_id'] );
}
// SECURE
if ( ! wp_verify_nonce( $_POST['nonce'], 'mypl_delete_user' ) ) {
wp_die( 'Security check failed' );
}
delete_user( absint( $_POST['user_id'] ) );
Issue #4:権限チェックの欠落
エラー:一般ユーザーが管理者機能にアクセスできます
出典:WordPress セキュリティレビューガイドライン
発生する理由:current_user_can() の代わりに is_admin() を使用しています
防止策:管理者コンテキストだけでなく、必ず権限を確認してください
// VULNERABLE
if ( is_admin() ) {
// Any logged-in user can trigger this
}
// SECURE
if ( current_user_can( 'manage_options' ) ) {
// Only administrators can trigger this
}
Issue #5:ファイルへの直接アクセス
エラー:PHP ファイルが WordPress のコンテキスト外で実行されます 出典:WordPress プラグインハンドブック 発生する理由:ファイルの先頭に ABSPATH チェックがありません 防止策:すべての PHP ファイルに ABSPATH チェックを追加してください
// Add to top of EVERY PHP file
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
Issue #6:プレフィックスの衝突
エラー:関数やクラスが他のプラグインと衝突します 出典:WordPress コーディング規約 発生する理由:一意のプレフィックスを付けずに汎用的な名前を使用しています 防止策:グローバルコードすべてに4~5文字のプレフィックスを使用してください
// CAUSES CONFLICTS
function init() {}
class Settings {}
add_option( 'api_key', $value );
// SAFE
function mypl_init() {}
class MyPL_Settings {}
add_option( 'mypl_api_key', $value );
Issue #7:リライトルールがフラッシュされない
エラー:カスタム投稿タイプで404エラーが返されます 出典:WordPress プラグインハンドブック 発生する理由:CPT の登録後にリライトルールをフラッシュし忘れています 防止策:有効化時にフラッシュし、無効化時にクリアしてください
function mypl_activate() {
mypl_register_cpt();
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'mypl_activate' );
function mypl_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );
Issue #8:トランジェントがクリーンアップされない
エラー:期限切れのトランジェントがデータベースに蓄積します 出典:WordPress Transients API ドキュメント 発生する理由:アンインストール時にクリーンアップしていません 防止策:uninstall.php でトランジェントを削除してください
// uninstall.php
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
global $wpdb;
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mypl_%'" );
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mypl_%'" );
Issue #9:スクリプトがあらゆる場所で読み込まれる
エラー:不要なアセットの読み込みによってパフォーマンスが低下します 出典:WordPress パフォーマンスのベストプラクティス 発生する理由:条件を確認せずにスクリプトやスタイルをエンキューしています 防止策:必要な場所でのみアセットを読み込んでください
// BAD - Loads on every page
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script( 'mypl-script', $url );
} );
// GOOD - Only loads on specific page
add_action( 'wp_enqueue_scripts', function() {
if ( is_page( 'my-page' ) ) {
wp_enqueue_script( 'mypl-script', $url, array( 'jquery' ), '1.0', true );
}
} );
Issue #10:保存時のサニタイズ漏れ
エラー:悪意のあるデータがデータベースに保存されます 出典:WordPress データ検証 発生する理由:サニタイズせずに $_POST データを保存しています 防止策:保存前に必ずサニタイズしてください
// VULNERABLE
update_option( 'mypl_setting', $_POST['value'] );
// SECURE
update_option( 'mypl_setting', sanitize_text_field( $_POST['value'] ) );
Issue #11:LIKE クエリの誤り
エラー:SQL 構文エラーまたはインジェクションの脆弱性が発生します
出典:WordPress $wpdb ドキュメント
発生する理由:LIKE のワイルドカードが適切にエスケープされていません
防止策:$wpdb->esc_like() を使用してください
// WRONG
$search = '%' . $term . '%';
// CORRECT
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results( $wpdb->prepare( "... WHERE title LIKE %s", $search ) );
Issue #12:extract() の使用
エラー:変数の衝突やセキュリティ上の脆弱性が発生します 出典:WordPress コーディング規約 発生する理由:extract() は配列のキーから変数を作成します 防止策:extract() は使用せず、配列要素に直接アクセスしてください
// DANGEROUS
extract( $_POST );
// Now $any_array_key becomes a variable
// SAFE
$name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : '';
Issue #13:REST API の権限コールバックの欠落
エラー:誰でもエンドポイントにアクセスできます
出典:WordPress REST API ハンドブック
発生する理由:permission_callback が指定されていません
防止策:必ず permission_callback を追加してください
// VULNERABLE
register_rest_route( 'myplugin/v1', '/data', array(
'callback' => 'my_callback',
) );
// SECURE
register_rest_route( 'myplugin/v1', '/data', array(
'callback' => 'my_callback',
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
Issue #14:アンインストールフックが繰り返し登録される
エラー:ページが読み込まれるたびにオプションが書き込まれます 出典:WordPress プラグインハンドブック 発生する理由:メインの処理フロー内で register_uninstall_hook() を呼び出しています 防止策:代わりに uninstall.php ファイルを使用してください
// BAD - Runs on every page load
register_uninstall_hook( __FILE__, 'mypl_uninstall' );
// GOOD - Use uninstall.php file (preferred method)
// Create uninstall.php in plugin root
Issue #15: 無効化時にデータが削除される
エラー:プラグインを一時的に無効化すると、ユーザーのデータが失われます 出典:WordPress プラグイン開発のベストプラクティス 原因:無効化とアンインストールの混同 防止策:データを削除するのは uninstall.php 内だけにし、無効化時には削除しないでください
// WRONG - Deletes user data on deactivation
register_deactivation_hook( __FILE__, function() {
delete_option( 'mypl_user_settings' );
} );
// CORRECT - Only clear temporary data on deactivation
register_deactivation_hook( __FILE__, function() {
delete_transient( 'mypl_cache' );
} );
// CORRECT - Delete all data in uninstall.php
Issue #16: 非推奨関数の使用
エラー:WordPress の更新時にプラグインが動作しなくなります 出典:WordPress の非推奨関数一覧 原因:新しいバージョンの WordPress で削除された関数を使用しています 防止策:開発中は WP_DEBUG を有効にしてください
// In wp-config.php (development only)
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
Issue #17: テキストドメインの不一致
エラー:翻訳が読み込まれません 出典:WordPress の国際化 原因:テキストドメインがプラグインのスラッグと一致していません 防止策:すべての箇所で正確なプラグインのスラッグを使用してください
// Plugin header
// Text Domain: my-plugin
// In code - MUST MATCH EXACTLY
__( 'Text', 'my-plugin' );
_e( 'Text', 'my-plugin' );
Issue #18: プラグイン依存関係の不足
エラー:必須プラグインが無効な場合に致命的なエラーが発生します 出典:WordPress プラグインの依存関係 原因:必須プラグインの確認がありません 防止策:plugins_loaded で依存関係を確認してください
add_action( 'plugins_loaded', function() {
if ( ! class_exists( 'WooCommerce' ) ) {
add_action( 'admin_notices', function() {
echo '<div class="error"><p>My Plugin requires WooCommerce.</p></div>';
} );
return;
}
// Initialize plugin
} );
Issue #19: 自動保存によるメタデータの保存
エラー:メタデータが複数回保存され、パフォーマンスの問題が発生します 出典:WordPress の投稿メタデータ 原因:save_post フックで自動保存を確認していません 防止策:DOING_AUTOSAVE 定数を確認してください
add_action( 'save_post', function( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Safe to save meta
} );
Issue #20: admin-ajax.php のパフォーマンス
エラー:AJAX の応答が遅くなります 出典:https://deliciousbrains.com/comparing-wordpress-rest-api-performance-admin-ajax-php/ 原因:admin-ajax.php は WordPress コア全体を読み込みます 防止策:新しいプロジェクトでは REST API を使用してください(10 倍高速です)
// OLD: admin-ajax.php (still works but slower)
add_action( 'wp_ajax_mypl_action', 'mypl_ajax_handler' );
// NEW: REST API (10x faster, recommended)
add_action( 'rest_api_init', function() {
register_rest_route( 'myplugin/v1', '/endpoint', array(
'methods' => 'POST',
'callback' => 'mypl_rest_handler',
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
} );
プラグインのアーキテクチャパターン
パターン 1:シンプルなプラグイン(関数のみ)
使用する場合:関数が 5 個未満で、複雑な状態を持たない小規模なプラグイン
<?php
/**
* Plugin Name: Simple Plugin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
function mypl_init() {
// Your code here
}
add_action( 'init', 'mypl_init' );
function mypl_admin_menu() {
add_options_page(
'My Plugin',
'My Plugin',
'manage_options',
'my-plugin',
'mypl_settings_page'
);
}
add_action( 'admin_menu', 'mypl_admin_menu' );
function mypl_settings_page() {
?>
<div class="wrap">
<h1>My Plugin Settings</h1>
</div>
<?php
}
パターン 2:OOP プラグイン
使用する場合:関連する機能を持ち、整理が必要な中規模のプラグイン
<?php
/**
* Plugin Name: OOP Plugin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class MyPL_Plugin {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->define_constants();
$this->init_hooks();
}
private function define_constants() {
define( 'MYPL_VERSION', '1.0.0' );
define( 'MYPL_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MYPL_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
}
private function init_hooks() {
add_action( 'init', array( $this, 'init' ) );
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
}
public function init() {
// Initialization code
}
public function admin_menu() {
add_options_page(
'My Plugin',
'My Plugin',
'manage_options',
'my-plugin',
array( $this, 'settings_page' )
);
}
public function settings_page() {
?>
<div class="wrap">
<h1>My Plugin Settings</h1>
</div>
<?php
}
}
// Initialize plugin
function mypl() {
return MyPL_Plugin::get_instance();
}
mypl();
パターン 3:PSR-4 プラグイン(モダン、推奨)
使用する場合:大規模またはモダンなプラグイン、チーム開発、2025 年以降のベストプラクティス
ディレクトリ構成:
my-plugin/
├── my-plugin.php # Main file
├── composer.json # Autoloading config
├── src/ # PSR-4 autoloaded classes
│ ├── Admin.php
│ ├── Frontend.php
│ └── Settings.php
├── languages/
└── uninstall.php
composer.json:
{
"name": "my-vendor/my-plugin",
"autoload": {
"psr-4": {
"MyPlugin\\": "src/"
}
},
"require": {
"php": ">=7.4"
}
}
my-plugin.php:
<?php
/**
* Plugin Name: PSR-4 Plugin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
use MyPlugin\Admin;
use MyPlugin\Frontend;
class MyPlugin {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init();
}
private function init() {
new Admin();
new Frontend();
}
}
MyPlugin::get_instance();
src/Admin.php:
<?php
namespace MyPlugin;
class Admin {
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_menu' ) );
}
public function add_menu() {
add_options_page(
'My Plugin',
'My Plugin',
'manage_options',
'my-plugin',
array( $this, 'settings_page' )
);
}
public function settings_page() {
?>
<div class="wrap">
<h1>My Plugin Settings</h1>
</div>
<?php
}
}
よく使われるパターン
パターン 1:カスタム投稿タイプ
function mypl_register_cpt() {
register_post_type( 'book', array(
'labels' => array(
'name' => 'Books',
'singular_name' => 'Book',
'add_new_item' => 'Add New Book',
),
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // Gutenberg support
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'books' ),
'menu_icon' => 'dashicons-book',
) );
}
add_action( 'init', 'mypl_register_cpt' );
// CRITICAL: Flush rewrite rules on activation
function mypl_activate() {
mypl_register_cpt();
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'mypl_activate' );
function mypl_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );
パターン 2:カスタムタクソノミー
function mypl_register_taxonomy() {
register_taxonomy( 'genre', 'book', array(
'labels' => array(
'name' => 'Genres',
'singular_name' => 'Genre',
),
'hierarchical' => true, // Like categories
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'genre' ),
) );
}
add_action( 'init', 'mypl_register_taxonomy' );
パターン 3:メタボックス
function mypl_add_meta_box() {
add_meta_box(
'book_details',
'Book Details',
'mypl_meta_box_html',
'book',
'normal',
'high'
);
}
add_action( 'add_meta_boxes', 'mypl_add_meta_box' );
function mypl_meta_box_html( $post ) {
$isbn = get_post_meta( $post->ID, '_book_isbn', true );
wp_nonce_field( 'mypl_save_meta', 'mypl_meta_nonce' );
?>
<label for="book_isbn">ISBN:</label>
<input type="text" id="book_isbn" name="book_isbn" value="<?php echo esc_attr( $isbn ); ?>" />
<?php
}
function mypl_save_meta( $post_id ) {
// Security checks
if ( ! isset( $_POST['mypl_meta_nonce'] )
|| ! wp_verify_nonce( $_POST['mypl_meta_nonce'], 'mypl_save_meta' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Save data
if ( isset( $_POST['book_isbn'] ) ) {
update_post_meta(
$post_id,
'_book_isbn',
sanitize_text_field( $_POST['book_isbn'] )
);
}
}
add_action( 'save_post_book', 'mypl_save_meta' );
パターン 4:Settings API
function mypl_add_menu() {
add_options_page(
'My Plugin Settings',
'My Plugin',
'manage_options',
'my-plugin',
'mypl_settings_page'
);
}
add_action( 'admin_menu', 'mypl_add_menu' );
function mypl_register_settings() {
register_setting( 'mypl_options', 'mypl_api_key', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
) );
add_settings_section(
'mypl_section',
'API Settings',
'mypl_section_callback',
'my-plugin'
);
add_settings_field(
'mypl_api_key',
'API Key',
'mypl_field_callback',
'my-plugin',
'mypl_section'
);
}
add_action( 'admin_init', 'mypl_register_settings' );
function mypl_section_callback() {
echo '<p>Configure your API settings.</p>';
}
function mypl_field_callback() {
$value = get_option( 'mypl_api_key' );
?>
<input type="text" name="mypl_api_key" value="<?php echo esc_attr( $value ); ?>" />
<?php
}
function mypl_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form method="post" action="options.php">
<?php
settings_fields( 'mypl_options' );
do_settings_sections( 'my-plugin' );
submit_button();
?>
</form>
</div>
<?php
}
パターン 5:REST API エンドポイント
add_action( 'rest_api_init', function() {
register_rest_route( 'myplugin/v1', '/data', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'mypl_rest_callback',
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
'args' => array(
'id' => array(
'required' => true,
'validate_callback' => function( $param ) {
return is_numeric( $param );
},
'sanitize_callback' => 'absint',
),
),
) );
} );
function mypl_rest_callback( $request ) {
$id = $request->get_param( 'id' );
// Process...
return new WP_REST_Response( array(
'success' => true,
'data' => $data,
), 200 );
}
パターン 6:AJAX ハンドラー(レガシー)
// Enqueue script with localized data
function mypl_enqueue_ajax_script() {
wp_enqueue_script( 'mypl-ajax', plugins_url( 'js/ajax.js', __FILE__ ), array( 'jquery' ), '1.0', true );
wp_localize_script( 'mypl-ajax', 'mypl_ajax_object', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mypl-ajax-nonce' ),
) );
}
add_action( 'wp_enqueue_scripts', 'mypl_enqueue_ajax_script' );
// AJAX handler (logged-in users)
function mypl_ajax_handler() {
check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );
$data = sanitize_text_field( $_POST['data'] );
// Process...
wp_send_json_success( array( 'message' => 'Success' ) );
}
add_action( 'wp_ajax_mypl_action', 'mypl_ajax_handler' );
// AJAX handler (logged-out users)
add_action( 'wp_ajax_nopriv_mypl_action', 'mypl_ajax_handler' );
JavaScript(js/ajax.js):
jQuery(document).ready(function($) {
$('#my-button').on('click', function() {
$.ajax({
url: mypl_ajax_object.ajaxurl,
type: 'POST',
data: {
action: 'mypl_action',
nonce: mypl_ajax_object.nonce,
data: 'value'
},
success: function(response) {
console.log(response.data.message);
}
});
});
});
パターン 7:カスタムデータベーステーブル
function mypl_create_tables() {
global $wpdb;
$table_name = $wpdb->prefix . 'mypl_data';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
data text NOT NULL,
created datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (id),
KEY user_id (user_id)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
add_option( 'mypl_db_version', '1.0' );
}
// Create tables on activation
register_activation_hook( __FILE__, 'mypl_create_tables' );
パターン 8:キャッシュ用の Transients
function mypl_get_expensive_data() {
// Try to get cached data
$data = get_transient( 'mypl_expensive_data' );
if ( false === $data ) {
// Not cached - regenerate
$data = perform_expensive_operation();
// Cache for 12 hours
set_transient( 'mypl_expensive_data', $data, 12 * HOUR_IN_SECONDS );
}
return $data;
}
// Clear cache when data changes
function mypl_clear_cache() {
delete_transient( 'mypl_expensive_data' );
}
add_action( 'save_post', 'mypl_clear_cache' );
同梱リソースの使用
テンプレート(templates/)
これらの本番環境対応テンプレートを使って、プラグインをすばやく作成できます。
templates/plugin-simple/- 関数を使ったシンプルなプラグインtemplates/plugin-oop/- オブジェクト指向のプラグイン構成templates/plugin-psr4/- Composer を使ったモダンな PSR-4 プラグインtemplates/examples/meta-box.php- メタボックスの実装templates/examples/settings-page.php- Settings API のページtemplates/examples/custom-post-type.php- CPT の登録templates/examples/rest-endpoint.php- REST API エンドポイントtemplates/examples/ajax-handler.php- AJAX の実装
Claude がこれらを使用する場合:新しいプラグインを作成するとき、または特定の機能パターンを実装するとき。
スクリプト(scripts/)
scripts/scaffold-plugin.sh- 対話形式のプラグインひな形作成scripts/check-security.sh- よくある問題のセキュリティ監査scripts/validate-headers.sh- プラグインヘッダーの検証
使用例:
# Scaffold new plugin
./scripts/scaffold-plugin.sh my-plugin simple
# Check for security issues
./scripts/check-security.sh my-plugin.php
# Validate plugin headers
./scripts/validate-headers.sh my-plugin.php
参考資料(references/)
Claude が必要に応じて読み込める詳細なドキュメントです。
references/security-checklist.md- セキュリティ監査の完全なチェックリストreferences/hooks-reference.md- WordPress の一般的なフックとフィルターreferences/sanitization-guide.md- すべてのサニタイズ/エスケープ関数references/wpdb-patterns.md- データベースクエリのパターンreferences/common-errors.md- エラー防止に関する詳細ガイド
Claude がこれらを読み込むべき場面:セキュリティの問題への対応、適切なフックの選択、特定のデータ型のサニタイズ、データベースクエリの作成、よくあるエラーのデバッグを行う場合です。
高度なトピック
国際化(i18n)
// Load text domain
function mypl_load_textdomain() {
load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'plugins_loaded', 'mypl_load_textdomain' );
// Translatable strings
__( 'Text', 'my-plugin' ); // Returns translated string
_e( 'Text', 'my-plugin' ); // Echoes translated string
_n( 'One item', '%d items', $count, 'my-plugin' ); // Plural forms
esc_html__( 'Text', 'my-plugin' ); // Translate and escape
esc_html_e( 'Text', 'my-plugin' ); // Translate, escape, and echo
WP-CLI コマンド
if ( defined( 'WP_CLI' ) && WP_CLI ) {
class MyPL_CLI_Command {
/**
* Process data
*
* ## EXAMPLES
*
* wp mypl process --limit=100
*
* @param array $args
* @param array $assoc_args
*/
public function process( $args, $assoc_args ) {
$limit = isset( $assoc_args['limit'] ) ? absint( $assoc_args['limit'] ) : 10;
WP_CLI::line( "Processing $limit items..." );
// Process...
WP_CLI::success( 'Processing complete!' );
}
}
WP_CLI::add_command( 'mypl', 'MyPL_CLI_Command' );
}
スケジュールイベント(Cron)
// Schedule event on activation
function mypl_activate() {
if ( ! wp_next_scheduled( 'mypl_daily_task' ) ) {
wp_schedule_event( time(), 'daily', 'mypl_daily_task' );
}
}
register_activation_hook( __FILE__, 'mypl_activate' );
// Clear event on deactivation
function mypl_deactivate() {
wp_clear_scheduled_hook( 'mypl_daily_task' );
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );
// Hook to scheduled event
function mypl_do_daily_task() {
// Perform task
}
add_action( 'mypl_daily_task', 'mypl_do_daily_task' );
プラグインの依存関係チェック
add_action( 'admin_init', function() {
// Check for WooCommerce
if ( ! class_exists( 'WooCommerce' ) ) {
deactivate_plugins( plugin_basename( __FILE__ ) );
add_action( 'admin_notices', function() {
echo '<div class="error"><p><strong>My Plugin</strong> requires WooCommerce to be installed and active.</p></div>';
} );
if ( isset( $_GET['activate'] ) ) {
unset( $_GET['activate'] );
}
}
} );
配布と自動更新
GitHub の自動更新を有効にする
WordPress.org 以外でホストされているプラグインでも、YahnisElsts の Plugin Update Checker を使用すれば自動更新を提供できます。ほとんどのユースケースで推奨されるソリューションです。
クイックスタート:
// 1. Install library (git submodule or Composer)
git submodule add https://github.com/YahnisElsts/plugin-update-checker.git
// 2. Add to main plugin file
require plugin_dir_path( __FILE__ ) . 'plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;
$updateChecker = PucFactory::buildUpdateChecker(
'https://github.com/yourusername/your-plugin/',
__FILE__,
'your-plugin-slug'
);
// Use GitHub Releases (recommended)
$updateChecker->getVcsApi()->enableReleaseAssets();
// For private repos, use token from wp-config.php
if ( defined( 'YOUR_PLUGIN_GITHUB_TOKEN' ) ) {
$updateChecker->setAuthentication( YOUR_PLUGIN_GITHUB_TOKEN );
}
デプロイ:
# 1. Update version in plugin header
# 2. Commit and tag
git add my-plugin.php
git commit -m "Bump version to 1.0.1"
git tag 1.0.1
git push origin main
git push origin 1.0.1
# 3. Create GitHub Release (optional but recommended)
# - Upload pre-built ZIP file (exclude .git, tests, etc.)
# - Add release notes for users
主な機能:
✅ GitHub、GitLab、BitBucket、またはカスタムサーバーで動作します ✅ パブリックリポジトリとプライベートリポジトリに対応しています ✅ バージョン管理に GitHub Releases またはタグを使用します ✅ HTTPS ベースの安全な更新を利用できます ✅ オプションでライセンスキーと連携できます ✅ プロフェッショナルなリリースノートと変更履歴を利用できます ✅ ライブラリのサイズは約 100KB です
代替ソリューション:
- Git Updater(ユーザーがインストールできるプラグイン。コーディングは不要です)
- カスタム更新サーバー(完全な制御が可能ですが、ホスティングが必要です)
- Freemius(商用サービス。ライセンス管理と支払い機能を含みます)
詳細なリソース:
- 完全ガイド:
references/github-auto-updates.mdを参照してください(全 21 ページ、すべての方法を網羅) - 実装例:
examples/github-updater.phpを参照してください(10 個の例) - セキュリティのベストプラクティス:チェックサム、署名、トークンの保存、レート制限
- テンプレートとの連携:3 種類すべてのプラグインテンプレートにセットアップ手順が含まれています
セキュリティに関する考慮事項:
- ✅ リポジトリの URL には必ず HTTPS を使用してください
- ✅ 認証トークンをハードコードしないでください(wp-config.php を使用してください)
- ✅ 更新を提供する前にライセンスを検証してください
- ✅ オプション:ファイル検証用のチェックサムを追加してください
- ✅ API のレート制限を超えないよう、更新チェックをレート制限してください
- ✅ インストール後にキャッシュされた更新データを消去してください
各方法の用途:
| 用途 | 推奨ソリューション |
|---|---|
| オープンソース、パブリックリポジトリ | Plugin Update Checker |
| プライベートプラグイン、クライアント案件 | Plugin Update Checker + プライベートリポジトリ |
| 商用プラグイン | Freemius またはカスタムサーバー |
| 複数のプラットフォームでの Git ホスティング | Git Updater |
| カスタムライセンス要件 | カスタム更新サーバー |
ZIP 構造の要件:
plugin.zip
└── my-plugin/ ← Plugin folder MUST be inside ZIP
├── my-plugin.php
├── readme.txt
└── ...
構造が正しくない場合、WordPress がランダムなフォルダー名を作成し、プラグインが動作しなくなります!
依存関係
必須:
- WordPress 5.9+(6.7+ を推奨)
- PHP 7.4+(8.0+ を推奨)
任意:
- Composer 2.0+ - PSR-4 オートローディング用
- WP-CLI 2.0+ - コマンドラインでのプラグイン管理用
- Query Monitor - デバッグとパフォーマンス分析用
公式ドキュメント
- WordPress プラグインハンドブック:https://developer.wordpress.org/plugins/
- WordPress コーディング標準:https://developer.wordpress.org/coding-standards/
- WordPress REST API:https://developer.wordpress.org/rest-api/
- WordPress データベースクラス($wpdb):https://developer.wordpress.org/reference/classes/wpdb/
- WordPress セキュリティ:https://developer.wordpress.org/apis/security/
- Settings API:https://developer.wordpress.org/plugins/settings/settings-api/
- カスタム投稿タイプ:https://developer.wordpress.org/plugins/post-types/
- Transients API:https://developer.wordpress.org/apis/transients/
- Context7 ライブラリ ID:/websites/developer_wordpress
トラブルシューティング
問題:プラグインが致命的なエラーを引き起こす
解決方法:
- wp-config.php で WP_DEBUG を有効にします
- wp-content/debug.log のエラーログを確認します
- すべてのクラス名/関数名にプレフィックスが付いていることを確認します
- 依存関係の不足がないか確認します
問題:カスタム投稿タイプのページで 404 エラーが発生する
解決方法:リライトルールを再生成します
// Temporarily add to wp-admin
flush_rewrite_rules();
// Remove after visiting wp-admin once
問題:Nonce の検証が常に失敗する
解決方法:
- フィールドと検証処理で Nonce 名が一致していることを確認します
- 正しいアクション名を使って検証します
- Nonce の有効期限が切れていないことを確認します(デフォルトは 24 時間です)
問題:AJAX が 0 または -1 を返す
解決方法:
- アクション名がフック
wp_ajax_{action}と一致していることを確認します - Nonce が送信され、検証されていることを確認します
- ハンドラー関数が存在し、正しくフックされていることを確認します
問題:サニタイズによって HTML が削除される
解決方法:安全な HTML を許可するには、sanitize_text_field() の代わりに wp_kses_post() を使用します
問題:データベースクエリが機能しない
解決方法:
- 変数を含むクエリには、必ず
$wpdb->prepare()を使用します - テーブル名に
$wpdb->prefixが含まれていることを確認します - カラム名と構文を確認します
セットアップ完全チェックリスト
このチェックリストを使って、プラグインを確認してください。
- すべてのフィールドを含むプラグインヘッダーが完成している
- すべての PHP ファイルの先頭に ABSPATH チェックがある
- すべての関数/クラスで一意のプレフィックスを使用している
- すべてのフォームで Nonce 検証を行っている
- すべてのユーザー入力をサニタイズしている
- すべての出力をエスケープしている
- すべてのデータベースクエリで $wpdb->prepare() を使用している
- 権限チェックを行っている(is_admin() だけに頼っていない)
- カスタム投稿タイプの有効化時にリライトルールを再生成している
- 停止フックでは一時データのみを消去している
- uninstall.php で永続データをクリーンアップしている
- テキストドメインがプラグインのスラッグと一致している
- スクリプト/スタイルを必要な場所でのみ読み込んでいる
- 開発中は WP_DEBUG を有効にしている
- Query Monitor でパフォーマンスをテストしている
- 非推奨関数の警告がない
- 最新の WordPress バージョンで動作する
質問や問題がある場合
- 詳細なトラブルシューティングについては
references/common-errors.mdを確認します - セキュリティの基礎に関するすべての手順を確認します
- 公式ドキュメントを確認します:https://developer.wordpress.org/plugins/
- WP_DEBUG を有効にして debug.log を確認します
- Query Monitor プラグインを使ってフックとクエリをデバッグします