リリースされてないけど WordPress 4.0 対応(になるかもしれない)プラグインを作ってみた

インストール時の言語選択
インストール時の言語選択

WordPress 4.0 の beta1 が先日発表されましたね。beta1以降は、バグフィックスのみとなり、新しい機能などは盛り込まれないので、よほど致命的なバグがない限り、ほぼこのままということになるでしょう。

どこらへんが変わったかは、おでさんの「WordPress 4.0 ファーストプレビュー」を見てもらうとして、なんと言っても、表示言語をインストール時に選択することで、言語専用のパッケージが基本的に不要になる(残るのかな)ことが一番の変わった点だと思います。

これは、インストール時にインストールするバージョンに対応した翻訳ファイルのリストをオンラインで取得して表示し、選択した言語の翻訳ファイルをインストール時にダウンロードして適用するような仕組みになっています。

select-wplang

また、「一般設定」画面において、マルチサイトには以前から存在していた「サイトの言語」の選択がシングルインストールにおいても利用できるようになり、インストール済みの言語を選べるようになっています。

で、1つ気になったのが、せっかくオンラインから翻訳ファイルを入手する仕組みがあるのに、インストール時しか言語を入手するインターフェースがなさそうだということです。

幸い 4.0 をインストールする際に利用される wp-admin/setup-config.php には、翻訳ファイルの取得やインストール方法も書いてあったので、これを参考に管理画面から翻訳ファイルを追加できるプラグインを作ってみました。

https://github.com/jim912/WP-Language-Plus

wp-lang-plus

added-langs

追加したい言語にチェックを入れて、下部の Install ボタンをクリックすると、翻訳ファイルを追加できるようになっています。

StaticPress でリンク切れの場合はファイルを作らないようにする

一旦、空のファイルを作って、直後に消すようにするという方法です。

add_filter( 'StaticPress::put_content', 'empty_404_content', 10, 2 );
add_action( 'StaticPress::file_put'   , 'unlink_empty_static_file' );
function empty_404_content( $content, $code ) {
	if ( $code == 404 ) {
		$content = '';
	}
	return $content;
}


function unlink_empty_static_file( $file_dest ) {
	if ( file_exists( $file_dest ) && is_file( $file_dest ) && filesize( $file_dest ) === 0 && is_writable( $file_dest ) ) {
		unlink( $file_dest );
	}
}

管理画面でジェネリコン(Genericons)を使ってみる #wpacja2013

    WordPress Advent Calendar 2013 も残すところあと僅か、しまっていきますぞ。

    bokettch-title

    ごく一部で多大な人気を誇る?プラグイン「Stop the Bokettch」ですが、レスポンシブ化され、メニューやツールバーのアイコンもWebフォント化された WordPress 3.8 では、ツールバーに表示されるべき画像アイコンが表示されず、ちょっとおまぬけな表示になってしまっていました。さらに、1px はみ出てるし。。(まさにぼけっち)

    old-tool-bar-in-bokettch

    また、モバイル端末の画面幅だとアイコン自体も非表示となってしまうことに。。

    old-mobile-admin-bar

    このままでは、さすがに情けないので、ようやく重い腰をあげて WordPress 3.8 に対応することにしたのでした。

    3.8 でアイコンが表示されないという情報は、前から得ていたので Webフォントで使えそうなものがないか探していたのですが、ジェネリコン(Genericons)に、ちょうどよいのがあったので、これを使うことに。
    ※ Genericons は、WordPress.com の運営元でもある Automattic が開発しているアイコンフォントで、デフォルトテーマの Twenty Thirteen や Twenty Fourteen でも採用されています。

    genericons-sample

    参考にしたのは、以下の2記事

    まずは、Genericons を表示できるようにフォントを読み込まなければなりません。が、これは結構簡単で、ダウンロードした Genericons に含まれている genericons.css を読み込むだけで大丈夫です。さらに、wp_enqueue_style を使っておけば、他のプラグインなどで同じCSSを利用する場合でも、重複して読み込んでしまうことを避けることができます。

    wp_enqueue_style( 'genericons', $this->genericons_dir_url . 'genericons.css', array(), '3.0.2' );
    

    CSS が読み込めたら、他のツールバーで表示されているアイコンを参考にCSSの指定を追加していきます。Genericons のサイトで表示したいアイコンをクリックして、「Copy CSS」をクリックすると、CSSで指定すべきコードをコピーすることができるようになっているので、これを利用するのが便利です。

    copy-css

    font-family プロパティで Genericons を指定し、さらにアイコンの表示位置やサイズを調整して完成。

    #wpadminbar #wp-admin-bar-bokettch-notice .ab-icon:before {
    	font-family: 'Genericons';
    	content: '\f446';
    	color: white;
    	font-size: 1.2em;
    	top: 1px;
    }
    

    さらに、モバイル端末でも表示されるようにメディアクエリーの指定を追加

    @media screen and (max-width: 782px) {
    	#wp-toolbar > ul > li#wp-admin-bar-bokettch-notice {
    		display:inline;
    	}
    	#wpadminbar #wp-admin-bar-bokettch-notice .ab-icon:before {
    		font-size: 1em;
    		top: 4px;
    	}
    }
    

    WordPress 3.7 以前では、元と同じ表示されるようにしたかったので、まずは、バージョン判別用の関数を作成

    private function is_admin_responsive() {
    	return version_compare( '3.8.*', get_bloginfo( 'version' ), '<=' );
    }
    

    最終的に、この条件分岐タグを使って、genericons.css の読み込みと、吐き出すCSSをバージョンによって変更するようにしています。詳しくは、ソース嫁

    修正後のツールバーの表示は、こんな感じ。

    updated-adminbar

    アップデートした Stop the Bokettch のダウンロードは、公式ディレクトリからどうぞ。

    こんな形で、プラグインのメンテナンスはそれなりに面倒なときもあったりしますが、自分の興味が尽きない限り続けていきたいと思います。そして、同じように地道にメンテを続けているプラグインの作者さんたちにも惜しみない賛辞を。

    さて、あとアップデートすべきプラグインの数は。。。

    orz

    明日の WordPress Advent Calendar 2013 は、Stop the Bokettch プラグインのタイトルにもなったぼけっちこと、うぇぶるじょんさんが、読まなければ損するとってもお役に立つ内容をお送りするぞ!?

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 );
				}
			}
		}
*/

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

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

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

stip-the-bokettch

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

スライドはこちら