WordPress MU

 ブログソフトのWordPressにはマルチユーザー対応のWordPress MUがあります。次期バージョンのWordPress 3ではこのMUと統合されると言うことでちょっと楽しみです。(その事は先日お伝えしましたね(^^ゞ)

 そのWordPress MUなのですが、仕事でMU対応のプラグインを作っていると何やらエラーが発生。

 いろいろ調べてみると設定データを取得するget_blog_option関数に問題があることがわかりました。以下にその原因と対策を明記しておきます。

get_blog_option関数とは

 まず、get_blog_option関数に付いて説明しておきます。WordPress MUは複数のブログを運営することができます。そのブログ別にいろいろな設定ができ、そのブログ別に設定内容を取得するのがget_blog_option関数です。

 WordPressの場合はget_option関数で設定データを取得するのですが、get_blog_option関数はWordPress MU独自の関数になります。

連想配列のシリアル化

 今回発生したエラーはWordPress MUのバージョン2.9.2で、登録してある設定データをただ単に呼び出すだけなら特に問題はありません。発生するのは一度、データを保存してすぐにデータを呼び出す時に発生します。それもある特定のデータのみです。

 「特定のデータ」とはどのようなデータかというと、WordPress(MUも含む)ではPHPの連想配列をデータベースに保存することができます。

 どのようにして保存するかというとPHPのserialize関数を使用して連想配列をシリアル化、つまり文字列に変換してデータベースに保存します。これは連想配列だけではなくオブジェクトもシリアル化して保存することができるようです。

 このシリアル化された連想配列をget_blog_option関数で呼び出す時に問題が発生します。

get_blog_option関数のソース

 ではどの部分でその様な問題が発生するのかを見てみましょう。

 場所はWordPress MUの /wp-includes/wpmu-functions.php ファイルです。このファイルの中の「function get_blog_option」を検索してもらうと下記のようなプログラムがあります。


function get_blog_option( $blog_id, $setting, $default = false ) {
	global $wpdb;

	$key = $blog_id."-".$setting."-blog_option";
	$value = wp_cache_get( $key, "site-options" );
	if ( $value == null ) {
		$blog_prefix = $wpdb->get_blog_prefix( $blog_id );
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$blog_prefix}options WHERE option_name = %s", $setting ) );
		if ( is_object( $row ) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
			$value = $row->option_value;
			if ( $value == false ) {
				wp_cache_set( $key, 'falsevalue', 'site-options' );
			} else {
				wp_cache_set( $key, $value, 'site-options' );
			}
		} else { // option does not exist, so we must cache its non-existence
			wp_cache_set( $key, 'noop', 'site-options' );
			$value = $default;
		}
	} elseif( $value == 'noop' ) {
		$value = $default;
	} elseif( $value == 'falsevalue' ) {
		$value = false;
	}
	// If home is not set use siteurl.
	if ( 'home' == $setting && '' == $value )
		return get_blog_option( $blog_id, 'siteurl' );

	if ( 'siteurl' == $setting || 'home' == $setting || 'category_base' == $setting )
		$value = preg_replace( '|/+$|', '', $value );

	if (! @unserialize( $value ) )
		$value = stripslashes( $value );

	return apply_filters( 'blog_option_' . $setting, maybe_unserialize( $value ), $blog_id );
}

 上からプログラムを見てみると4行目で作ったキーを元にwp_cache_get関数を使用して設定データをキャッシュから引っ張り出します。もし、キャッシュがない場合は次のif文でデータベースから設定データを引っ張り出しています。

 $value変数に入った設定値はちょっとゴニョゴニョしてreturnで返されます。かなり端折りましたが大体こんな感じです。

問題の箇所

 さて、どのような場合に問題となるかですが、登録してあるデータが連想配列でキャッシュから引っ張り出した場合はシリアル化されたままの文字列が$valueに格納されます。しかし、キャッシュがなかった場合はDBからシリアル化から戻された連想配列で$valueに格納されます。

 なぜ、ここでシリアル化が戻されているかまでは追いかけていませんが、おそらくWordPressの関数のどこかで戻しているのでしょう。

 そして、先ほど「ゴニョゴニョして」と省略しましたが、32行目の

	if (! @unserialize( $value ) )
		$value = stripslashes( $value );

で、キャッシュが無くでDBから返された連想配列はunserialize関数で戻すことができないのでFalseが返り、さらに「!」で否定されるので「$value = stripslashes( $value );」が行われます。

 けど、stripslashes関数は文字列のクォート部分を取り除く関数で、マニュアルを見てみると

注意: stripslashes() は再帰的な処理を行いません。 この関数を多次元配列に適用する場合は、 再帰的な関数を使用する必要があります。

とあり、実際にstripslashes関数に連想配列を通すと「Array」という文字列になってしまうのです。

対策

 根本的な対策はWordPress MUの開発者にお願いするとして、とりあえずは

	if (! @unserialize( $value ) )
		$value = stripslashes( $value );

の「!」を取り除いた

	if ( @unserialize( $value ) )
		$value = stripslashes( $value );

にすることで暫定的な対策を施せました。

 多分、これでいいと思うのですが、他のプラグインとによってどうなるのか?などいろいろあるかもしれません。けど、とりあえずはこれで回避できました。もし、他に何かあれば教えて頂ければと思います。

アマゾンのサーバでエラーが起こっているかもしれません。
一度ページを再読み込みしてみてください。