Jetpack for WordPressの機能を拡張するプラグインを作ってみた #wpacja2013

20140811T0246+0900 追記:このコードはJetpack及びその他のプラグインやWordPress本体に予期せぬ動作を生じさせる恐れがありますので注意してください。より正確な実装については、今後、発信していきます。


WordPress Advent Calendar 2013の25日目担当の高橋です。いつも、Advent Calendarの公式サイトを更新してる人でもあります。

さて、この投稿の編集を始めたのが記録によれば2013年11月3日。はじめの一行だけ書いて、下書き保存したまま、約50日ほど放置。
「今年もAdvent Calendarやる!」って言い出した人間がネタ切れで悩んでましたw

  • 最近はOpenGLで3Dのゲームを書いていたりもするので、そういうネタならいくらでもあるけど、WordPressにまったく関係ない上に、著作権の微妙な問題でアウト。
  • WordPressのセキュリティについて書こうとしたら当然のごとく、先を越されて、
  • 先日リリースされたばかりの3.8での変更点とかをいろいろ書こうと思うと、すでに複数の人に書かれて、

というカンジで、ネタが全く決まらなくて涙目になりそうになりながら、東進の招待講習に通うことすでに半月。しかも、生徒用の画面は古めのInternet Explorer以外ではレイアウトが崩れるダメダメ仕様。そのうえ、風邪をうつされたっぽいし。

「もう、どうしろというんだ!」と逆ギレしそうになりつつ、開発環境を整理していたときに今年の5月にSend to Kindleプラグインのバグについて書いた時のコードの残骸を大量に発掘。これ、使いまわそう!ではなく、有効活用しよう!」というわけで、JetpackのSharingに自分の好きなサービスを好き勝手追加するプラグインを大急ぎ(4日)で作りました。


前提

  • JetpackがインストールされているWordPress
  • テキストエディタ
  • ZIP形式が扱えるファイル圧縮ソフト
  • 多少のPHPプログラミングの知識(クラスが使える程度で十分)
  • wordpress.orgが快適に見られるインターネット回線

コードを書く前に、ほかのプラグインを研究

簡単なプラグインの作り方は大石さんジャスティスさんがWordPress Advent Calendar 2013の中で書いてくださってますし、ほかにも資料はいっぱいあると思うので割愛します。

プラグインの動作として必要なことは

  1. Jetpackを見つけ出す
  2. Sharing Servicesに強制的に追加する

の2つがメインです。

まず、1つ目について、Jetpackが見つからなかった場合にはエラーメッセージでも出してユーザに教えてあげることにします。
Jetpackと同じくAutomatticからリリースされている超有名プラグインAkismetのソースコードを眺めてみると、

function akismet_admin_menu() {
	if ( class_exists( 'Jetpack' ) ) {
		add_action( 'jetpack_admin_menu', 'akismet_load_menu' );
	} else {
		akismet_load_menu();
	}
}

function akismet_load_menu() {	
	if ( class_exists( 'Jetpack' ) ) {
		add_submenu_page( 'jetpack', __( 'Akismet' ), __( 'Akismet' ), 'manage_options', 'akismet-key-config', 'akismet_conf' );
		add_submenu_page( 'jetpack', __( 'Akismet Stats' ), __( 'Akismet Stats' ), 'manage_options', 'akismet-stats-display', 'akismet_stats_display' );
	} else {
		add_submenu_page('plugins.php', __('Akismet'), __('Akismet'), 'manage_options', 'akismet-key-config', 'akismet_conf');
		add_submenu_page('index.php', __('Akismet Stats'), __('Akismet Stats'), 'manage_options', 'akismet-stats-display', 'akismet_stats_display');
	}
}

Jetpackというクラスが存在するかどうかで区別するなんていう便利な方法があるようなので、是非使わせてもらいましょう。ついでに、管理画面でJetpackとメニューを統合する方法はadd_submenu_page()の$parent_slugに’jetpack’を指定すればいいことが分かりました。
(ちなみにAmazonのSend to Kindleプラグインはwp_get_active_and_valid_plugins()関数を使って頑張っているようですが、マルチサイト環境下では処理が煩雑になるため、おススメできません)

Jetpackの各モジュールの動作状況をうまく取得する方法はどうしても思いつかなかったので今後の課題にさせてください。とりあえず、Send to Kindleの方法を乗せておきます。

if ( ! class_exists( 'Sharing_Source' ) ) {
	include_once( preg_replace( '/jetpack\.php$/i', 'modules/sharedaddy/sharing-sources.php', reset( $share_plugin ) ) );
}
add_filter( 'sharing_services', array( 'Share_Kindle', 'inject_service' ) );
add_action( 'admin_notices', array( 'Share_Kindle', 'jetpack_message') );

Codexにもある通り、Share_Kindleはクラス名を指しています。
ここでは架空の共有サービスを例に説明しますが、便宜上、クラス名はShare_Aigisとでも名付けておきましょう。
共有サービスを追加するためのテンプレートのようなShare_Sourceというクラスを継承することで追加が簡単になるので積極的に使っていきます。Share_Sourceクラスの定義は長いのでjetpack/modules/sharedaddy/sharing-sources.phpを確認してください。

クラスの継承って、普段はそこまでのメリットを感じていない方も多いかもしれませんが、この定義のおかげで、たとえばGoogle+への共有は次のように短くまとまっています。

class Share_GooglePlus1 extends Sharing_Source {
	var $shortname = 'googleplus1';
	private $state = false;

	public function __construct( $id, array $settings ) {
		parent::__construct( $id, $settings );
		if ( 'official' == $this->button_style )
			$this->smart = true;
		else
			$this->smart = false;
	}

	public function get_name() {
		return __( 'Google', 'jetpack' );
	}

	public function has_custom_button_style() {
		return $this->smart;
	}

	public function get_display( $post ) {
		$share_url = $this->get_share_url( $post->ID );
		if ( $this->smart ) {
			return '<div class='googleplus1_button'><div class='g-plus' data-action='share' data-annotation='bubble' data-href='' . esc_url( $share_url ) . ''></div></div>';
		} else {
			return $this->get_link( get_permalink( $post->ID ), _x( 'Google', 'share to', 'jetpack' ), __( 'Click to share on Google+', 'jetpack' ), 'share=google-plus-1', 'sharing-google-' . $post->ID );
		}
	}

	public function get_state() {
		return $this->state;
	}

	public function process_request( $post, array $post_data ) {
		省略
	}

	public function display_footer() {
		省略
	}

	public function get_total( $post = false ) {
		省略
	}
}

それぞれの関数の意味もShare_Sourceクラスを読めば大体は理解することができます。「if ( $this->smart )」という判定は、ちょっとわかりずらいかもしれないですが、公式ボタン or それ以外の判定の様です。

やっと、コーディング開始

これだけ、ほかのプラグインのデータを集めれば十分でしょう。
将来的に、管理画面から何か設定するかもしれないので、空の管理用ページも作っておきます。
管理画面にメニューを追加する方法はCodexの方が詳しいでしょうから、そっちを。

まずは、プラグインのメインとなるファイルです。あとあとの拡張性も考えて、実際の処理はこれ以外のファイルに書いていきます。

<?php
/*
Plugin Name: Aigis Shortener for WordPress
Plugin URI: http://example.org/extend/plugins/aigis/
Description: This plugin extends Jetpack Sharing Service.
Version: 0.1-dev
Author: Daisuke
Author URI: http://www.extendwings.com
*/

if(!function_exists('add_action')) {
	echo 'Hi there!  I\'m just a plugin, not much I can do when called directly.';
	exit;
}

include_once(dirname( __FILE__ ) . '/jetpack.php');

if(is_admin())
	include_once(dirname( __FILE__ ) . '/admin.php');

?>

続いて、Jetpackに強引にサービスを追加する部分は、見ればわかると思いますが、Jetpackのモジュールの稼働状況をしっかり判定する方法が分からなかったので、4行目の部分で不具合が出ない範囲で強引に追加しています。
process_request()関数の中を弄るだけで大抵のサービスには対応できると思います。一番最後のjetpack_message()関数はJetpackが有効になっていた場合の管理画面のID “jetpack_page_aigis-setting”にアクセスしたときにAdmin Noticeを出力します。

<?php
if(!class_exists('Jetpack')) {
	if(!class_exists('Sharing_Source')) {
		include_once(WP_PLUGIN_DIR.'/jetpack/modules/sharedaddy/sharing-sources.php');
	}
	add_filter('sharing_services', array('Share_Aigis', 'inject_service'));
	add_action('admin_notices', array('Share_Aigis', 'jetpack_message'));
	
} else {
	return;
}

class Share_Aigis extends Sharing_Source {
	var $shortname = 'aigis';

	public function __construct($id, array $settings) {
		parent::__construct($id, $settings);

		if ('official' == $this->button_style)
			$this->smart = true;
		else
			$this->smart = false;
	}

	public function get_name() {
		return __('Aigis', 'aigis');
	}

	public function process_request($post, array $post_data) {
		// Record stats
		parent::process_request($post, $post_data);

		$aigis_url = esc_url_raw('http://example.com/?url=' . rawurlencode($this->get_share_url($post->ID)));
		wp_redirect($aigis_url);
		exit;
	}

	public function get_display($post) {
		return $this->get_link(get_permalink($post->ID), __('Aigis', 'aigis'), __('Click to shorten with Aigis', 'aigis'), 'share=aigis');
	}

	function display_footer() {
		$this->js_dialog($this->shortname, array('width' => 768, 'height' => 450));
	}

	public function inject_service ( array $services ) {
		if ( ! array_key_exists( 'aigis', $services ) ) {
			$services['aigis'] = 'Share_Aigis';
		}
		return $services;
	}

	public function jetpack_message() {
		if ( 'jetpack_page_aigis-setting' != get_current_screen()->id ) {
			return;
		}
		echo '<div class='updated'><p>';
		_e('It looks like you have Jetpack installed! Go to the settings screen for the sharebar and you will find an option for adding the Shorten with Aigis Button next to your other share buttons. Come back to this page to customize the text and icon.', 'aigis');
		echo '</p></div>';
	}

}
?>

最後に、何もない管理画面を表示させるためのスカスカのファイルです。

<?php

class Aigis {
	function __construct() {
		add_action('admin_menu', array(&$this, 'aigis_admin_menu'), 9);
	}
	function aigis_admin_menu() {
		if(class_exists('Jetpack') ) {
			add_action('jetpack_admin_menu', array(&$this, 'aigis_load_menu'));
		} else {
			$this->aigis_load_menu();
		}
	}
	
	
	function aigis_load_menu() {
		if( class_exists('Jetpack') ) {
			add_submenu_page(
				'jetpack',
				'Aigis',
				'Aigis',
				'manage_options',
				'aigis-setting',
				array(&$this,'conf')
			);
		} else {
			add_options_page(
				'Aigis Settings',
				'Aigis',
				'manage_options',
				'aigis-setting',
				array(&$this,'conf')
			);
		}
	}
	function conf() {
	?>
		<div class='wrap'>
			<h2>Aigis Settings</h2>
		</div><!-- /.wrap -->
		<?php
	}
}

function aigis_loader() {
	$instance = new Aigis;
}
add_action('init', 'aigis_loader');
?>

で、結局何をするためにこれつくったの?

そう、これだけのことを実装するのであればJetpackが任意のサービスを追加する機能をすでに提供してくれています。しかも、こっちはCSSすらまともに書いていないので、ボタンに画像は表示されないですし、完全に負けてます。

でも、なぜこんなものを作ったのかというと、①外部のAPIを叩いて結果を取得し、②その結果を共有するということは、Jetpackのカスタム機能では現状、できないからです。(処理がめんどくさくなるので今後も対応予定はないと思いますが。)

というわけで、自己満足のためにどこに需要があるのだかよくわからないプラグインを作ってみましたが、まだまだ改善の余地が山ほどあるので、公式プラグインディレクトリに登録するかどうかは完全に未定です。(需要がないからしないかもw)

最後に

今年はこれがラストの投稿です。今、周囲で発生した問題の対応をしているので、当分は出現頻度が減りますが、ちゃんと生存していますのでご安心を!

それでは、また、どこかでお会いしましょう!
Happy Holidays!

“Jetpack for WordPressの機能を拡張するプラグインを作ってみた #wpacja2013” への2件の返信

  1. 東進衛星予備校のシステムは確かIE9以上で見たらレイアウト崩れが発生するのであえてIE8止、良くてIE9止だったはずです。

    いい加減システムやデザイン変えろよって思うのですが、あのシステムってたしか教員とか保護者も全部流用していたはずで…。。。

    結果を言ってしまえば東進が悪いのですが、まぁあと10年ぐらいはあれで行くんじゃないんですかね、Macな人とかCentOSな人を敵に回してでも多分あのデザインをWin7サポートが終わるまでかわかりませんが、そのままでいると思いますよ。
    スマホ用のアプリ作る暇あるんだったらもう少しやることがあるんじゃないかって思いますが。

    1. IE対応を頑張るならせめてIEの最新版ぐらいはしっかりサポートしてほしいですね。

      それに、スマホアプリを作るなとは言いませんが、できればiOSのデザインっぽいものをAndroid上で再現するようなことはしないでほしかったかな(笑)。って感じです。

コメントを残す

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