WordPressマルチサイト対応プラグインの作り方

WordPress のマルチサイトは構築したことがなく、必要なら複数のシングルサイトを構築すればいいんじゃないのと思ってましたが、プラグインをいくつか公開しているとマルチサイトはサポートしてますかと問い合わせを受けることもあります

そこで、先日マルチサイト対応のページキャッシュプラグイン YASAKANI Cache を作ってみて、マルチサイトに対応プラグインを作る上で注意すべきポイント等がある程度わかったので紹介したいと思います

 

マルチサイト

初めにマルチサイトの構築を行ってみます

使用したのは VAW という Vagrant 仮想環境です。また、構築したサイトのデータベースを確認できたほうが解りやすいと思いますので下記記事を参照に初めに仮想環境に WordPress のシングルサイトを構築してみてください

VAW(Vagrant Ansible WordPress) のMySQLデータベースにHeidiSQLで接続する
HeidiSQLで VAW(Vagrant Ansible WordPress)のMySQLデータベースにSSHポートフォワード接続する手順を紹介します。 これでデータベース処理の確認やデバッグが格段に便利になります (^^)…

※VAWは、プロビジョニング時に指定してあれば簡単にマルチサイトが構築できますが、それでは手順がブラックボックス化されてしまうので、一旦シングル用にインストールしてからマルチサイト化します

マルチサイト構築

マルチサイトの構築に関しては、検索すればたくさんの記事が見つかります

ネットワークの作成 – WordPress Codex 日本語版

ここでは実際にテスト用 WordPress 4.5 のシングルサイトをマルチサイト化した手順を紹介します

  1. 準備(バックアップとプラグインの無効化)
  2. wp-config.php ファイルにマルチサイト許可用の設定を追記
  3. ネットワークの設置
  4. ネットワークの作成
  5. wp-config.php, .htaccess ファイルの修正
  6. サブサイト作成
準備(バックアップとプラグインの無効化)

テスト用の環境ならデータのバックアップは不要ですが、実際のサイトをマルチサイト化する場合には、忘れずにバックアップを行ってください

また、マルチサイト化するには、有効化しているプラグインを全て無効化する必要があります

wp-config.php ファイルにマルチサイト許可用の設定を追記

実際のサイトでは、FTPツール等を使って wp-config.php ファイルを置き換えますが、今回は Vagrant 仮想環境なので、同期フォルダー内の wp-config.php ファイルを UTF-8 対応エディターで直接編集すればOKです

wp-config.php の ABSPATH を定義より前に define (‘WP_ALLOW_MULTISITE’, true) を追記して保存します

//Multisite Settings
define( 'WP_ALLOW_MULTISITE' , true );


/* That's all, stop editing! Happy blogging. */

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
	define('ABSPATH', dirname(__FILE__) . '/');

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');
ネットワークの設置

管理画面にアクセスするとツールにネットワークの設定が追加されているので実行します

multisite-setting-1

ネットワークの作成

multisite-setting-2

ローカル環境なので、ここではサブディレクトリ型を選択して、インストールを実行します

※サブドメイン形式では、DNSやドメインウェブ等を設定してメインサイトとサブサイトの全てが同じサーバーの wordpress ディレクトリへ転送されるように設定する必要があります

wp-config.php, .htaccess ファイルの修正

下記のような画面が表示されますので、この設定を wp-config.php と .htaccess に行います

multisite-setting-3

wp-config.php

//Multisite Settings
define( 'WP_ALLOW_MULTISITE' , true );
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);
define('DOMAIN_CURRENT_SITE', 'vaw.local');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);


/* That's all, stop editing! Happy blogging. */

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
	define('ABSPATH', dirname(__FILE__) . '/');

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

.htaccess

元の記述を #でコメントアウトして、マルチサイト用のルールに置き換えます

# BEGIN WordPress
<IfModule mod_rewrite.c>
#RewriteEngine On
#RewriteBase /
#RewriteRule ^index\.php$ - [L]
#RewriteCond %{REQUEST_FILENAME} !-f
#RewriteCond %{REQUEST_FILENAME} !-d
#RewriteRule . /index.php [L]

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]

# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

これで再ログインして管理画面を開くと、元のシングルサイトが基本サイトとして登録されています

multisite-setting-4

サブサイト作成

この状態では1つの基本サイトがあるだけなので、ここにサブサイトを追加してみます
サイトネットワーク管理者からサイトを選択

multisite-setting-5

サブサイトの設定を開き新規追加からサブサイトを追加します

multisite-setting-6

multisite-setting-7

基本サイトに1つのサブサイトを追加した場合に管理画面の上部管理バーから3つの管理画面が操作可能となります

multisite-setting-8

実際にマルチサイトを構築しないとピンと来ないかもしれませんので、構築したことがない方は実際に構築してみることをお勧めします

3つの管理画面が出来て、ネットワーク管理者(特権管理者)は、全ての管理画面の操作が可能となります

  1. マルチサイトを管理する為のネットワーク管理者用の管理画面
  2. 基本サイト管理者用の管理画面
  3. サブサイト管理者用の管理画面

マルチサイトの場合、各サイトで使用するテーマ、プラグイン登録管理がシングルサイトと大きく変わってきます。これらの新規登録管理はネットワーク管理者が一元で管理を行います

マルチサイトを構築する場合の多くは、サブサイト毎に管理ユーザー(サブサイト管理者)を登録して、そのユーザーに管理者権限を与えます(もちろん特権管理者なら全てのサブサイトを管理することが出来ます)

そうすると、各サブサイトの管理者は、サブサイトの管理画面にしかログインできないので、テーマやプラグインをインストールする権限はなく、ネットワークの特権管理者がインストールしたテーマ、プラグインの有効化、無効化権限のみに限定されます

※サブサイトの管理者がプラグインの有効化、無効化の設定を行えるようにするには、特権管理者の設定画面で 管理メニューを有効化 でプラグインをチェックする必要があります。そうしないとサブサイトの管理者がログインしても管理画面に プラグイン のメニュー項目が表示されません

※この権限の違いは結構重要で、シングルサイトの管理者権限とマルチサイトの管理者権限では許可されている内容が変わるのでマルチサイトでは特権管理者と管理者の権限の違いを意識しなければなりません。プラグインの処理中に権限をチェックして条件判断しているような場合にはマルチサイトで不具合が出る可能性もありますので注意です

マルチサイトでインストール出来るのは特権管理者

それでは、テーマとプラグインでどんな感じか確かめてみましょう

テーマ

各サイトが使用できるテーマは、一旦ネットワーク管理者のテーマ管理ページで登録して、有効化されたテーマだけが各サイト毎のテーマで選択できるようになります

ネットワーク管理者からテーマをインストールして、使用を許可するテーマのみ有効化します

multisite-theme-install
基本サイト
ネットワーク管理で許可されているテーマが選択可能なので Twenty Sixteen を選択してみます

multisite-theme-main
サブサイト
ネットワーク管理で許可されているテーマが選択可能なので Twenty Forteen を選択してみます

multisite-theme-sub

※すべてのサイトでテーマを共通化する場合は、1つのテーマのみを有効化することで自動的にそのテーマが使われますが、各サイト毎に異なるテーマを使用し たい場合は、ネットワーク管理者が複数のテーマを有効化すると各サイトでその許可されたテーマから選択できるようになります

プラグイン

プラグインは、すべてのサイトで使用する場合と各サイト毎に使用するプラグインを設定する場合にわけられます。

定番のプログラムコードをハイライト表示する SyntaxHighlighter Evolved をインストールして様子を見てみます

multisite-plugin-install

ネットワークで有効化 をクリックするとすべてのサイトでこのプラグインが有効化されます

multisite-plugin-network-activate

この場合は、メインサイト、サブサイト共にプラグインが有効化され、各サイトでは無効化することもできなくなります。また、プラグインのオプション設定は各サイト別に行います。

それでは、基本サイトでは行番号を表示させ、サブサイトでは行番号を非表示に設定して、それぞれテスト用の投稿を1つ作ってみます

基本サイトmultisite-pluginoption-main

multisite-sample-main

サブサイトmultisite-pluginoption-sub

multisite-sample-sub

きちんと各サイト毎に設定どおり表示されるのが確認できます

以上がマルチサイトでの基本的な使用法となりますが、特定のサイトでのみプラグインを使いたい場合はネットワークで有効化しないことで、特権管理者が各サイト毎に有効化/無効化を指定できるようになります

サイト毎に有効化したい場合は、ネットワーク管理者では有効化しません

multisite-plugin-network-nonactive

すると各サイト毎に有効化するかどうかが指定できます

multisite-plugin-main-activate

このようにサイト毎に有効化するか無効化するかが指定できます

※サブサイトの管理者(管理者権限)がプラグインの有効化、無効化の設定を行えるようにするには、特権管理者の設定画面で 管理メニューを有効化 でプラグインをチェックする必要があります

 

データはデータベースにどのように保存されるか

multisite-DBtable

ちなみにデータベースを見てみると、サイト毎にテーブルが分けられているのが解ります

ネットワーク管理で有効化したプラグイン の場合は、wp_sitemeta テーブルの active_sitewide_plugins にセットされて有効化され、wp-settings.php から wp_get_active_network_plugins() 関数で呼び出されてロードされます。

個別サイト毎に有効化したプラグイン の場合は、サイト毎のテーブル wp_options, wp_2_options, wp_3_options, — から有効化した対象サイトのテーブルのみで active_plugins で有効化され、wp-settings.php から wp_get_active_and_valid_plugins() 関数で呼び出されてロードされます

また、各サイト毎のオプション設定値は、サイト別の wp_options, wp_2_options, wp_3_options,等でプラグインごとにプラグインが決めた設定名でオプション値が保存されます

従って、プラグインの有効状態、設定値は全てサイト毎に別設定となっていて、サーバーにアクセスがあった時にリクエストURLからどのサイトへのアクセスか判定され、対象テーブルが自動的に切り替えられて実行されます

以上でマルチサイトの構築法と設定、動作状態の概要が解ったと思いますので、いよいよマルチサイト対応のプラグインを作る上の注意点を紹介します

マルチサイト対応プラグインの作り方

権限

マルチサイト対応する場合は、特権管理者と管理者の権限の違いに気をつける必要があります

多くのプラグインでは、権限の違いで条件判断を行い処理機能を分けているということは行っていないと思いますが、プラグインのオプション設定画面で権限により設定項目を変えたいというようなケースでは、下記のように分けることもできます

if (current_user_can( 'edit_plugins' )) {
  //シングルサイト管理者及びマルチサイト特権管理者のみに許可する処理を記述

}
else if (current_user_can( 'activate_plugins' )) {
  //シングルサイト管理者及びマルチサイト特権管理者と管理者に許可する処理を記述

}

また、セキュリティ機能として権限の有無を判断するケースもよくあると思いますが、マルチサイトを考慮していなかった場合の条件とマルチサイト対応で条件に変わりがないか再チェックを行います

一番の違いは、プラグインの有効化/無効化/アンインストール時の処理になりますので、これらについては後述します

個別と共有

プラグイン毎のオプション設定値は、通常 get_option(), update_option() 等を使用していると思いますので、これらの関数を使い設定値の取得や更新を行っているだけならプラグインで何もしなくとも自然にサイト毎の設定で機能するようになっています

但し、データベースに独自テーブルを作成する場合は注意が必要です

独自テーブルに wp_ 等のプレフィックスが反映するようにテーブル名を作成していればサイト毎にテーブルが分けられますが、プレフィックスをつけない場合は、全てのサイトでテーブルを共有することとなります

意図して共有するなら問題ないのですが、共有することで思わぬ競合が起きることもあり得ますので注意が必要です

また、データファイルを利用する場合は、全てのサイトで共有するリソースとなりますので、共有されていることをきちんと認識して実装する必要があります

プラグインのオプション設定変更

権限や個別と共有で紹介したことを利用するとマルチサイトのオプションはいくつかのパターンに分けられます

  • プラグインのオプション設定はだれが行うか
    • 特権管理者権限のみに限定
    • 特権管理者およびサブサイト管理者
  • オプション設定値はサイト間で共通化、個別か
    • 各サイトで個別に設定する時は get_option, update_option 等のWP専用関数のみ使用
    • 共通で使用する時は、独自テーブルやファイルの共有リソースを用い、書き込みをメインサイトに限定、読み込みは全てのサイトにする等で実装可能

プラグインの有効化/無効化/アンインストール

有効化

プラグインのコード内でインストール時の処理というのは思いつかないのですが、有効化に対しては、シングルサイトとマルチサイトでは区別する必要がある場合があります

ちょっと特殊なケースとなりますが、ドロップイン (drop-in) や 必須 (must-use) プラグインなどの通常のプラグインとは違うプラグインと連動して動作する場合等は特に注意が必要です

私の作ったキャッシュプラグインを例に紹介します
キャッシュプラグインでは advanced-cache.php というドロップインプラグインを有効化した時にファイル生成して設置しますが、マルチサイトの場合は、他のサイトを有効化した時に既に生成済みで機能している場合を考慮する必要があります

実際のコードはこんな感じです

    function yasakani_cache_activation( $network_wide ) {
        wp_mkdir_p( YASAKANI_CACHE_DB_DIR );
        $act = false;
        if (is_multisite()) {
            if(! $network_wide){
                global $wpdb;
                $current_blog_id = get_current_blog_id();
                $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
                foreach ( $blog_ids as $blog_id ) {
                    if($blog_id != $current_blog_id){
                        switch_to_blog( $blog_id );
                        if ( is_plugin_active( plugin_basename( __FILE__ ))){
                            $act = true;
                        }
                    }
                }
                switch_to_blog( $current_blog_id );
            }
        }
        if($act === false){
            //db file direct access deny for apache server
            if ( !file_exists( YASAKANI_CACHE_DB_DIR . '/.htaccess' ))
                @file_put_contents( YASAKANI_CACHE_DB_DIR . '/.htaccess', "Deny from all\r\n");
            
            if(!class_exists('Magatama_DB', FALSE))
                require_once( __DIR__ . '/magatama_DB.php');
            $dbobj = new Magatama_DB('CREATE');

            Yasakani_setting::advanced_cache_file('create');
            Yasakani_setting::wp_config_file( 'insert_wp_cache' );
        }
    }
    register_activation_hook( __FILE__,   'yasakani_cache_activation' );

他のサイトで既にプラグインが有効化しているか、ベータベーステーブルを切り替えながら確認して、有効化されていない場合のみ advanced-cache.php 生成と wp-config.php にドロップインを有効化するための定義値を書き込んでいます

無効化

これも有効化と同様ですが、ドロップイン (drop-in) や 必須 (must-use) プラグインなどの通常のプラグインとは違うプラグインと連動して動作する場合等は注意が必要です

例えばページキャッシュで1つのサブサイトを無効化した場合、advanced-cache.php ファイル自体は共有しているプログラムコードなので他のサイトでは必要なので、削除することは出来ません

他の方法で無効にしたサイトでキャッシュが機能しないようにする必要があり、私の作ったキャッシュプラグインでは共有リソース中のデータを無効化して機能しないようにしました

    function yasakani_cache_deactivation( $network_deactivating ) {
        $act = false;
        if (is_multisite()) {
            if(! $network_deactivating){
                global $wpdb, $yasakani_cache;
                $current_blog_id = get_current_blog_id();
                $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
                foreach ( $blog_ids as $blog_id ) {
                    if($blog_id == $current_blog_id){
                        //current site cache clear
                        if(!empty($yasakani_cache))
                            $yasakani_cache->delete_all_content($current_blog_id);
                    }
                    else {
                        //other site active check
                        switch_to_blog( $blog_id );
                        if ( is_plugin_active( plugin_basename( __FILE__ )))
                            $act = true;
                    }
                }
                switch_to_blog( $current_blog_id );
            }
        }
        if($act === false){
            Yasakani_setting::wp_config_file( 'delete_wp_cache');
            Yasakani_setting::advanced_cache_file('delete');
        }
    }
    register_deactivation_hook( __FILE__,   'yasakani_cache_deactivation' );

自サイトのキャッシュデータのみクリアすることでキャッシュが機能しないようにしています

アンインストール

これは、プラグインの削除時に実行される処理で、通常はデータベース内のオプション設定値や作成した共有リソース等をクリアして後始末します

立つ鳥跡を濁さずといいますが、アンインストールで後始末していないプラグインも結構あります (^_^;

特にマルチサイトの場合は、各サブサイトのデータベーステーブルにオプション設定値が分かれていますので消し忘れないように注意が必要です

    function yasakani_cache_uninstall() {
        
        if ( !is_multisite()) {
            delete_option('yasakani_option' );
        } else {
            global $wpdb;
            $current_blog_id = get_current_blog_id();
            $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
            foreach ( $blog_ids as $blog_id ) {
                switch_to_blog( $blog_id );
                delete_option('yasakani_option' );
            }
            switch_to_blog( $current_blog_id );
        }        
        if(class_exists('Magatama_DB', FALSE)){
            global $yasakani_cache;
            if ( !empty($yasakani_cache->db) )
                $yasakani_cache->db = NULL;   //pdo_sqlite Close
        }
        Yasakani_setting::wp_config_file( 'delete_wp_cache');
        Yasakani_setting::advanced_cache_file('delete');
        if ( file_exists( YASAKANI_CACHE_DB_DIR . '/yasakani_cache.db' ))  
            @unlink( YASAKANI_CACHE_DB_DIR . '/yasakani_cache.db' );
        if ( file_exists( YASAKANI_CACHE_DB_DIR . '/.htaccess' ))  
            @unlink( YASAKANI_CACHE_DB_DIR . '/.htaccess' );
        @rmdir( YASAKANI_CACHE_DB_DIR );
    }
    register_uninstall_hook(__FILE__, 'yasakani_cache_uninstall');

上記ポイントを押さえれば、大抵のプラグインは簡単な修正でマルチサイトに対応することが可能です

ちなみに私の作成したキャッシュプラグインのソースを見てみたいという方は公式サイトに公開していますのでダウンロードしてみて下さい

YASAKANI Cache
Simple ! Easy !! Ultra-high-speed !!!. Definitive edition of the page cache.

以上です

マルチサイトに対応するのはそれほど難しくないので、プラグインを作る方はマルチサイトのこともお忘れなく (^^)

 


まとめ記事紹介

search star user home refresh tag chevron-left chevron-right exclamation-triangle calendar comment folder thumb-tack navicon angle-double-up angle-double-down angle-up angle-down quote-left googleplus facebook instagram twitter rss