wp-includes/pluggable.phpをrequire_onceしちゃ駄目という話

INDEX

  1. 前置き
  2. 問題となったコード
  3. やっちゃ駄目な解決方法
  4. pluggable.phpはオーバーライドできる関数なのでreuqire_onceしちゃ駄目
  5. 修正後のコード

前置き

WordPress でスニペットを簡単に管理する方法 : dogmap.jpを参考にこれまでfunctions.phpに記述していたスニペットをプラグインで管理するようにしました。簡潔に説明するとプラグインディレクトリ下にスニペット格納用ディレクトリを設置して、スニペットが必要な時にはその中にphpファイルを保存していく、というものです。

そこでいくつかファイルを放り込んで行ったのですがとある箇所でエラーが発生したのでその解決方法なり原因をシェアします。

問題となったコード

	if ( !current_user_can( 'edit_users' ) ) {

		function remove_menus() {
			global $menu;

			unset( $menu[2] ); // ダッシュボード
			unset( $menu[5] ); // 投稿
			// 省略
			unset( $menu[80] ); // 設定
		}

		add_action( 'admin_menu', 'remove_menus' );
	}
	

エラーが発生したのが上記コードです。
特定権限ユーザ以下の場合、管理画面のメニューを表示しないというものです。

エラー内容は次の通り。

Fatal error: Call to undefined function wp_get_current_user() in C:\xampp\htdocs\wordpress\wp-includes\capabilities.php on line 1187

wp-includes/capabilities.phpの1187行目にあるwp_get_current_user()が未定義とのこと。
で、wp_get_current_user()はwp-includes/pluggable.phpで定義されているからどうやらpluggable.phpがプラグイン読み込み時に読み込まれていないのが問題らしい。

	function current_user_can( $capability ) {
	$current_user = wp_get_current_user();

	if ( empty( $current_user ) )
		return false;

	$args = array_slice( func_get_args(), 1 );
	$args = array_merge( array( $capability ), $args );

	return call_user_func_array( array( $current_user, 'has_cap' ), $args );
	}
	

やっちゃ駄目な解決方法

上記エラー内容で検索を掛けるとよく目にした解決方法がこちら。

	// プラグインの先頭でpluggable.phpをrequire_onceする
	require_once ( ABSPATH.WPINC . '/pluggable.php');
	if ( !current_user_can( 'edit_users' ) ) {
		// 省略
	}
	

これでエラーが出ず処理も問題なく行われるようになったのですが(実際これでしばらく動かしてしまっていました)Ktai Styleプラグインを入れると以下のエラーが出るように。
auth_redirect()の関数名が重複してしまっているとのこと。
どうやら上記解決方法は問題がある模様・・・

Fatal error: Cannot redeclare auth_redirect() (previously declared in C:\xampp\htdocs\wordpress\wp-includes\pluggable.php:731) in C:\xampp\htdocs\wordpress\wp-content\plugins\ktai-style\admin\pluggable-override.php on line 13

pluggable.phpはオーバーライドできる関数なのでreuqire_onceしちゃ駄目

ここらでよくわからなくなったのでtwitterでつぶやいて見ることに。

@jim0912さんからダメとのご意見を頂いたのでやはり問題のある方法みたいだったので更に調べて見ることに。

そこで参考になったのが以下のサイト。

要はpluggable.php内の関数はプラグインでの再定義を意図した関数であるから先に定義するなっ、と。
例えばwp_get_current_user()を例にとると

  1. プラグインでwp_get_current_user関数を再定義
  2. pluggable.php読み込み時にwp_get_current_user関数が定義されていれば飛ばし、定義されていなれけばpluggable.php内のwp_get_current_user関数を読み込む

という流れになります。
なので先のKtai Styleの場合で、pluggable.phpをrequire_onceしてしまっていると以下のような流れになってバッティングしてしまいます。

  1. プラグインの先頭でpluggable.phpがrequire_onceされ関数が先に定義されてしまう
  2. Ktai Styleプラグインが読み込まれpluggable.phpで使用されている関数と同じ物を定義(←バッティング)

修正後のコード

というわけでフックを使いましょう、というご意見を頂いたので以下の様にしてみました。
一応問題はないと思いますがフック等の理解がまだ微妙なので合ってるか分かりません・・・

		function remove_menus() {
			if ( !current_user_can( 'edit_users' ) ) {
				global $menu;
	
				unset( $menu[2] ); // ダッシュボード
				unset( $menu[5] ); // 投稿
				// 省略
				unset( $menu[80] ); // 設定
			}
		}
		add_action( 'admin_init', 'remove_menus' );