カスタム投稿タイプを用いて「CTA」ウィジェットを管理する方法

Cocoonカスタマイズ集
この記事は約30分で読めます。
記事内に広告が含まれています。

はじめに

WordPressを用いた情報発信において、コンバージョン率の向上を目的としたCTA(Call To Action)の設置は重要な施策です。
特にブログやメディアサイトでは、読者の閲覧文脈に即したCTAを適切に表示することが、成果に直結します。

しかし、Cocoonテーマに標準搭載されている「CTA」ウィジェットは、表示条件ごとに個別のウィジェットを配置する必要があり、投稿単位やカテゴリー単位で設定を管理しなければなりません。
そのため、サイト規模の拡大に伴い設定の重複や修正漏れが発生しやすく、運用や保守の負担が大きくなるという課題があります。

そこで、従来のウィジェット型CTAが抱える運用上の問題点を整理し、それに対する設計思想と実装方針を明らかにすることで、本手法の有効性を検討することを目的としています。

この記事では、この方法について説明します。

目的

Cocoon標準の「CTA」ウィジェットを使ってカテゴリーごとに表示条件を分岐させる運用では、主に2つの課題が生じます。

  1. 表示条件ごとに「CTA」ウィジェットを複数配置する必要がある
    カテゴリーが増えるたびにウィジェットエリアへ個別にCTAを追加し、それぞれ表示条件を設定しなければならず、配置作業が煩雑になります。
  1. ウィジェットエリア上でしか管理できず、全体像を一括把握できない
    どのCTAがどのカテゴリーに対応しているのか、何がどこに配置されているのかを一覧で確認できないため、管理や見直しの負担が大きくなります。

今回紹介するコードを導入すれば、CTAの設定を「カスタム投稿タイプ(cta)」として一括管理できるようになり、投稿ページで該当カテゴリーに応じたCTAが自動表示されるようになります。

  1. ウィジェットを複数配置する必要がなく、カテゴリーに応じて自動切り替えが可能
  2. 管理画面のCTA一覧で設定状況を一括把握でき、追加・編集が容易になる

単純にカテゴリー一覧などに一律表示するのではなく、投稿本文の文脈に沿った適切なCTAを自動表示させることで、読者の関心や行動を自然に誘導し、コンバージョン率の最適化を図ることができます。

完成イメージ

CTA一覧
CTA編集画面

実装手順

プラグインの形式で実装します。

以下の手順で実装します。

  • my-auto-cta
    • auto-cta.php
    • assets
      • js
        • admin.js
    • includes
      • admin-ui.php
      • cpt.php
      • matcher.php
      • preview.php
      • renderer.php
      • save.php
      • widget.php
  • ラベル
    PHPを追加

    以下のコードをauto-cta.phpに追加します。

    <?php
    /**
     * Plugin Name: My Auto CTA Manager
     * Description: Cocoon用CTAをカテゴリー条件に応じて自動表示します。
     * Version: 1.0.0
     * Author: CHU-YA
     */
    
    if (!defined('ABSPATH')) {
      exit;
    }
    
    // 定数定義
    define('AUTO_CTA_PATH', plugin_dir_path(__FILE__));
    define('AUTO_CTA_URL', plugin_dir_url(__FILE__));
    
    
    // Cocoon以外では警告表示
    add_action('admin_notices', function () {
      if (stripos(get_template(), 'cocoon') !== false) {
        return;
      }
    
      echo '<div class="notice notice-error"><p><strong>CTA管理:</strong> このプラグインは Cocoon テーマ専用です。</p></div>';
    });
    
    
    // Cocoon以外は処理対象外
    if (stripos(get_template(), 'cocoon') === false) {
      return;
    }
    
    
    // ファイル読み込み
    require_once AUTO_CTA_PATH . 'includes/admin-ui.php';
    require_once AUTO_CTA_PATH . 'includes/cpt.php';
    require_once AUTO_CTA_PATH . 'includes/matcher.php';
    require_once AUTO_CTA_PATH . 'includes/preview.php';
    require_once AUTO_CTA_PATH . 'includes/renderer.php';
    require_once AUTO_CTA_PATH . 'includes/save.php';
    require_once AUTO_CTA_PATH . 'includes/widget.php';
  • ラベル
    PHPを追加

    以下のコードをadmin-ui.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    // メタボックスを追加
    add_action('add_meta_boxes', function () {
      add_meta_box(
        'auto_cta',
        'CTA設定',
        'render_auto_cta',
        'my_auto_cta',
        'normal',
        'high'
      );
    });
    
    
    // スラッグメタボックスを非表示
    add_action('add_meta_boxes', function () {
      remove_meta_box('slugdiv', 'my_auto_cta', 'normal');
    });
    
    
    /**
     * メタボックスの入力フォームを出力
     *
     * @param WP_Post $post 投稿オブジェクト
     * @return void
     */
    if (!function_exists('render_auto_cta')):
    function render_auto_cta($post) {
      // CTAプレビューURLを取得
      $preview_url = add_query_arg([
        'action'  => 'auto_cta_preview',
        'post_id' => $post->ID,
      ], admin_url('admin-ajax.php'));
    
      // CTAフィールドを取得
      $fields = [
        'heading',
        'image_url',
        'layout',
        'button_text',
        'button_url',
        'button_color',
      ];
    
      foreach ($fields as $key) {
        $$key = get_post_meta($post->ID, 'my_auto_cta_' . $key, true);
      }
    
      // 選択済みカテゴリーを取得
      $selected_terms = (array) get_post_meta($post->ID, 'my_auto_cta_target_categories', true);
    
      // ボタンカラーリストを設定
      $button_colors = [
        'btn-black'       => '黒色',
        'btn-red'         => '赤色',
        'btn-pink'        => 'ピンク',
        'btn-purple'      => '紫色',
        'btn-deep'        => '深紫',
        'btn-indigo'      => '紺色(インディゴ)',
        'btn-blue'        => '青色',
        'btn-light-blue'  => '水色',
        'btn-cyan'        => '明るい青(シアン)',
        'btn-teal'        => '緑色がかった青(ティール)',
        'btn-green'       => '緑色',
        'btn-light-green' => '明るい緑',
        'btn-lime'        => 'ライム',
        'btn-yellow'      => '黄色',
        'btn-amber'       => '琥珀色(アンバー)',
        'btn-orange'      => 'オレンジ',
        'btn-deep-orange' => 'ディープオレンジ',
        'btn-brown'       => '茶色',
        'btn-grey'        => '灰色',
      ];
    
      wp_nonce_field('auto_cta_action', 'auto_cta_nonce');
      ?>
      <div class="auto-cta">
        <div class="auto-cta__preview-frame" style="margin-bottom:16px;">
          <iframe id="auto-cta-preview-frame" src="<?= esc_url($preview_url); ?>" style="width:100%;border:1px solid #ddd;min-height:300px;"></iframe>
        </div>
    
        <hr>
    
        <p>
          <label>見出し</label><br>
          <input type="text" name="heading" value="<?= esc_attr($heading); ?>" class="widefat">
        </p>
    
        <p>
          <label>画像とメッセージのレイアウト</label><br>
          <select name="layout">
            <option value="cta-top-and-bottom" <?php selected($layout, 'cta-top-and-bottom'); ?>>画像・メッセージを上下に配置</option>
            <option value="cta-left-and-right" <?php selected($layout, 'cta-left-and-right'); ?>>画像・メッセージを左右に配置</option>
            <option value="cta-right-and-left" <?php selected($layout, 'cta-right-and-left'); ?>>メッセージ・画像を左右に配置</option>
          </select>
        </p>
    
        <p>
          <label>画像</label><br>
          <input type="text" name="image_url" id="auto-cta-image-url" value="<?= esc_attr($image_url); ?>" class="widefat" style="margin-bottom:5px;" placeholder="URLがここに入ります">
          <button type="button" class="button" id="auto-cta-media-button">メディアから選択</button>
          <button type="button" class="button" id="auto-cta-media-clear">クリア</button>
        </p>
    
        <p>
          <label>ボタンテキスト</label><br>
          <input type="text" name="button_text" value="<?= esc_attr($button_text); ?>">
        </p>
    
        <p>
          <label>ボタンURL</label><br>
          <input type="url" name="button_url" value="<?= esc_attr($button_url); ?>" class="widefat">
        </p>
    
        <p>
          <label>ボタン色</label><br>
          <select name="button_color">
            <?php foreach ($button_colors as $val => $label) : ?>
              <option value="<?= esc_attr($val); ?>" <?php selected($button_color, $val); ?>><?= esc_html($label); ?></option>
            <?php endforeach; ?>
          </select>
        </p>
    
        <label>表示対象カテゴリー</label>
    
        <div class="auto-cta__custom-tax-container categorydiv" id="cta-custom-tax-container" style="max-height:200px; overflow-y:auto; border:1px solid #ddd; padding:8px; background:#fdfdfd;">
          <ul class="auto-cta__category-checklist categorychecklist form-no-clear" id="auto-cta-category-checklist" style="margin:0">
            <?php
            wp_terms_checklist(0, [
              'taxonomy'      => 'category',
              'checked_ontop' => false,
              'selected_cats' => $selected_terms,
            ]);
            ?>
          </ul>
        </div>
    
        <div class="auto-cta__hidden-cats" id="auto-cta-hidden-cats"></div>
      </div>
      <?php
    }
    endif;
    
    
    // 管理画面用JS・CSSを追加
    add_action('admin_enqueue_scripts', function ($hook_suffix) {
      global $post;
    
      if (
        ($hook_suffix === 'post-new.php' || $hook_suffix === 'post.php') &&
        isset($post) &&
        $post->post_type === 'my_auto_cta'
      ) {
        wp_enqueue_media();
    
        wp_enqueue_script('auto-cta-admin',  AUTO_CTA_URL . 'assets/js/admin.js',  ['jquery', 'wp-element', 'wp-data'], '1.0.0', true);
      }
    });
  • ラベル
    PHPを追加

    以下のコードをcpt.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    // カスタム投稿タイプを追加
    add_action('init', function () {
      register_post_type('my_auto_cta', [
        'label'         => 'CTA',
        'public'        => false,
        'show_ui'       => true,
        'supports'      => ['title', 'editor'],
        'menu_position' => 20,
        'show_in_rest'  => true,
      ]);
    });
    
    
    // 管理画面一覧にカラムを追加
    add_filter('manage_my_auto_cta_posts_columns', function ($columns) {
      $columns['cta_categories']    = 'カテゴリー';
      $columns['button_url_status'] = 'リンク状態';
    
      return $columns;
    });
    
    
    // 管理画面一覧のカラムにデータを表示
    add_action('manage_my_auto_cta_posts_custom_column', function ($column, $post_id) {
      switch ($column) {
        case 'cta_categories':
          // 表示対象カテゴリーを取得
          $cats = get_post_meta($post_id, 'my_auto_cta_target_categories', true);
    
          if (empty($cats) || !is_array($cats)) {
            echo '—';
            break;
          }
    
          $names = [];
    
          // IDごとにカテゴリー情報を取得
          foreach ($cats as $cat_id) {
            $term = get_term($cat_id, 'category');
    
            if (!$term || is_wp_error($term)) {
              continue;
            }
    
            $names[] = $term->name;
          }
    
          // カテゴリー名を表示
          echo !empty($names)
            ? esc_html(implode('、', $names))
            : '—';
    
          break;
    
        case 'button_url_status':
          // ボタンURLを取得
          $url = get_post_meta($post_id, 'my_auto_cta_button_url', true);
    
          if (empty($url)) {
            echo '—';
            break;
          }
    
          // HTTPステータスを取得
          $response = wp_remote_head($url, ['timeout' => 5]);
    
          if (is_wp_error($response)) {
            echo '<span style="color:red;">リンク切れ</span>';
            break;
          }
    
          $status = wp_remote_retrieve_response_code($response);
    
          if ($status >= 200 && $status < 300) {
            echo '<span style="color:green;">OK</span>';
          } else {
            echo '<span style="color:red;">リンク切れ</span>';
          }
    
          break;
      }
    }, 10, 2);
  • ラベル
    PHPを追加

    以下のコードをmatcher.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    /**
     * 投稿にマッチするCTA投稿IDを取得
     *
     * @return array CTA投稿IDの配列
     */
    if (!function_exists('auto_cta_get_matched_cta_ids')):
    function auto_cta_get_matched_cta_ids() {
      // 現在の投稿IDを取得
      $post_id = get_the_ID();
    
      // 投稿IDが存在しない場合は処理対象外
      if (!$post_id) {
        return [];
      }
    
      // 現在の投稿のカテゴリーIDを取得
      $post_cats = wp_get_post_categories($post_id);
    
      // カテゴリーが存在しない場合は処理対象外
      if (empty($post_cats)) {
        return [];
      }
    
      // 公開済みCTAを取得
      $query = new WP_Query([
        'post_type'      => 'my_auto_cta',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'fields'         => 'ids',
        'meta_query'     => [
          [
            'key'     => 'my_auto_cta_target_categories',
            'compare' => 'EXISTS',
          ],
        ],
      ]);
    
      // CTAが存在しない場合は処理対象外
      if (empty($query->posts)) {
        return [];
      }
    
      $matched = [];
    
      foreach ($query->posts as $cta_id) {
        // CTAの表示対象カテゴリーを取得
        $cta_cats = (array) get_post_meta($cta_id, 'my_auto_cta_target_categories', true);
    
        // 対象カテゴリーが存在しない場合は処理対象外
        if (empty($cta_cats)) {
          continue;
        }
    
        // 投稿カテゴリーと対象カテゴリーが一致したCTAを追加
        if (array_intersect($post_cats, $cta_cats)) {
          $matched[] = $cta_id;
        }
      }
    
      return $matched;
    }
    endif;
  • ラベル
    PHPを追加

    以下のコードをpreview.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    // Ajaxアクションを追加
    add_action('wp_ajax_auto_cta_preview', function () {
      // 権限チェック
      if (!current_user_can('edit_posts')) {
        wp_die();
      }
    
      // 投稿IDを取得
      $post_id = (int) ($_GET['post_id'] ?? 0);
    
      if (!$post_id) {
        wp_die();
      }
    
      // ヘッダーを出力
      status_header(200);
      header('Content-Type: text/html; charset=' . get_option('blog_charset'));
    
      ?>
      <!doctype html>
      <html>
      <head>
        <meta charset="utf-8">
        <!-- Cocoon 親テーマ CSS -->
        <link rel="stylesheet" href="<?= esc_url(get_template_directory_uri() . '/style.css'); ?>">
        <!-- Cocoon 子テーマ CSS -->
        <link rel="stylesheet" href="<?= esc_url(get_stylesheet_directory_uri() . '/style.css'); ?>">
        <!-- Font Awesome 4 -->
        <link rel="stylesheet" href="<?= esc_url(FONT_AWESOME_4_URL); ?>">
      </head>
      <body style="margin:0;padding:12px;background:#fff;">
        <?= auto_cta_render_from_post_id($post_id); ?>
      </body>
      </html>
      <?php
      exit;
    });
  • ラベル
    PHPを追加

    以下のコードをrenderer.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    /**
     * CTA投稿IDからCTAのHTMLを生成
     *
     * @param int $cta_id CTA投稿ID
     * @return string CTA用HTML
     */
    if (!function_exists('auto_cta_render_from_post_id')):
    function auto_cta_render_from_post_id($cta_id) {
      if (!$cta_id) {
        return '';
      }
    
      // 本文を取得
      $content = get_post_field('post_content', $cta_id);
    
      // CTAデータを取得
      $data = [
        'heading'      => get_post_meta($cta_id, 'my_auto_cta_heading', true),
        'image_url'    => get_post_meta($cta_id, 'my_auto_cta_image_url', true),
        'layout'       => get_post_meta($cta_id, 'my_auto_cta_layout', true),
        'button_text'  => get_post_meta($cta_id, 'my_auto_cta_button_text', true),
        'button_url'   => get_post_meta($cta_id, 'my_auto_cta_button_url', true),
        'button_color' => get_post_meta($cta_id, 'my_auto_cta_button_color', true),
      ];
    
      // HTMLを生成
      return get_cta_tag($data, $content);
    }
    endif;
  • ラベル
    PHPを追加

    以下のコードをsave.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    // カスタムフィールドを保存
    add_action('save_post_my_auto_cta', function ($post_id) {
      // nonceチェック
      if (!isset($_POST['auto_cta_nonce']) || !wp_verify_nonce(wp_unslash($_POST['auto_cta_nonce']), 'auto_cta_action')) {
        return;
      }
    
      // 自動保存チェック
      if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
      }
    
      // 権限チェック
      if (!current_user_can('edit_post', $post_id)) {
        return;
      }
    
      // 各フィールドを保存
      $fields = [
        'heading',
        'image_url',
        'layout',
        'button_text',
        'button_url',
        'button_color',
      ];
    
      foreach ($fields as $key) {
        if (!isset($_POST[$key])) {
          continue;
        }
    
        $value = sanitize_text_field(wp_unslash($_POST[$key]));
    
        update_post_meta($post_id, 'my_auto_cta_' . $key, $value);
      }
    
      // 表示対象カテゴリーを取得
      $cats = isset($_POST['target_categories'])
        ? array_map('intval', (array) wp_unslash($_POST['target_categories']))
        : [];
    
      // 表示対象カテゴリーを保存
      update_post_meta($post_id, 'my_auto_cta_target_categories', $cats);
    });
  • ラベル
    PHPを追加

    以下のコードをwidget.phpに追加します。

    <?php
    if (!defined('ABSPATH')) {
      exit;
    }
    
    /**
     * 自動CTAウィジェット
     */
    class Auto_CTA_Widget extends WP_Widget {
      public function __construct() {
        parent::__construct(
          'auto_cta_widget',
          '自動CTA(カテゴリー連動)',
          [
            'description' => 'カテゴリー条件に応じてCTAを自動表示します。',
          ]
        );
      }
    
    
      /**
       * ウィジェットの設定フォーム
       *
       * @param array $instance 保存された値
       * @return void
       */
      public function form($instance) {
        $title = $instance['title'] ?? '';
        ?>
        <p>
          <label for="<?= esc_attr($this->get_field_id('title')); ?>">タイトル:</label>
          <input class="widefat" id="<?= esc_attr($this->get_field_id('title')); ?>" name="<?= esc_attr($this->get_field_name('title')); ?>" type="text" value="<?= esc_attr($title); ?>">
        </p>
        <p>設定不要です。ウィジェットを配置すると自動でCTAが表示されます。</p>
        <?php
      }
    
    
      /**
       * ウィジェットの設定を保存
       *
       * @param array $new_instance 新しい値
       * @param array $old_instance 古い値
       * @return array 保存する値
       */
      public function update($new_instance, $old_instance) {
        $instance = [];
    
        // タイトルを保存
        $instance['title'] = !empty($new_instance['title'])
          ? sanitize_text_field($new_instance['title'])
          : '';
    
        return $instance;
      }
    
    
      /**
       * ウィジェットを表示
       *
       * @param array $args ウィジェット引数
       * @param array $instance 保存された値
       * @return void
       */
      public function widget($args, $instance) {
        // 投稿ページ以外は処理対象外
        if (!is_single()) {
          return;
        }
    
        // 該当するCTAを取得
        $cta_ids = auto_cta_get_matched_cta_ids();
    
        // 該当するCTAがない場合は処理対象外
        if (empty($cta_ids)) {
          return;
        }
    
        // ウィジェットを表示
        echo $args['before_widget'];
    
        // ウィジェットタイトルを表示
        if (!empty($instance['title'])) {
          echo $args['before_title'];
          echo esc_html($instance['title']);
          echo $args['after_title'];
        }
    
        // CTAを表示
        echo '<div class="auto-cta"><div class="auto-cta__widget-wrapper" style="display:flex;flex-direction:column;gap:1em;">';
    
        foreach ($cta_ids as $cta_id) {
          echo auto_cta_render_from_post_id($cta_id);
        }
    
        echo '</div></div>';
        echo $args['after_widget'];
      }
    }
    
    
    // ウィジェットを追加
    add_action('widgets_init', function () {
      register_widget('Auto_CTA_Widget');
    });
  • ラベル
    JavaScriptを追加

    以下のコードをadmin.jsに追加します。

    jQuery(function ($) {
      // DOM要素を取得
      var $frame         = $('#auto-cta-preview-frame');
      var $list          = $('#auto-cta-category-checklist');
      var $hidden        = $('#auto-cta-hidden-cats');
      var $imageUrlInput = $('#auto-cta-image-url');
      var $mediaButton   = $('#auto-cta-media-button');
      var $clearButton   = $('#auto-cta-media-clear');
    
      // 必須要素がない場合は終了
      if (!$frame.length || !$list.length || !$hidden.length) {
        return;
      }
    
    
      /**
       * 選択カテゴリーを隠しフィールドに同期
       */
      function syncHidden() {
        $hidden.empty();
    
        $list.find('input[type=checkbox]:checked').each(function () {
          $('<input>', {
            type: 'hidden',
            name: 'target_categories[]',
            value: this.value,
          }).appendTo($hidden);
        });
      }
    
    
      /**
       * プレビューを再読み込み
       */
      function reloadPreview() {
        if (!$frame.length) {
          return;
        }
    
        try {
          var url = new URL($frame.attr('src'));
    
          url.searchParams.set('_t', Date.now());
    
          $frame.attr('src', url.toString());
        } catch (e) {
          var src  = $frame.attr('src');
          var base = src.indexOf('&_t=') !== -1
            ? src.split('&_t=')[0]
            : src;
    
          $frame.attr('src', base + '&_t=' + Date.now());
        }
      }
    
    
      // メディアアップローダー
      var customUploader;
    
    
      // メディア選択
      $mediaButton.on('click', function (e) {
        e.preventDefault();
    
        if (customUploader) {
          customUploader.open();
          return;
        }
    
        customUploader = wp.media({
          title: 'CTA画像を選択',
          button: {
            text: '画像を使用する',
          },
          multiple: false,
        });
    
    
        // 画像選択
        customUploader.on('select', function () {
          var attachment = customUploader
            .state()
            .get('selection')
            .first()
            .toJSON();
    
          $imageUrlInput.val(attachment.url);
    
          reloadPreview();
        });
    
        customUploader.open();
      });
    
    
      // 画像をクリア
      $clearButton.on('click', function () {
        $imageUrlInput.val('');
    
        reloadPreview();
      });
    
    
      // 初期化
      syncHidden();
    
    
      // カテゴリー変更を監視
      $list.on('change', 'input[type=checkbox]', syncHidden);
    
    
      // Gutenbergの保存完了を監視
      if (window.wp && wp.data && wp.data.select('core/editor')) {
        var wasSaving = false;
    
        wp.data.subscribe(function () {
          var editor = wp.data.select('core/editor');
    
          if (!editor) {
            return;
          }
    
          var isSaving     = editor.isSavingPost();
          var isAutosaving = editor.isAutosavingPost();
    
          // 保存完了を確認
          if (wasSaving && !isSaving && !isAutosaving) {
            reloadPreview();
          }
    
          wasSaving = isSaving;
        });
      }
    
    
      // フォーム送信後にプレビューを更新
      $('#post').on('submit', function () {
        setTimeout(reloadPreview, 1000);
      });
    });

さいごに

今回紹介した方法を使えば、投稿ページにおいて、カテゴリーに応じたCTAを自動的に表示できるようになります。
従来のウィジェット単位での手動管理に比べ、設定や運用の手間を大幅に削減できる点が大きなメリットです。
また、どの投稿にどのCTAが表示されるかを簡単に把握できるため、運用効率が格段に向上します。

さらに、CTA本文や画像、ボタンの色などのカスタマイズも、カスタム投稿タイプ(cta)上で一元管理できます。
CTA本文はGutenbergエディターを用いて簡単に入力できるため、直感的な編集が可能です。
これにより、サイト全体で統一感のあるコンバージョン誘導を実現できます。

タイトルとURLをコピーしました