WP Post Branches をカスタム投稿タイプに対応させる方法

WP Post Branches という便利なプラグインをご存じでしょうか。公開済みの投稿などの下書きの複製を作成し、公開記事には影響せずに下書き状態での更新や予約投稿機能を使って、時限更新ができるという優れもので、私も度々愛用しています。

wp-post-branches

ただし、ご覧のようにまだプラグインのヘッダー画像がないので、我こそはと思われる方は、ぜひ確認ちゃんに、いきなり送りつけてあげてください。素材がないよという方は、ここらへんをご自由に使っていただいて構いません。

ちなみに、WP Post Branches の仕組みを簡単に説明すると

  1. 公開済みの投稿を複製し下書き状態の投稿(ブランチ)を作る
  2. 公開中の投稿はそのままの状態で、ブランチを編集
  3. ブランチの公開時に、ブランチの内容を元の投稿にマージし、ブランチは削除

といった感じとなります。

branch-flow

固定ページを更新するような場合には、非常に便利なのですが、現バージョン(2.2)では、カスタム投稿タイプに対応できておらず、ブランチの作成はできるものの、公開時に元の記事にマージされず、そのまま公開されてしまいます。これは、プラグインのブランチ記事の公開処理が投稿と固定ページの時のみにしか実行されず、カスタム投稿タイプでは、通常の公開処理になってしまうためです。

add_action( 'publish_page', 'wpbs_save_post', 9999, 2 );
add_action( 'publish_post', 'wpbs_save_post', 9999, 2 );

カスタム投稿タイプに対応させるためには、上記フックの publish_xxxxxx の xxxxx をカスタム投稿タイプのスラッグにすればよいので、functions.php に

add_action( 'publish_book', 'wpbs_save_post', 9999, 2 );

などと、追記すれば book 投稿タイプでも問題なく利用できます。

ただし、この記述だと投稿タイプ毎に記述しなくてはならないので、手間になりがちです。
追加したカスタム投稿タイプを全てサポートさせるのであれば、以下のような記述となります。init で行っているのは、register_post_type の実行タイミングとして、init フック以降が推奨されているためです。

function add_wpbs_save_post_hooks() {
	// デフォルト以外で、show_uiがtrue(管理画面が有効)となっている、追加されたカスタム投稿タイプを取得
	$additional_post_types = get_post_types( array( '_builtin' => false, 'show_ui' => true ) );
	foreach ( $additional_post_types as $post_type ) {
		// 追加されたカスタム投稿ごとにフックを追加
		add_action( 'publish_' . $post_type, 'wpbs_save_post', 9999, 2 );
	}
}
add_action( 'init', 'add_wpbs_save_post_hooks', 9999 );

ただし、せっかくブランチができるのですから、できればプラグインのデフォルトで、カスタム投稿タイプに対応してほしいですよね。某案件では、プラグイン名を変更した上で以下のように変更してみました。

//add_action( 'publish_page', 'wpbs_save_post', 9999, 2 );
//add_action( 'publish_post', 'wpbs_save_post', 9999, 2 );
add_action( 'transition_post_status', 'wpbs_save_post', 9999, 3 );
function wpbs_save_post( $new_status, $old_status, $post ) {

	if ( $post->post_status != 'publish' ) { return; }
	if ( $org_id = get_post_meta( $post->ID, '_wpbs_pre_post_id', true ) ) {
		// post
		$new = array(
			'ID' => $org_id,
			'post_author' => $post->post_author,
			'post_date' => $post->post_date,
			'post_date_gmt' => $post->post_date_gmt,
			'post_content' => $post->post_content,
			'post_title' => $post->post_title,
			'post_excerpt' => $post->post_excerpt,
			'post_status' => 'publish',
			'comment_status' => $post->comment_status,
			'ping_status' => $post->ping_status,
			'post_password' => $post->post_password,
//			'post_name' => $post->post_name,
			'to_ping' => $post->to_ping,
			'pinged' => $post->pinged,
			'post_modified' => $post->post_modified,
			'post_modified_gmt' => $post->post_modified_gmt,
			'post_content_filtered' => $post->post_content_filtered,
			'post_parent' => $post->post_parent,
			'guid' => $post->guid,
			'menu_order' => $post->menu_order,
			'post_type' => $post->post_type,
			'post_mime_type' => $post->post_mime_type
		);
		wp_update_post( apply_filters( 'wpbs_draft_to_publish_update_post', $new ) );


		//postmeta
		$keys = get_post_custom_keys( $post->ID );

		$custom_field = array();
		foreach ( (array) $keys as $key ) {
			if ( preg_match( '/^_feedback_/', $key ) )
				continue;

			if ( preg_match( '/_wpbs_pre_post_id/', $key ) )
				continue;

			if ( preg_match( '/_wp_old_slug/', $key ) )
				continue;
				
			$key = apply_filters( 'wpbs_draft_to_publish_postmeta_filter', $key );

			delete_post_meta( $org_id, $key );
			$values = get_post_custom_values($key, $post->ID );
			foreach ( $values as $value ) {
				add_post_meta( $org_id, $key, $value );
			}
		}


		//attachment
//		$args = array( 'post_type' => 'attachment', 'numberposts' => -1, 'post_status' => null, 'post_parent' => $org_id );
//		$attachments = get_posts( $args );
//		if ($attachments) {
//			foreach ( $attachments as $attachment ) {
//				wp_delete_post( $attachment->ID );
//			}
//		}

		$args = array( 'post_type' => 'attachment', 'numberposts' => -1, 'post_status' => null, 'post_parent' => $post->ID ); 
		$attachments = get_posts( $args );
		if ($attachments) {
			foreach ( $attachments as $attachment ) {
				$new = array(
					'post_author' => $attachment->post_author,
					'post_date' => $attachment->post_date,
					'post_date_gmt' => $attachment->post_date_gmt,
					'post_content' => $attachment->post_content,
					'post_title' => $attachment->post_title,
					'post_excerpt' => $attachment->post_excerpt,
					'post_status' => $attachment->post_status,
					'comment_status' => $attachment->comment_status,
					'ping_status' => $attachment->ping_status,
					'post_password' => $attachment->post_password,
					'post_name' => $attachment->post_name,
					'to_ping' => $attachment->to_ping,
					'pinged' => $attachment->pinged,
					'post_modified' => $attachment->post_modified,
					'post_modified_gmt' => $attachment->post_modified_gmt,
					'post_content_filtered' => $attachment->post_content_filtered,
					'post_parent' => $draft_id,
					'guid' => $attachment->guid,
					'menu_order' => $attachment->menu_order,
					'post_type' => $attachment->post_type,
					'post_mime_type' => $attachment->post_mime_type,
					'comment_count' => $attachment->comment_count
				);
				$new = apply_filters( 'wpbs_pre_draft_to_publish_attachment', $new );
				$attachment_newid = wp_insert_post( $new );
				$keys = get_post_custom_keys( $attachment->ID );

				$custom_field = array();
				foreach ( (array) $keys as $key ) {
					$value = get_post_meta( $attachment->ID, $key, true );

					delete_post_meta( $org_id, $key );
					add_post_meta( $org_id, $key, $value );
				}
			}
		}


		//tax
		$taxonomies = get_object_taxonomies( $post->post_type );
		foreach ($taxonomies as $taxonomy) {
			$post_terms = wp_get_object_terms($post->ID, $taxonomy, array( 'orderby' => 'term_order' ));
			$post_terms = apply_filters( 'wpbs_pre_draft_to_publish_taxonomies', $post_terms );
			$terms = array();
			for ($i=0; $i<count($post_terms); $i++) {
				$terms[] = $post_terms[$i]->slug;
			}
			wp_set_object_terms($org_id, $terms, $taxonomy);
		}

	wp_delete_post( $post->ID );
	wp_safe_redirect( admin_url( '/post.php?post=' . $org_id . '&action=edit&message=1' ) );
	exit;
	}
}

あと、ブランチの都度に投稿にひも付いたメディアが増えてしまうのがいやだったので、wpbs_pre_post_update 関数内の、メディアを複製する処理をコメントアウトして利用しています。

/*
		//attachment
		$args = array( 'post_type' => 'attachment', 'numberposts' => -1, 'post_status' => null, 'post_parent' => $id ); 
		$attachments = get_posts( $args );
		if ($attachments) {
			foreach ( $attachments as $attachment ) {
				$new = array(
					'post_author' => $attachment->post_author,
					'post_date' => $attachment->post_date,
					'post_date_gmt' => $attachment->post_date_gmt,
					'post_content' => $attachment->post_content,
					'post_title' => $attachment->post_title,
					'post_excerpt' => $attachment->post_excerpt,
					'post_status' => $attachment->post_status,
					'comment_status' => $attachment->comment_status,
					'ping_status' => $attachment->ping_status,
					'post_password' => $attachment->post_password,
					'post_name' => $attachment->post_name,
					'to_ping' => $attachment->to_ping,
					'pinged' => $attachment->pinged,
					'post_modified' => $attachment->post_modified,
					'post_modified_gmt' => $attachment->post_modified_gmt,
					'post_content_filtered' => $attachment->post_content_filtered,
					'post_parent' => $draft_id,
					'guid' => $attachment->guid,
					'menu_order' => $attachment->menu_order,
					'post_type' => $attachment->post_type,
					'post_mime_type' => $attachment->post_mime_type,
					'comment_count' => $attachment->comment_count
				);
				$new = apply_filters( 'wpbs_pre_publish_to_draft_attachment', $new );
				$attachment_newid = wp_insert_post( $new );
				$keys = get_post_custom_keys( $attachment->ID );

				$custom_field = array();
				foreach ( (array) $keys as $key ) {
					$value = get_post_meta( $attachment->ID, $key, true );

				add_post_meta( $attachment_newid, $key, $value );
				}
			}
		}
*/

あと、希望を言えば、管理画面の記事一覧表示で、ブランチかどうか、どの記事のブランチかなどの情報が表示されると、もっと使いやすくなるのではーと思ってます。

WordPressで特定の関数から呼ばれたときだけフックをかける裏技!?

フォーラムの 「カテゴリのよく使うものを削除」の回答にて使ったものなのだけど、特定の関数から呼ばれたときだけフックさせたい時ってありませんか?私は結構あります。

自作のプラグインとか、テーマであれば、直前に add_filter して、直後に remove_filter することも出来ますが、コアファイルや配布されているプラグイン・テーマだとそうもいかないですよね。

そんなにっちもさっちも行かないようなケースの場合、php の debug_backtrace 関数を使って、呼び出し元に特定の関数名が存在しているかのチェックを行えばなんとかなるということに気がつきました。

ただし、本来デバッグ用のものですから、こういうことに用いていいのかどうかはわかりません(爆
※ 詳しい方コメントください。

function exclude_default_category_from_popular_list( $args, $taxonomies ) {
	if ( in_array( 'category', $taxonomies ) ) {
	$traces = debug_backtrace(); // 呼び出し元を取得する
		foreach ( $traces as $trace ) {
			// 関数名を照合
			if ( isset( $trace['function'] ) && $trace['function'] == 'wp_popular_terms_checklist' ) {
				// get_terms の引数に、デフォルトカテゴリーの除外を追加
				$args['exclude'] = array( get_option( 'default_category' ) );
				// 合致したらループをその場で抜ける
				break;
			}
		}
	}
	return $args;
}
add_filter( 'get_terms_args', 'exclude_default_category_from_popular_list', 10, 2 );

WordCamp Kobe 2013中に作ったプラグイン「Stop the Bokettch」を公開

WordCamp Kobe 2013 のお昼休みの会話で突然作ることになったプラグイン「Stop the Bokettch」を公開します。
プラグインを有効化していると、表示設定の検索エンジンがサイトをインデックスできないようにするにチェックが入っている場合に、管理バー上にアラート表示がなされます。

stip-the-bokettch

※ 2013.6.18. アップデートしました。
※ 2013.6.26. プラグイン公式ディレクトリで公開しました。

スライドはこちら

WordPress の管理画面にユーザー権限グループに応じた class を出力する

管理画面の body_class に相当する admin_body_class にフィルターフックをかけて、ユーザーの権限グループを含める方法

出力されるソースの修正ができない場合、これを使ってCSSで非表示にすることができますね。
CSSの読み込みは、admin_initなどで、wp_enqueue_style すればオッケー。

admin_body_class で渡されてくるものは文字列形式なので、扱いがちょっとめんどくさいのだけど。

function add_user_role_class( $admin_body_class ) {
	global $current_user;
	if ( ! $admin_body_class ) {
		$admin_body_class .= ' ';
	}
	$admin_body_class .= 'role-' . urlencode( $current_user->roles[0] );
	return $admin_body_class;
}
add_filter( 'admin_body_class', 'add_user_role_class' );

出力結果はこんな感じ

<body class="wp-admin wp-core-ui no-js  role-administrator index-php auto-fold admin-bar branch-3-5 version-3-5 admin-color-fresh locale-ja no-customize-support">

role-administrator で判別可能ですね。

非表示にする以外にもいろんな使い方ができそうな気がするので、こんな方法あるよってーのがあったら こそっと 教えてくださいw

カスタム投稿タイプを管理する Custom Post Type Maker を試してみた

管理画面からカスタム投稿タイプやら、カスタム分類やらを設定するために有名なプラグインといえば、Custom Post Type UI だと思いますが、デバッグモードにすると notice エラーが出たり(あまり人のこと言えない。。)、アップデートもしばらくされておらず、3.5 で追加になったパラメーターの指定ができなかったり、UIが今ひとつ使いにくいなど、今後にちょっと心配があり、代替となるプラグインで良いものないかなと探していたところ、Custom Post Type Maker の存在を知り、試してみました。

結論から言うと、現バージョンでは、使用すべきではないというのが私の判断です。

カスタム投稿タイプなどを新しく追加した場合、表示されるURLが新たに加わりますが、パーマリンク使用時にこのURLを表示するためには、リライトルールというマッピングデータを更新してあげる必要があります。

Custom Post Type Maker は、Webページ表示時にこのリライトルールを毎回再生成しているのですね。こうすることによって、新しくカスタム投稿タイプやカスタム分類を追加したときにも、自動的に表示されるようになるのですが、このリライトルールの再生成は、それなりに重い処理で表示の際に毎回やるようなことではありません。これは、Codex の flush_rewrite_rules の説明にもしっかりと注意書きとして載っています。

Important: Flushing the rewrite rules is an expensive operation, there are tutorials and examples that suggest executing it on the ‘init’ hook. This is bad practice. Instead you should flush rewrite rules on the activation hook of a plugin, or when you know that the rewrite rules need to be changed ( e.g. the addition of a new taxonomy or post type in your code ).

WordPress は、3.3 で、リライトルールの改善を行い、この処理も大幅に軽減はされていますが、毎回行うようなことではありません。

私の判断ではありますが、この点が改善されるまで、利用すべきでないプラグインととらえています。