[コードリーディング]wpmu-delete-blog()関数を読む!

WordPressのマルチサイトで、子ブログを削除するwpmu-delete-blog()という関数を読んでいきたいと思います。

Codexは英語版でも何も書かれてません。(定義場所だけ書いてるけど(´・ω・`))。

定義は、wp-admin/includes/ms.phpです。msはmulti-siteの事なので、このファイルを通して読むとよさそうです。

バージョンは、3.2.1です。

ソースコード

/**
 * Delete a blog
 *
 * @since 3.0.0
 *
 * @param int $blog_id Blog ID
 * @param bool $drop True if blog's table should be dropped.  Default is false.
 * @return void
 */
function wpmu_delete_blog( $blog_id, $drop = false ) {
	global $wpdb;

	$switch = false;
	if ( $blog_id != $wpdb->blogid ) {
		$switch = true;
		switch_to_blog( $blog_id );
	}

	$blog_prefix = $wpdb->get_blog_prefix( $blog_id );

	do_action( 'delete_blog', $blog_id, $drop );

	$users = get_users( array( 'blog_id' => $blog_id, 'fields' => 'ids' ) );

	// Remove users from this blog.
	if ( ! empty( $users ) ) {
		foreach ( $users as $user_id ) {
			remove_user_from_blog( $user_id, $blog_id) ;
		}
	}

	update_blog_status( $blog_id, 'deleted', 1 );

	if ( $drop ) {
		if ( substr( $blog_prefix, -1 ) == '_' )
			$blog_prefix =  substr( $blog_prefix, 0, -1 ) . '\_';

		$drop_tables = $wpdb->get_results( "SHOW TABLES LIKE '{$blog_prefix}%'", ARRAY_A );
		$drop_tables = apply_filters( 'wpmu_drop_tables', $drop_tables );

		reset( $drop_tables );
		foreach ( (array) $drop_tables as $drop_table) {
			$wpdb->query( "DROP TABLE IF EXISTS ". current( $drop_table ) ."" );
		}
		$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->blogs WHERE blog_id = %d", $blog_id ) );
		$dir = apply_filters( 'wpmu_delete_blog_upload_dir', WP_CONTENT_DIR . "/blogs.dir/{$blog_id}/files/", $blog_id );
		$dir = rtrim( $dir, DIRECTORY_SEPARATOR );
		$top_dir = $dir;
		$stack = array($dir);
		$index = 0;

		while ( $index < count( $stack ) ) {
			# Get indexed directory from stack
			$dir = $stack[$index];

			$dh = @opendir( $dir );
			if ( $dh ) {
				while ( ( $file = @readdir( $dh ) ) !== false ) {
					if ( $file == '.' || $file == '..' )
						continue;

					if ( @is_dir( $dir . DIRECTORY_SEPARATOR . $file ) )
						$stack[] = $dir . DIRECTORY_SEPARATOR . $file;
					else if ( @is_file( $dir . DIRECTORY_SEPARATOR . $file ) )
						@unlink( $dir . DIRECTORY_SEPARATOR . $file );
				}
			}
			$index++;
		}

		$stack = array_reverse( $stack );  // Last added dirs are deepest
		foreach( (array) $stack as $dir ) {
			if ( $dir != $top_dir)
			@rmdir( $dir );
		}
	}

	$wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = '{$blog_prefix}autosave_draft_ids'" );
	$blogs = get_site_option( 'blog_list' );
	if ( is_array( $blogs ) ) {
		foreach ( $blogs as $n => $blog ) {
			if ( $blog['blog_id'] == $blog_id )
				unset( $blogs[$n] );
		}
		update_site_option( 'blog_list', $blogs );
	}

	if ( $switch === true )
		restore_current_blog();
}

コメント

/**
 * Delete a blog
 *
 * @since 3.0.0
 *
 * @param int $blog_id Blog ID
 * @param bool $drop True if blog's table should be dropped.  Default is false.
 * @return void
 */
 

(子)ブログを削除します。
バージョン3.0.0から導入です(マルチサイトが導入された時)
ひとつ目の引数は、整数値でブログID。
ふたつ目の引数は、trueかfalse。trueにすると子ブログ用に作られたテーブルも削除します(何も書かないとfalseなのでテーブルは保存される)。

いくつかのパートに分けていく

function wpmu_delete_blog( $blog_id, $drop = false ) {
	// 0:下準備
	/* 
	グローバル変数 $wpdb、
	操作対象にするブログIDの手当、
	子ブログの接頭辞を取得、
	子ブログに所属するユーザを取得。
	*/
	global $wpdb;

	$switch = false;
	if ( $blog_id != $wpdb->blogid ) {
		$switch = true;
		switch_to_blog( $blog_id );
	}

	$blog_prefix = $wpdb->get_blog_prefix( $blog_id );

	do_action( 'delete_blog', $blog_id, $drop );

	$users = get_users( array( 'blog_id' => $blog_id, 'fields' => 'ids' ) );

	// 1:子ブログからユーザを取り除く
	// Remove users from this blog.
	if ( ! empty( $users ) ) {
		foreach ( $users as $user_id ) {
			remove_user_from_blog( $user_id, $blog_id) ;
		}
	}

	// 2:ブログのステータスフラグをdeletedにする。
	update_blog_status( $blog_id, 'deleted', 1 );

	// 3:子ブログ専用のテーブル群を削除するところ
	/*
	テーブル削除、
	子ブログのファイルも削除、
	インデックスがおかしなことにならないようにするなど。
	*/
	if ( $drop ) {
		if ( substr( $blog_prefix, -1 ) == '_' )
			$blog_prefix =  substr( $blog_prefix, 0, -1 ) . '\_';

		$drop_tables = $wpdb->get_results( "SHOW TABLES LIKE '{$blog_prefix}%'", ARRAY_A );
		$drop_tables = apply_filters( 'wpmu_drop_tables', $drop_tables );

		reset( $drop_tables );
		foreach ( (array) $drop_tables as $drop_table) {
			$wpdb->query( "DROP TABLE IF EXISTS ". current( $drop_table ) ."" );
		}
		$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->blogs WHERE blog_id = %d", $blog_id ) );
		$dir = apply_filters( 'wpmu_delete_blog_upload_dir', WP_CONTENT_DIR . "/blogs.dir/{$blog_id}/files/", $blog_id );
		$dir = rtrim( $dir, DIRECTORY_SEPARATOR );
		$top_dir = $dir;
		$stack = array($dir);
		$index = 0;

		while ( $index < count( $stack ) ) {
			# Get indexed directory from stack
			$dir = $stack[$index];

			$dh = @opendir( $dir );
			if ( $dh ) {
				while ( ( $file = @readdir( $dh ) ) !== false ) {
					if ( $file == '.' || $file == '..' )
						continue;

					if ( @is_dir( $dir . DIRECTORY_SEPARATOR . $file ) )
						$stack[] = $dir . DIRECTORY_SEPARATOR . $file;
					else if ( @is_file( $dir . DIRECTORY_SEPARATOR . $file ) )
						@unlink( $dir . DIRECTORY_SEPARATOR . $file );
				}
			}
			$index++;
		}

		$stack = array_reverse( $stack );  // Last added dirs are deepest
		foreach( (array) $stack as $dir ) {
			if ( $dir != $top_dir)
			@rmdir( $dir );
		}
	}

	// 4:ユーザメタ情報から削除ブログに関わる部分を削除
	$wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = '{$blog_prefix}autosave_draft_ids'" );
	$blogs = get_site_option( 'blog_list' );
	if ( is_array( $blogs ) ) {
		foreach ( $blogs as $n => $blog ) {
			if ( $blog['blog_id'] == $blog_id )
				unset( $blogs[$n] );
		}
		update_site_option( 'blog_list', $blogs );
	}

	// 5:0番で手当てしたブログIDをもとに戻す
	if ( $switch === true )
		restore_current_blog();
}

パート0:下準備

global $wpdb;

$switch = false;
if ( $blog_id != $wpdb->blogid ) {
	$switch = true;
	switch_to_blog( $blog_id );
}

$blog_prefix = $wpdb->get_blog_prefix( $blog_id );

do_action( 'delete_blog', $blog_id, $drop );

$users = get_users( array( 'blog_id' => $blog_id, 'fields' => 'ids' ) );

global $wpdb;は、関数リファレンス/wpdb Classで解説されていますが、データベースに安全に接続するために用意されたWordPressのクラスです。

関数の中で使うためには、global $wpdb;を宣言する必要があります。今回もDBにアクセスして消したり書き換えたりしていますので。

次に、$switch変数に絡めてこれから操作するブログのIDをきちんとセットしています。
この関数を実際に使う時は、wpmu_delete_blog( $blog_id )という風にします。この時、操作しているブログは本人のブログであるときと、ネットワークアドミンである時があるはずです。()他にもあるかも?

なので、引数で渡された$blog_idとDBにアクセスして調べる今現在のブログID$wpdb->blogidを比較して、異なっていれば、操作対象を一時的に変更するswitch_to_blog( $blog_id )を使います。

で、スイッチをしたかどうかを$switchに保存しておいて、あとで元に戻しています。

その後、ブログIDからそのブログのprefixを取得したり(wp_2_optionsとか)、そのブログに所属するユーザを取得したりしています。

パート1:子ブログからユーザを取り除く

if ( ! empty( $users ) ) {
	foreach ( $users as $user_id ) {
		remove_user_from_blog( $user_id, $blog_id) ;
	}
}

さっき取得したユーザをブログから取り除いています。
Codex > WPMU Functions/remove user from blog

パート2:ブログのステータスフラグをdeletedにする

update_blog_status( $blog_id, 'deleted', 1 );

wp_blogsテーブルに保存されている値を変更するようです。
これによって、表示されなかったりするようです。他には、成人用とかスパムとかそういうフラグを立てることもできるようです。

パート3:子ブログ専用のテーブル群を削除するところ

ここは、wpmu_delete_blog( $blog_id, $drop )で、2個目の引数をtrueにした時だけ、実行されます。

パート3−1:下準備

if ( substr( $blog_prefix, -1 ) == '_' )
	$blog_prefix =  substr( $blog_prefix, 0, -1 ) . '\_';

substr(検索対象文字列, 何番目の文字目からですか, 何文字取得しますか)で取得するので、テーブル接頭辞の最後の文字を取得し、それが”_”だった場合、最後の_と\_に入れ替えます。

パート3−2:子ブログのテーブルを削除するところ

$drop_tables = $wpdb->get_results( "SHOW TABLES LIKE '{$blog_prefix}%'", ARRAY_A );
$drop_tables = apply_filters( 'wpmu_drop_tables', $drop_tables );

SHOW TABLES LIKE 'pattern'は、データベース全体からテーブルを検索して取得します。$wpdb->get_results('query', output_type);は、クエリを実行しその結果を取得する$wpdbの使い方です。ARRAY_Aは連想配列として取得するための引数です。

取得したものを使った、wpmu_drop_tablesフィルターフックがあります。直前で取得したテーブルの配列に対して何か操作したい場合はこれが使えるということでしょうか。(オプションテーブルは消さないで残したいとか、そういうことでしょうかね。使い方がイマイチ不明です。)

reset( $drop_tables );
foreach ( (array) $drop_tables as $drop_table) {
	$wpdb->query( "DROP TABLE IF EXISTS ". current( $drop_table ) ."" );
}

reset()関数は、配列の中で何番目の要素を扱うのかを、一番最初に戻すためのもの。直前のフィルターで操作した場合、たとえば最後の配列要素を取得したり操作したりすると、次からのforeach文で、最初の方の要素が飛ばされてしまったりするからではと思ってます。

そして、foreachでテーブルを削除しています。

$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->blogs WHERE blog_id = %d", $blog_id ) );

ここでは、wp_blogsテーブルから該当ブログIDを持つデータを削除しています。
$wpdb->prepare( 'query'[, value_parameter, value_parameter ... ] );はSQLインジェクションを防ぐためのメソッドです。sprintfみたいにあとから代入する値を、後ろに引数としてわたします。

パート3−3:子ブログのメディアファイル群を削除するところ

$dir = apply_filters( 'wpmu_delete_blog_upload_dir', WP_CONTENT_DIR . "/blogs.dir/{$blog_id}/files/", $blog_id );
$dir = rtrim( $dir, DIRECTORY_SEPARATOR );
$top_dir = $dir;
$stack = array($dir);
$index = 0;

while ( $index < count( $stack ) ) {
	# Get indexed directory from stack
	$dir = $stack[$index];

	$dh = @opendir( $dir );
	if ( $dh ) {
		while ( ( $file = @readdir( $dh ) ) !== false ) {
			if ( $file == '.' || $file == '..' )
				continue;

			if ( @is_dir( $dir . DIRECTORY_SEPARATOR . $file ) )
				$stack[] = $dir . DIRECTORY_SEPARATOR . $file;
			else if ( @is_file( $dir . DIRECTORY_SEPARATOR . $file ) )
				@unlink( $dir . DIRECTORY_SEPARATOR . $file );
		}
	}
	$index++;
}

$stack = array_reverse( $stack );  // Last added dirs are deepest
foreach( (array) $stack as $dir ) {
	if ( $dir != $top_dir)
	@rmdir( $dir );
}

最初になんかフィルターがある。
rtrim(‘文字列’, 削除対象)関数は、文字列の末尾から空白や改行、タブなんかを取り除くものです。ここでは削除対象にDIRECTORY_SEPARATORが指定されていますが、これはphp自体の定数でその名の通りディレクトリを区切る文字のこと。(OSによって違うらしい。スラッシュ以外に何があるのよ??)
というわけで、$dirには/Applications/MAMP/htdocs/wp-install-directory/wp-content/blogs.dir/2/filesみたいなのが入ります。

その後、保存したり配列にしたりしています。

まず、$dh = @opendir( $dir );@readdir( $dh )でディレクトリ内の要素を保存して、要素がディレクトリだったら$stack配列に保存、ファイルだったらunlink()で削除します。

これが、count( $stack )でこれが$indexよりも大きい間ずっとグルグル回るので、ファイルが全部削除されます。

このままだと、ファイルは消えてもディレクトリが残っているので以下のコードでディレクトリも削除します。

$stack = array_reverse( $stack );  // Last added dirs are deepest
foreach( (array) $stack as $dir ) {
	if ( $dir != $top_dir)
	@rmdir( $dir );
}

array_revers()で配列要素を逆にして、filesでなければ削除します。

パート4:ユーザメタ情報から削除ブログに関わる部分を削除

$wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = '{$blog_prefix}autosave_draft_ids'" );
$blogs = get_site_option( 'blog_list' );
if ( is_array( $blogs ) ) {
	foreach ( $blogs as $n => $blog ) {
		if ( $blog['blog_id'] == $blog_id )
			unset( $blogs[$n] );
	}
	update_site_option( 'blog_list', $blogs );
}

1行目でwp_usermetaテーブルからオートセーブされた下書きのIDを消しているみたいです。ここだけ外に出てるんですね。
その下のblog_listというのはよく分かりませんでしたがググると、WPMU時代に使われたものらしいです。

パート5:0番で手当てしたブログIDをもとに戻す

if ( $switch === true )
	restore_current_blog();

switch_to_blog()で操作対象ブログを変更したら、restore_current_blogで元に戻します。

やっていることまとめ

  1. 子ブログからユーザを取り除く
  2. wp_blogsテーブルに保存されたブログのステータスをdeletedに変更
  3. テーブルを削除($drop引数がtrueなら)
  4. 子ブログ用のメディアファイルを削除

以上です。

↓ プラグインを作る方々への本、書きました。 ↓

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です