プラグインなしでPHPファイルを機能単位で管理する方法

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

はじめに

以前の記事では、functions.phpを機能ごとにファイル分割してすっきり管理する方法を紹介しました。

今回はこの方法をさらに発展させ、子テーマ内に配置した機能ごとのフォルダ(PHP / CSS / JS)を、WordPressの管理画面からスイッチひとつで有効化・無効化(オン・オフ)できるようにします。

プラグイン「Code Snippets」等を使わず、子テーマ内に自分だけの「ミニプラグイン管理画面」を作るイメージです。

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

目的

通常のカスタマイズでは、PHPはfunctions.php、CSSはstyle.css、JSはjavascript.jsへとコードが分散してしまい管理が煩雑になりますが、この仕組みを導入することで主に2つのメリットがあります。

  1. PHP・CSS・JSを1箇所(1フォルダ)に集約できる
    機能ごとに1つのフォルダへまとめることで、関連するファイルをまとめて管理できます。
    「CSSのみ」「JSのみ」のカスタマイズであっても共通ファイルに追記する必要がなく、どこに何を書いたかが一目で分かります。
  1. スイッチひとつで安全にオン・オフ切り替えができる
    コードを直接削除したりコメントアウトしたりすることなく、管理画面上の操作だけでPHPの処理からCSS・JSの読み込みまで、機能全体を丸ごと安全に停止・再開できます。

プラグイン「Code Snippets」との比較

データベース上に直接コードを保存する「Code Snippets」とは異なり、通常のテキストファイルやディレクトリ構造としてそのまま管理できるため、Git等によるバージョン管理や他のサイトへの移植も非常に容易になります。(表1参照)。

プラグイン「Code Snippets」との比較
項目本手法Code Snippets
保存場所子テーマ内ファイルデータベース
バージョン管理Gitなどで可能難しい
可搬性(移植性)フォルダ移動・テーマ移行で完結スニペットのエクスポートが必要
テーマとの連動性高い(テーマに依存)低い(テーマと独立)

完成イメージ

基本仕様

子テーマ内にmy-pluginフォルダを作成し、その中に機能ごとのサブフォルダを配置します。

  • cocoon-child-master/
    • my-plugin
      • my-module1(モジュールフォルダ)
        • module1.php
        • style.css
        • javascript.js
      • my-module2
      • :
    • functions.php

フォルダ構成のルール

  • エントリーポイント
    各モジュールフォルダの直下に存在するPHPファイルのうち、アルファベット順で先頭になる1ファイルが起点(メインファイル)として実行されます。
    起点のファイルから呼び出すサブファイル群は、直下に並べずincludes/やassets/などのサブフォルダに分けて配置してください。
  • プラグインヘッダー
    WordPressの標準プラグインと同様の形式で、メインとなるPHPファイルの先頭にヘッダー情報を記述します。
    管理画面上でモジュール名や説明文として取得・表示されます。
<?php
/**
 * Plugin Name: モジュール名
 * Description: モジュールの説明
 * Version: バージョン
 * Author: 作成者名
 */

実装手順

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

// 定数定義
define('MY_MODULE_DIR', get_stylesheet_directory() . '/my-plugin');
define('MY_MODULE_URL', get_stylesheet_directory_uri() . '/my-plugin');


// 管理画面にメニューを追加
add_action('admin_menu', function () {
  add_menu_page(
    'モジュール管理',
    'モジュール管理',
    'manage_options',
    'php-module-manager',
    'render_php_module_manager',
    'dashicons-media-code',
    2
  );
});


/**
 * 管理画面ページを出力
 *
 * @return void
 */
if (!function_exists('render_php_module_manager')):
function render_php_module_manager() {
  if (!current_user_can('manage_options')) {
    wp_die('このページにアクセスする権限がありません。');
  }

  $enabled_modules = get_option('my_php_module_manager', []);
  $all_directories = glob(MY_MODULE_DIR . '/*', GLOB_ONLYDIR) ?: [];

  // 各ステータスの件数をカウント
  $count_all      = 0;
  $count_active   = 0;
  $count_inactive = 0;
  $valid_directories = [];

  foreach ($all_directories as $dir) {
    $php_files = glob($dir . '/*.php');
    if (!empty($php_files)) {
      $valid_directories[] = $dir;
      $slug = basename($dir);
      $count_all++;
      if (in_array($slug, $enabled_modules, true)) {
        $count_active++;
      } else {
        $count_inactive++;
      }
    }
  }

  // 現在のフィルター状態を取得
  $status = $_GET['status'] ?? 'all';

  // フィルターに応じて表示対象を絞り込み
  $directories = array_filter($valid_directories, function ($dir) use ($status, $enabled_modules) {
    $slug = basename($dir);
    $is_enabled = in_array($slug, $enabled_modules, true);
    if ($status === 'active') return $is_enabled;
    if ($status === 'inactive') return !$is_enabled;
    return true;
  });

  $base_url = admin_url('admin.php?page=php-module-manager');
  ?>
  <div class="wrap php-module-manager">
    <h1 class="wp-heading-inline">モジュール管理</h1>
    <hr class="wp-header-end">

    <ul class="subsubsub">
      <li class="all">
        <a href="<?= esc_url($base_url); ?>" class="<?= $status === 'all' ? 'current' : ''; ?>">
          すべて <span class="count">(<?= $count_all; ?>)</span>
        </a> |
      </li>
      <li class="active">
        <a href="<?= esc_url(add_query_arg('status', 'active', $base_url)); ?>" class="<?= $status === 'active' ? 'current' : ''; ?>">
          使用中 <span class="count">(<?= $count_active; ?>)</span>
        </a> |
      </li>
      <li class="inactive">
        <a href="<?= esc_url(add_query_arg('status', 'inactive', $base_url)); ?>" class="<?= $status === 'inactive' ? 'current' : ''; ?>">
          停止中 <span class="count">(<?= $count_inactive; ?>)</span>
        </a>
      </li>
    </ul>

    <table class="wp-list-table widefat plugins">
      <thead>
        <tr>
          <th scope="col" class="manage-column column-name column-primary">モジュール</th>
          <th scope="col" class="manage-column column-description">説明</th>
        </tr>
      </thead>
      <tbody id="the-list">
        <?php if (empty($directories)): ?>
          <tr class="no-items">
            <td class="colspanchange" colspan="2" style="padding: 15px; text-align: center; color: #666;">
              モジュールが見つかりません。
            </td>
          </tr>
        <?php else: ?>
          <?php foreach ($directories as $module_dir): ?>
            <?php
            $module_slug = basename($module_dir);
            $php_files   = glob($module_dir . '/*.php');
            $main_file   = $php_files[0];

            $data = get_file_data($main_file, [
              'name'        => 'Plugin Name',
              'version'     => 'Version',
              'description' => 'Description',
              'author'      => 'Author',
            ]);

            $is_enabled   = in_array($module_slug, $enabled_modules, true);
            $status_class = $is_enabled ? 'active' : 'inactive';
            $action       = $is_enabled ? 'deactivate' : 'activate';
            $action_label = $is_enabled ? '無効化' : '有効化';

            $border_style = $is_enabled ? 'style="border-left: 4px solid var(--wp-admin-theme-color, #2271b1);"' : '';

            $toggle_url = wp_nonce_url(
              admin_url('admin.php?page=php-module-manager&action=' . $action . '&module=' . urlencode($module_slug)),
              'toggle_module_' . $module_slug
            );
            ?>
            <tr class="<?= $status_class; ?>">
              <td class="plugin-title column-primary" <?= $border_style; ?>>
                <strong><?= esc_html($data['name'] ?: $module_slug); ?></strong>
                <div class="row-actions visible">
                  <span class="<?= $action; ?>">
                    <a href="<?= esc_url($toggle_url); ?>" class="<?= $is_enabled ? 'aria-button-if-js' : ''; ?>">
                      <?= $action_label; ?>
                    </a>
                  </span>
                </div>
              </td>
              <td class="column-description desc">
                <div class="plugin-description">
                  <p><?= esc_html($data['description'] ?: ''); ?></p>
                </div>
                <div class="<?= $status_class; ?> second plugin-version-author-uri">
                  <?php if (!empty($data['version'])): ?>
                    バージョン <?= esc_html($data['version']); ?> | 
                  <?php endif; ?>
                  <?php if (!empty($data['author'])): ?>
                    作成者: <?= esc_html($data['author']); ?> | 
                  <?php endif; ?>
                  フォルダ名: <code><?= esc_html($module_slug); ?></code>
                </div>
              </td>
            </tr>
          <?php endforeach; ?>
        <?php endif; ?>
      </tbody>
    </table>
  </div>
  <?php
}
endif;


// 管理画面からの設定データ更新
add_action('after_setup_theme', function () {
  if (!isset($_GET['page']) || $_GET['page'] !== 'php-module-manager') {
    return;
  }

  if (!isset($_GET['action'], $_GET['module'])) {
    return;
  }

  if (!current_user_can('manage_options')) {
    return;
  }

  $module = sanitize_file_name(basename($_GET['module']));
  $action = $_GET['action'];

  if (!check_admin_referer('toggle_module_' . $module)) {
    return;
  }

  $enabled_modules = get_option('my_php_module_manager', []);

  if ($action === 'activate') {
    if (!in_array($module, $enabled_modules, true)) {
      $enabled_modules[] = $module;
      $message = 'モジュールを有効化しました。';
    }
  } elseif ($action === 'deactivate') {
    $enabled_modules = array_diff($enabled_modules, [$module]);
    $message = 'モジュールを無効化しました。';
  }

  update_option('my_php_module_manager', array_values($enabled_modules));

  set_transient('my_admin_notice', [
    'message' => $message ?? '設定を更新しました。',
    'type'    => 'success',
  ], 30);

  // リダイレクト時に現在のステータスフィルター(status)を保持
  $redirect_url = admin_url('admin.php?page=php-module-manager');
  if (isset($_GET['status'])) {
    $redirect_url = add_query_arg('status', sanitize_key($_GET['status']), $redirect_url);
  }

  wp_safe_redirect($redirect_url);
  exit;
});


// 通知メッセージを出力
add_action('admin_notices', function () {
  if ($notice = get_transient('my_admin_notice')) {
    echo '<div class="notice notice-' . esc_attr($notice['type']) . ' is-dismissible"><p>' . esc_html($notice['message']) . '</p></div>';
    delete_transient('my_admin_notice');
  }
});


// 対象の処理を実行
add_action('after_setup_theme', function () {
  $enabled_modules = get_option('my_php_module_manager', []);

  if (empty($enabled_modules) || !is_array($enabled_modules)) {
    return;
  }

  $valid_enabled_modules = [];
  $has_changed = false;

  foreach ($enabled_modules as $module_slug) {
    $folder_name = sanitize_file_name(basename($module_slug));
    $module_dir  = MY_MODULE_DIR . '/' . $folder_name;

    if (is_dir($module_dir)) {
      $php_files = glob($module_dir . '/*.php');

      if (!empty($php_files) && is_readable($php_files[0])) {
        require_once $php_files[0];
        $valid_enabled_modules[] = $folder_name;
      } else {
        $has_changed = true;
      }
    } else {
      $has_changed = true;
    }
  }

  // 存在しないモジュールをDBから削除
  if ($has_changed) {
    update_option('my_php_module_manager', array_values($valid_enabled_modules));
  }
});

参考:分割モジュール内でのCSS・JS読み込み例

各モジュールフォルダ内に配置した独自のCSSやJSファイルは、モジュール単位で独立して読み込ませることができます。

機能ごとにスタイルやスクリプトを完結して管理できるため、保守性が大幅に向上します。

実装手順

以下は、モジュール内の style.cssやjavascript.jsを読み込む基本的な実装例です。

// フロント用JS・CSSを追加
add_action('wp_enqueue_scripts', function () {
  wp_enqueue_style('my-module1', MY_MODULE_URL . '/module1/style.css', [], null);
  wp_enqueue_script('my-module1', MY_MODULE_URL . '/module1/javascript.js', ['jquery'], null, true);
});

さいごに

今回紹介した方法を使えば、単なるコードの小分けにとどまらず、自作プラグインのように各機能を独立したモジュールとしてテーマ内で独自管理できるようになります。

モジュールごとに「PHP / CSS / JS」が1つのパッケージとして完結するため、機能のON/OFFはもちろん、不要になった機能の削除や別サイトへの移植もフォルダ単位で手軽に行えます。

プラグインを増やすことなく、WordPressプラグインと同等の柔軟性と保守性を子テーマ内で実現できる非常に強力な手法ですので、ぜひ活用してみてください。

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