いまWordPressでマルチサイトを作っています。親サイトがポータルで、子サイトが各地域のサイトという構成です。サイドバーにはバナーなどの導線を配置し、ネットワーク全体で共有したいと思います。で、WordPressには現在のブログを切り替える機能があるのでこんな感じでできるかなーと思いきや、なんとできません。
switch_to_blog(1);
dynamic_sidebar('main-sidebar');
restore_current_blog();
ググッて見たところ、Sharing Dynamic Sidebars across Multisite Blogsという記事が見つかりまして、3つのアプローチが紹介されていました。
- 親サイトでウィジェットを保存するタイミングでそれを子サイトにコピーする
- 親サイトでサイドバーの内容を全部キャッシュしておき、それを子サイト側から参照する
- 有料プラグインを買う
どれもあまりスマートではないし、「そんなに難しくないでしょハハハ」と高をくくっていたのですが、けっこうハマりました。
追記:2025年1月に更新したやり方が最後に書いてあります。
ダイナミックサイドバーの仕組み
ダイナミックサイドバーはウィジェットを表示します。管理画面からは各ウィジェットの情報を保存することができます。バナーなら画像のURLとか、カテゴリー一覧なら何回層目までだすかとか、そういう情報ですね。これは各ブログのwp_optionsテーブルに保存されます。データ構造的には2種類あります。
- どのサイドバーにどのウィジェットが保存されているか
- 各ウィジェットの情報
要するにこれを取得し、子サイトのサイドバーが表示されるタイミングで上書きすればよいということになります。
子テーマからサイドバーを削除する
今回はResponsiveというテーマの子テーマとして、ポータルテーマと地域テーマを作成します。最近のWordPressテーマはウィジェットが5個も6個も登録されているので、それを流用する形です。
ポイントとしては、子テーマの管理画面にウィジェットを表示しないようにすることでしょうか。「入力できる画面があるのに入力してもなにも起きない」となると、混乱の元ですからね。
子テーマのfunctions.phpにこんなコードを書きます。
/**
* ウィジェットを親サイトと共通にする
*/
function _child_site_widget(){
//ウィジェット情報を保存しているグローバル変数を取得
global $wp_registered_sidebars, $wp_registered_widgets, $wpdb, $wp_widget_factory;
foreach($wp_registered_sidebars as $id => $params){
//管理画面なら全部消す
if(!is_admin()){
unregister_sidebar($id);
}
}
}
add_action('widgets_init', '_child_site_widget', 90);
これで小テーマの管理画面には「このテーマはウィジェットに対応していません」と表示されます。
親サイトのサイドバー情報を取得する
WordPressはウィジェットの情報をsidebars_widgetsという関数で取得し、これにはフックが用意されています。管理画面の場合はサイドバーなし、公開画面の場合は親サイトのサイドバーを返すようにします。ちなみに、この情報は配列で返ってくるので、var_dumpするなり内容を確認してみてください。
/**
* サイドバーのオプションを返す
* @param array $sidebars
* @return array
*/
function _child_site_sidebar_widgets($sidebars){
if(is_admin()){
return array();
}else{
//親サイトのサイドバー情報を取得する
$sidebars = get_blog_option(1, 'sidebars_widgets');
return $sidebars;
}
}
add_filter('sidebars_widgets', '_child_site_sidebar_widgets');
子サイトのウィジェット情報を上書きする
ウィジェットの情報はテーマ初期化時点でグローバル変数に格納されます。これを上書きしましょう。さっき書いたコードに追加する形です。
/**
* ウィジェットを親サイトと共通にする
*/
function _child_site_widget(){
//ウィジェット情報を保存しているグローバル変数を取得
global $wp_registered_sidebars, $wp_registered_widgets, $wpdb, $wp_widget_factory;
foreach($wp_registered_sidebars as $id => $params){
//管理画面なら全部消す
if(!is_admin()){
unregister_sidebar($id);
}
}
//いったんウィジェットを空にする
$wp_registered_widgets = array();
//親サイトのウィジェット情報を取得。MySQLべた書きになってしまいましたが、
//wp_optionsなどのテーブル名は設定によって異なる可能性があります。
$results = $wpdb->get_results("SELECT option_name, option_value FROM wp_options WHERE option_name LIKE 'widget_%'");
foreach($results as $widget){
//配列に展開
$option_value = unserialize($widget->option_value);
if(empty($option_value)){
continue;
}
//一つ一つのウィジェットを登録
foreach($option_value as $key => $instance){
if(!is_numeric($key)){
continue;
}
//ウィジェットに必要な形式の配列を作成
$id = preg_replace("/^widget_/", "", $widget->option_name.'-'.$key);
$instance['number'] = $key;
$widget_instance = array(
'name' => $id,
'callback' => 'wp_widget_controll',
'classname' => '',
'params' => array(),
'object' => null
);
foreach($wp_widget_factory->widgets as $class_name => $widget_class){
//登録されているウィジェットから初期化のための情報を引っこ抜く
if($widget->option_name == $widget_class->option_name){
$widget_instance['params']['object'] = $widget_class; //ウィジェットのインスタンスを保存(重要)
$widget_instance['params']['instance'] = $instance;
$widget_instance['classname'] = $widget_class->widget_options['classname'];
$widget_instance['callback'] = '_my_widget'; //_my_widgetが呼び出される
break;
}
}
$wp_registered_widgets[$id] = $widget_instance;
}
}
}
add_action('widgets_init', '_child_site_widget', 90);
$widget_instance[‘callback’]がウィジェット出力時に呼び出される関数なのですが、これをWordPressのウィジェットAPIと上手く連携させることができません。また、関数はcall_user_func_arrayで呼び出されるので、引数もなんかアレです。したがって、自前の関数から無理矢理ウィジェットを初期化するという力技を使います。ここらへん、アップデートの時に壊れるやり方かもしれませんね。
function _my_widget(){
//渡された引数を全部取得
$args = func_get_args();
//$args[1] ウィジェットクラスのインスタンス
//$args[0] ウィジェットの見映えやクラス名が保存されている配列
//$args[2] 個々のウィジェットが持つ情報を保存した配列
if(is_callable(array($args[1], 'widget'))){
//ウィジェットのインスタンスがwidgetを実行可能なら実行
$args[1]->widget($args[0], $args[2]);
}
}
これで無事サイドバーが共通化できました。新規構築なら有料テーマ買って解決というのも全然アリだとは思いますが、既存サイトの場合はそうもいかないと思います。プラグインがあったりするかもしれませんので、知っている人はコメントいただけると助かります。
それではみなさま、よい週末を。
マルチサイトでウィジェット共有にキャッシュを使ったアプローチ
※2025年1月29日追記
この投稿が何度もヒットするので、最近利用したキャッシュを使う方法も紹介しておきます。なんでやり方を変えたかというと……
- ブロックウィジェットというものが登場し、レンダリングが一筋縄ではいかなくなった(どこでやってるのか、よくわからない)
- カスタム投稿タイプが親サイトにだけ存在している場合など、ウィジェットが正常表示されないケースが多い。これはメニューも同じ。
- 複数のウィジェットのうち、特定のものだけ共有したいというニーズが存在する。
よって、最近はこんなやり方をしています。
/**
* 共有するサイドバーのリストを返す
*
* ここをカスタムできるようにすると、便利かもしれません。
*
* @return string[]
*/
function my_get_shared_sidebars() {
return [
'footer-sidebar',
];
}
/**
* 共有サイドバーを出力する専用の関数
*
* @param string $sidebar サイドバーの名前
*
* @return void
*/
function my_shared_sidebar( $sidebar ) {
if ( is_main_site() ) {
// 親サイトならそのまま出力してキャッシュを保存。
ob_start();
dynamic_sidebar( $sidebar );
$rendered = ob_get_clean();
update_site_option( 'my_shared_sidebar_' . $sidebar, $rendered );
} elseif ( is_active_sidebar( $sidebar ) ) {
// 子サイトにサイドバーが設定されているならそのまま表示
ob_start();
dynamic_sidebar( $sidebar );
$rendered = ob_get_clean();
} else {
// 子サイトで個別設定がない場合は親サイトのキャッシュを表示
$rendered = get_site_option( 'my_shared_sidebar_' . $sidebar );
}
if ( $rendered ) {
// ここまできたら、表示すべきサイドバーがあるということ
echo $rendered;
}
}
/**
* 親サイトでウィジェットが更新されたらキャッシュも更新するフック
*
* @return void
*/
function my_clear_sidebar_cache_on_widget_update() {
if ( is_main_site() ) {
foreach ( my_get_shared_sidebars() as $sidebar ) {
//共有サイドバーを全部更新
ob_start();
dynamic_sidebar( $sidebar );
update_site_option( 'my_shared_sidebar_' . $sidebar, ob_get_clean() );
}
}
}
add_action( 'update_option_sidebars_widgets', 'my_clear_sidebar_cache_on_widget_update', 100 );
サイドバーの存在判定として is_active_sidebar() を使いたい場合はまた内部の処理が変わってくると思いますが、だいたいこんな感じです。
干支が一周まわってもググると自分の記事が出てくるので、やはりブログは書いとくものですね。
