mcpField
W
woocommerce-plugin-development

woocommerce-plugin-development

github.com/finsilabs開発6094 回インストール5 日前に更新
リポジトリ

woocommerce-plugin-development について

WooCommerce プラグイン開発者向けのスキルです。ストア機能、API、注文ストレージを対象に、拡張設計とアップグレードを考慮した実装方法を扱います。

主な機能

  1. 01プラグインの設計と基本構成
  2. 02注文・商品ライフサイクルフック
  3. 03WooCommerce 設定ページ
  4. 04REST API ルートの拡張
  5. 05HPOS 対応の注文データアクセス

活用シーン

  1. 01カスタム配送・決済・割引ロジックの追加
  2. 02管理設定・構成ページの作成
  3. 03チェックアウト・注文・商品動作の拡張
  4. 04HPOS 対応に向けたプラグイン移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill woocommerce-plugin-development

インストール後、Claude Code で/woocommerce-plugin-developmentを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/custom-shipping-method-and-rest-api-endp/
criteria.json
task.md
evals/plugin-boilerplate-hpos-compatibility-or/
criteria.json
task.md
evals/woocommerce-settings-api-and-product-cus/
criteria.json
task.md

詳細

WooCommerce プラグイン開発

概要

WordPress フック(アクションとフィルター)、WooCommerce Settings API、カスタム投稿タイプとメタボックス、REST API の拡張、HPOS(High-Performance Order Storage)への対応を使用して、ストアの機能を拡張するカスタム WooCommerce プラグインを構築します。このスキルでは、プラグインのアーキテクチャ、注文と商品の WooCommerce ライフサイクルフック、保守しやすくアップグレードに安全な拡張機能を構築するためのパターンを扱います。

このスキルを使用する場面

  • WooCommerce を拡張するカスタム機能(カスタム配送、決済、割引ロジックなど)を構築するとき
  • 管理設定や設定ページを追加するプラグインを作成するとき
  • WooCommerce のチェックアウト、注文、商品ライフサイクルにフックするとき
  • カスタムエンドポイントを使用して WooCommerce REST API を拡張するとき
  • プラグインを HPOS(High-Performance Order Storage)に対応させるとき

主な手順

  1. プラグインのひな形を設定する

    <?php
    /**
     * Plugin Name: My Custom WooCommerce Extension
     * Description: Adds custom functionality to WooCommerce
     * Version: 1.0.0
     * Author: Your Name
     * Requires Plugins: woocommerce
     * WC requires at least: 8.0
     * WC tested up to: 9.0
     *
     * @package MyWooExtension
     */
    
    defined('ABSPATH') || exit;
    
    // Check if WooCommerce is active
    if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
        return;
    }
    
    define('MWE_VERSION', '1.0.0');
    define('MWE_PLUGIN_DIR', plugin_dir_path(__FILE__));
    define('MWE_PLUGIN_URL', plugin_dir_url(__FILE__));
    
    // Declare HPOS compatibility
    add_action('before_woocommerce_init', function () {
        if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
            \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
                'custom_order_tables',
                __FILE__,
                true
            );
        }
    });
    
    // Initialize the plugin after WooCommerce loads
    add_action('woocommerce_loaded', function () {
        require_once MWE_PLUGIN_DIR . 'includes/class-mwe-main.php';
        MWE_Main::instance();
    });
    
  2. 注文のライフサイクルイベントに WooCommerce フックを使用する

    class MWE_Order_Handler {
        public function __construct() {
            // Order status transitions
            add_action('woocommerce_order_status_completed', [$this, 'on_order_completed'], 10, 2);
            add_action('woocommerce_order_status_changed', [$this, 'on_status_change'], 10, 4);
    
            // New order created
            add_action('woocommerce_checkout_order_created', [$this, 'on_order_created']);
    
            // Payment complete
            add_action('woocommerce_payment_complete', [$this, 'on_payment_complete']);
    
            // Before/after order item saved (HPOS compatible)
            add_action('woocommerce_new_order_item', [$this, 'on_new_order_item'], 10, 3);
        }
    
        public function on_order_completed(int $order_id, \WC_Order $order): void {
            // Example: trigger fulfillment via external API
            $items = $order->get_items();
            $shipping_address = $order->get_address('shipping');
    
            foreach ($items as $item) {
                $product = $item->get_product();
                $sku = $product->get_sku();
                $quantity = $item->get_quantity();
    
                // Call your external fulfillment service
                $this->send_to_fulfillment($sku, $quantity, $shipping_address);
            }
    
            $order->add_order_note('Order sent to fulfillment service.');
        }
    
        public function on_status_change(int $order_id, string $from, string $to, \WC_Order $order): void {
            // Log status transitions
            error_log(sprintf(
                'Order #%d transitioned from %s to %s',
                $order_id, $from, $to
            ));
        }
    }
    
  3. WooCommerce Settings API を使用して設定を追加する

    class MWE_Settings {
        public function __construct() {
            add_filter('woocommerce_settings_tabs_array', [$this, 'add_settings_tab'], 50);
            add_action('woocommerce_settings_tabs_mwe_settings', [$this, 'render_settings']);
            add_action('woocommerce_update_options_mwe_settings', [$this, 'save_settings']);
        }
    
        public function add_settings_tab(array $tabs): array {
            $tabs['mwe_settings'] = __('My Extension', 'my-woo-extension');
            return $tabs;
        }
    
        public function render_settings(): void {
            woocommerce_admin_fields($this->get_settings());
        }
    
        public function save_settings(): void {
            woocommerce_update_options($this->get_settings());
        }
    
        private function get_settings(): array {
            return [
                [
                    'title' => __('General Settings', 'my-woo-extension'),
                    'type'  => 'title',
                    'id'    => 'mwe_general_settings',
                ],
                [
                    'title'    => __('Enable Feature', 'my-woo-extension'),
                    'desc'     => __('Enable the custom feature', 'my-woo-extension'),
                    'id'       => 'mwe_enable_feature',
                    'type'     => 'checkbox',
                    'default'  => 'yes',
                ],
                [
                    'title'    => __('API Key', 'my-woo-extension'),
                    'desc'     => __('Enter your external service API key', 'my-woo-extension'),
                    'id'       => 'mwe_api_key',
                    'type'     => 'text',
                    'css'      => 'min-width: 300px;',
                ],
                [
                    'title'    => __('Processing Mode', 'my-woo-extension'),
                    'id'       => 'mwe_processing_mode',
                    'type'     => 'select',
                    'options'  => [
                        'sync'  => __('Synchronous', 'my-woo-extension'),
                        'async' => __('Asynchronous (Queue)', 'my-woo-extension'),
                    ],
                    'default'  => 'sync',
                ],
                [
                    'type' => 'sectionend',
                    'id'   => 'mwe_general_settings',
                ],
            ];
        }
    }
    
  4. フィルターを使用して商品データを変更する

    class MWE_Product_Customizer {
        public function __construct() {
            // Add a custom product tab in admin
            add_filter('woocommerce_product_data_tabs', [$this, 'add_product_tab']);
            add_action('woocommerce_product_data_panels', [$this, 'render_product_panel']);
            add_action('woocommerce_process_product_meta', [$this, 'save_product_meta']);
    
            // Modify price display on frontend
            add_filter('woocommerce_get_price_html', [$this, 'modify_price_display'], 10, 2);
    
            // Add custom data to cart item
            add_filter('woocommerce_add_cart_item_data', [$this, 'add_custom_cart_data'], 10, 3);
    
            // Display custom data in cart
            add_filter('woocommerce_get_item_data', [$this, 'display_cart_item_data'], 10, 2);
    
            // Save custom data to order item
            add_action('woocommerce_checkout_create_order_line_item', [$this, 'save_order_item_meta'], 10, 4);
        }
    
        public function add_product_tab(array $tabs): array {
            $tabs['mwe_custom'] = [
                'label'    => __('Custom Fields', 'my-woo-extension'),
                'target'   => 'mwe_custom_product_data',
                'class'    => [],
                'priority' => 80,
            ];
            return $tabs;
        }
    
        public function render_product_panel(): void {
            global $post;
            echo '<div id="mwe_custom_product_data" class="panel woocommerce_options_panel">';
    
            wp_nonce_field('mwe_save_product_meta', 'mwe_product_meta_nonce');
    
            woocommerce_wp_text_input([
                'id'          => '_mwe_custom_field',
                'label'       => __('Custom Field', 'my-woo-extension'),
                'description' => __('Enter a custom value for this product', 'my-woo-extension'),
                'desc_tip'    => true,
            ]);
    
            woocommerce_wp_select([
                'id'      => '_mwe_product_badge',
                'label'   => __('Product Badge', 'my-woo-extension'),
                'options' => [
                    ''         => __('None', 'my-woo-extension'),
                    'new'      => __('New', 'my-woo-extension'),
                    'sale'     => __('Sale', 'my-woo-extension'),
                    'limited'  => __('Limited Edition', 'my-woo-extension'),
                ],
            ]);
    
            echo '</div>';
        }
    
        public function save_product_meta(int $post_id): void {
            // Verify nonce before processing $_POST data
            if (!isset($_POST['mwe_product_meta_nonce']) ||
                !wp_verify_nonce($_POST['mwe_product_meta_nonce'], 'mwe_save_product_meta')) {
                return;
            }
    
            $custom_field = sanitize_text_field($_POST['_mwe_custom_field'] ?? '');
            update_post_meta($post_id, '_mwe_custom_field', $custom_field);
    
            $badge = sanitize_text_field($_POST['_mwe_product_badge'] ?? '');
            update_post_meta($post_id, '_mwe_product_badge', $badge);
        }
    }
    
  5. WooCommerce REST API を拡張する

    class MWE_REST_Controller {
        public function __construct() {
            add_action('rest_api_init', [$this, 'register_routes']);
        }
    
        public function register_routes(): void {
            register_rest_route('mwe/v1', '/analytics/summary', [
                'methods'             => \WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_analytics_summary'],
                'permission_callback' => [$this, 'check_admin_permission'],
            ]);
    
            register_rest_route('mwe/v1', '/products/(?P<id>\d+)/custom-data', [
                'methods'             => \WP_REST_Server::READABLE,
                'callback'            => [$this, 'get_product_custom_data'],
                'permission_callback' => '__return_true',
                'args'                => [
                    'id' => [
                        'validate_callback' => function ($param) {
                            return is_numeric($param);
                        },
                    ],
                ],
            ]);
        }
    
        public function check_admin_permission(\WP_REST_Request $request): bool {
            return current_user_can('manage_woocommerce');
        }
    
        public function get_analytics_summary(\WP_REST_Request $request): \WP_REST_Response {
            $days = absint($request->get_param('days') ?? 30);
            $date_from = date('Y-m-d', strtotime("-{$days} days"));
    
            // HPOS-compatible order query
            $orders = wc_get_orders([
                'date_created' => ">={$date_from}",
                'status'       => ['wc-completed', 'wc-processing'],
                'limit'        => -1,
                'return'       => 'ids',
            ]);
    
            $total_revenue = 0;
            foreach ($orders as $order_id) {
                $order = wc_get_order($order_id);
                $total_revenue += (float) $order->get_total();
            }
    
            return new \WP_REST_Response([
                'period'        => "{$days}_days",
                'total_orders'  => count($orders),
                'total_revenue' => $total_revenue,
                'avg_order'     => count($orders) > 0 ? $total_revenue / count($orders) : 0,
            ]);
        }
    }
    
  6. カスタム配送方法を作成する

    // Register the shipping method
    add_filter('woocommerce_shipping_methods', function (array $methods): array {
        $methods['mwe_custom_shipping'] = 'MWE_Custom_Shipping_Method';
        return $methods;
    });
    
    class MWE_Custom_Shipping_Method extends \WC_Shipping_Method {
        public function __construct(int $instance_id = 0) {
            $this->id                 = 'mwe_custom_shipping';
            $this->instance_id        = absint($instance_id);
            $this->method_title       = __('Custom Shipping', 'my-woo-extension');
            $this->method_description = __('Custom shipping rate calculation', 'my-woo-extension');
            $this->supports           = ['shipping-zones', 'instance-settings'];
    
            $this->init();
        }
    
        public function init(): void {
            $this->init_form_fields();
            $this->init_settings();
    
            $this->title = $this->get_option('title', 'Custom Shipping');
            $this->enabled = $this->get_option('enabled', 'yes');
        }
    
        public function init_form_fields(): void {
            $this->instance_form_fields = [
                'title' => [
                    'title'   => __('Method Title', 'my-woo-extension'),
                    'type'    => 'text',
                    'default' => 'Custom Shipping',
                ],
                'base_cost' => [
                    'title'   => __('Base Cost', 'my-woo-extension'),
                    'type'    => 'price',
                    'default' => '5.00',
                ],
                'per_item_cost' => [
                    'title'   => __('Per Item Cost', 'my-woo-extension'),
                    'type'    => 'price',
                    'default' => '1.00',
                ],
            ];
        }
    
        public function calculate_shipping($package = []): void {
            $base_cost = (float) $this->get_option('base_cost', 5);
            $per_item = (float) $this->get_option('per_item_cost', 1);
    
            $item_count = 0;
            foreach ($package['contents'] as $item) {
                $item_count += $item['quantity'];
            }
    
            $cost = $base_cost + ($per_item * $item_count);
    
            $this->add_rate([
                'id'    => $this->get_rate_id(),
                'label' => $this->title,
                'cost'  => $cost,
            ]);
        }
    }
    

例

カスタム WooCommerce メール通知
class MWE_Custom_Email extends \WC_Email {
    public function __construct() {
        $this->id             = 'mwe_order_shipped';
        $this->title          = __('Order Shipped', 'my-woo-extension');
        $this->description    = __('Sent when an order is marked as shipped', 'my-woo-extension');
        $this->heading        = __('Your order has shipped!', 'my-woo-extension');
        $this->subject        = __('Your {site_title} order #{order_number} has shipped', 'my-woo-extension');
        $this->template_base  = MWE_PLUGIN_DIR . 'templates/';
        $this->template_html  = 'emails/order-shipped.php';
        $this->template_plain = 'emails/plain/order-shipped.php';
        $this->customer_email = true;

        add_action('mwe_order_shipped_notification', [$this, 'trigger'], 10, 2);

        parent::__construct();
    }

    public function trigger(int $order_id, string $tracking_number): void {
        $this->object = wc_get_order($order_id);
        if (!$this->object) return;

        $this->recipient = $this->object->get_billing_email();
        $this->placeholders['{tracking_number}'] = $tracking_number;

        if ($this->is_enabled() && $this->get_recipient()) {
            $this->send(
                $this->get_recipient(),
                $this->get_subject(),
                $this->get_content(),
                $this->get_headers(),
                $this->get_attachments()
            );
        }
    }
}

// Register the email class
add_filter('woocommerce_email_classes', function (array $emails): array {
    $emails['MWE_Order_Shipped'] = new MWE_Custom_Email();
    return $emails;
});
HPOS に対応した注文メタデータへのアクセス
// Old way (post meta) — DEPRECATED
// $value = get_post_meta($order_id, '_custom_field', true);

// New way (HPOS compatible)
$order = wc_get_order($order_id);

// Read custom meta
$custom_value = $order->get_meta('_mwe_custom_field', true);

// Write custom meta
$order->update_meta_data('_mwe_custom_field', 'new_value');
$order->save();

// Query orders with custom meta (HPOS compatible)
$orders = wc_get_orders([
    'meta_query' => [
        [
            'key'   => '_mwe_custom_field',
            'value' => 'specific_value',
        ],
    ],
    'status' => 'completed',
    'limit'  => 50,
]);

ベストプラクティス

  • HPOS への対応を必ず宣言する — WooCommerce はカスタム注文テーブルへの移行を進めています。FeaturesUtil::declare_compatibility を使用し、注文に対する wp_posts の直接クエリは避けてください
  • WooCommerce の CRUD メソッドを使用する — 注文や商品のデータには get_post_meta() ではなく $order->get_total() などを使用してアクセスしてください。CRUD メソッドは従来のストレージと HPOS の両方で動作します
  • 適切な優先度でフックする — デフォルトの優先度は 10 です。WooCommerce のハンドラーより前に実行するには小さい値(5)を、後に実行するには大きい値(20 以上)を使用してください
  • すべてをサニタイズし、エスケープする — 入力には sanitize_text_field() を、出力には esc_html() / esc_attr() を使用してください。$_POST や $_GET のデータを決して信頼しないでください
  • WooCommerce の組み込み関数を使用する — wc_get_orders()、wc_get_products()、wc_price() はエッジケースに対応し、将来のバージョンとの互換性も確保します
  • メタキーに名前空間を使用する — 他のプラグインとの競合を避けるため、カスタムメタキーにはすべてプラグインのスラッグを接頭辞として付けてください(例:_mwe_)
  • マルチサイトに対応する — プラグインを WordPress マルチサイトで動作させる必要がある場合は、is_plugin_active_for_network() によるチェックを使用してください
  • WooCommerce のテストスイートでテストする — PHPUnit テストの基底クラスとして WC_Unit_Test_Case を使用してください

よくある落とし穴

問題解決策
WooCommerce の更新後にプラグインが動作しなくなるWooCommerce のクラスが利用可能であることを確認するため、plugins_loaded ではなく woocommerce_loaded にフックします
HPOS 有効時に注文メタデータが保存されないupdate_post_meta() ではなく $order->update_meta_data() と $order->save() を使用します
フックが複数回実行される(メールの重複送信、在庫の二重減算)静的フラグまたはトランジェントを使用して、フックがすでに処理されたか確認します。did_action() を使って確認してください
カスタム配送方法が表示されないwoocommerce_shipping_methods フィルターでその方法が登録されていること、およびクラスが WC_Shipping_Method を継承していることを確認します
REST API エンドポイントが 403 を返すpermission_callback を確認します。管理者向けエンドポイントには current_user_can('manage_woocommerce') を、公開エンドポイントには '__return_true' を使用してください

関連スキル

  • @woocommerce-performance
  • @product-data-modeling
  • @discount-engine
  • @ecommerce-seo
  • @erp-integration

コメント