スクリーンショット 2014-07-24 16.22.59

WordPressの機能を使って外部サイトのRSSからコンテンツを持ってきて表示する2つの方法

複数のサイトを管理していて、AというサイトにBやCのサイトのコンテンツを表示したいということがありますよね?そうした場合に、AがWordPressであれば、わりと簡単に実現できますのでその方法を書きます。

2つ方法がありまして、2つとも簡単です。

ひとつは、SimplePie というパーサー(Parse≒構文解析)がWordPressのコアに同梱されているので、それを利用する方法です。

もうひとつは、WordPressの内部APIのひとつであるHTTP APIを使う方法です。

このように、WordPressはいろいろな機能を持っており、そうしたことを知っていれば、今までよりも難しいことができたり、同じものを作るのにも、時間を節約したり確実性を上げたりということが可能です。

こうした、WordPressの力を最大限に活かす方法については、『サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル』に詳しく書かれていますので買いましょう!

1. Simple Pieを利用する方法

2行目のfeedurlには、RSSのURLを入れます。
そして、WordPressの関数、fetch_feed()に渡すことで、アイテムを取得してくれますので、ループで表示を行います。

include_once( ABSPATH . WPINC . '/feed.php' );
$feeduri = 'http://example.com/feed/uri/here';

$rss = fetch_feed( $feeduri );

if ( ! is_wp_error( $rss ) ):

	$maxitems = $rss->get_item_quantity( 4 ); // 4件表示
	$rss_items = $rss->get_items( 0, $maxitems );

endif;

if ( !$rss_items ):
	echo '<p><a href="http://example.com/" target="_blank">おもしろブログだよ。読みに来てね&#9825;</a></p>';
else:
	 foreach ( $rss_items as $i ) {
		 ?>
		 <li><a href="<?php echo esc_url( $i->get_permalink() ); ?>"><?php echo esc_html( $i->get_title() ); ?></a></li>
		 <?php
	 }
endif;

この方法を使えば、simplexml_load_file()などの関数を使ってエラーの想定をしたりなどはしなくても大丈夫です。

また、外部サイトへのアクセスと情報の取得は毎回行っていると表示も遅くなりますし、よそのサイトにも迷惑になりますが、この方法であればデフォルトで12時間キャッシュしてくれます。キャッシュの時間の変更は、フックを通して行います(過去に書いています)。

ん?フックってなんだ?という方は、『サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル』に詳しく書かれていますので買いましょう(二度目)!

2. HTTP APIを利用する方法

次の方法です。

WordPressのHTTP APIというのは、外部サイトとの通信を助けてくれるものと理解すれば大丈夫だと思います。

WordPressは色々な環境にインストールされる上に、情報をとりにいく外部のサイトの環境も違います。相手側のサーバーによっては、通信できる方法が限られていたりもします。そうした外部サイトとの通信を行う際に問題になる(面倒くさい)色々な問題を、まるっと任せてしまうことができます。

今回は、一番シンプルだと思われる、wp_remote_get()関数を使ってみたいと思います。基本的には上記とほぼ同じなので、上記の本の共著者である岡本さんのキャッシングも含めたコードを使わせていただきます。

functions.phpなどに以下を書いておきます。

function nskw_transient_remote_get( $url, $expiration = 43200 ) {

	$transient = 'remote_get-' . md5($url);

	if ( ! ($response_body = get_transient($transient)) ) {

		$response = wp_remote_get($url);

		if( !is_wp_error($response) && $response["response"]["code"] === 200 ) {

			$response_body = $response["body"];
			set_transient( $transient, $response_body, $expiration );

		} else {

			$response_body = false;

		}
	}

	return $response_body;

}

そして、結果を表示したいところに、下記を記述します。テーマにガンと書いてしまってもOKだと思います。キャッシングに使われているのは、Transient APIというデータをキャッシングしておいてくれる仕組みです。これも『サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル』に詳しく書かれてい(以下略

$rss_url = "http://example.com/path/to/feed/";

$responce = nskw_transient_remote_get( $rss_url );

if ( $responce ) {

	$res_obj = simplexml_load_string( $responce );
	$bs = $res_obj->channel;

	foreach ( $bs->item as $b ) {
		?>
		<li><a href="<?php echo esc_url( $b->link ); ?>"><?php echo esc_html( $b->title ); ?></a></li>
		<?php
	}

}

いかがでしょうか?とってもシンプルですよね!

両方の方法に出てくる、esc_url() や esc_html というのは、セキュリティに関する関数なのですが、こうしたセキュリティについても、(以下略

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

WordPressの管理画面の実行フローを視覚化してみる

WordPressの実行フローを視覚化してみる | Simple Colors の真似っ子です。

テーマが読み込まれる時との違いや共通点での面白ポイントは、

  • コアファイルが作るページの場合、そこにファイルがある
    (wp-admin/post-new.phpなど、そこにファイルが実際にあります。
    テーマの場合は、リライトの色々が発動して、URLによって指定されるパスに、
    そのファイルはありませんね。なので、index.phpが大事)
  • テーマ側の index.php のような位置づけにあるのが、wp-admin/admin.phpファイル
    • プラグインが作るページの場合でも、トップレベルならwp-admin/wp-admin.php?page=hogehogeのような形でadmin.phpは読み込まれ、
    • サブページとして登録される場合でも、wp-admin/options-general.php?page=hogehogeのような形でそこにあるファイルが読み込まれ、そこからwp-admin/admin.phpが読み込まれる

    という形になっている。

といったところかと思います。

クリックで拡大

クリックで拡大

以上です。

大曲さんの「WordPressの実行フローを視覚化してみる」のページ

大曲さんの「WordPressの実行フローを視覚化してみる」のページ

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

WordPressの管理画面の特定のページを指定して一番早いタイミングのフック

通常、管理画面の表示時にリダイレクトがしたいなど、なんらかの出力の前に処理を追加したい場合には、add_action( ‘admin_init’, ‘func_name’ ); などを利用しますが、その場合、管理画面内のすべてのページで発火(Fire!)してしまいます。特定のページのみでフックしたいときにはどのような方法があるのかをまとめました。

デフォルトのページの場合は、do_action( 'load-' . $pagenow );でシンプル

do_action( ‘load-’ . $pagenow ); は、デフォルトで存在する管理画面ページ(つまり、プラグインによって追加されたのではないページ)を表示する際に使えるフックです。

たとえば、「あなたのプロフィール」ページの場合には、URLがwp-admin/profile.phpなので、以下のように書きます。

add_action( 'load-profile.php', 'nskw_func_name' );

プラグインによって追加されたページの場合は、do_action( 'load-' . $page_hook );ですごく分かりにくい

wp-admin/admin.php?page=nskwというトップレベルに追加されたページの場合には、

add_action( 'load-toplevel_page_nskw', 'nskw_func_name' );

トップレベルではないページの場合は、

add_action( 'load-親_page_nskw_settings', 'nskw_func_name' );

などになります。

つまり、add_action( 'load-xxxx', '' );のxxxxの部分に何かを入れましょうということなのですが、このxxxxがすこぶる分かりにくいです。宮内さんが、init近くではなく特定のページでのみスクリプトやスタイルシートをenqueueするときの方法を書いてくれていますので、そちらを参照してください。

このload-xxxxのxxxx部分(hook-suffixという)の作られ方についてのルールがコアのコメントに入っていましたので、一応翻訳しておこうと思います。

コメントは以下(3.9.1ではwp-admin/admin.phpの180行目)

/**
* Fires before a particular screen is loaded.
*
* The load-* hook fires in a number of contexts. This hook is for plugin screens
* where a callback is provided when the screen is registered.
*
* The dynamic portion of the hook name, $page_hook, refers to a mixture of plugin
* page information including:
* 1. The page type. If the plugin page is registered as a submenu page, such as for
* Settings, the page type would be ‘settings’. Otherwise the type is ‘toplevel’.
* 2. A separator of ‘_page_’.
* 3. The plugin basename minus the file extension.
*
* Together, the three parts form the $page_hook. Citing the example above,
* the hook name used would be ‘load-settings_page_pluginbasename’.
*
* @see get_plugin_page_hook()
*
* @since 2.1.0
*/

手抜き翻訳は以下。

/**
* 特定の画面が読み込まれる前に処理が行われます。
*
* load-* フックは複数の文脈をふまえて白化します。このフックはプラグインによって追加されたページのためのもの。
* where a callback is provided when the screen is registered.
*
* フック名の動的に作られる部分($page_hook)は、以下のプラグインの情報によってできあがります。
*
* 1. ページのタイプ。何かの(たとえばSettingsの)サブページならsettingだし、トップレベルなら、toplevel
* 2. ‘_page_’というセパレータ
* 3. plugin basename から the file extension をひいたもの
*
* この3でできあがります。上記の例では、
* ‘load-settings_page_pluginbasename’ になるでしょう。
*
* @see get_plugin_page_hook()
*
* @since 2.1.0
*/

読んでもまだ分かりにくいですね。。。上記で紹介している宮内さんのページの、hook-suffix表示用プラグインを使うのがいいと思います。

僕はこのポストに気がつく前は、wp-admin/admin.phpの中で$page-hookとかをvar_dumpして値を取得していました。

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

スクリーンショット 2014-06-26 11.53.41

WordPressの翻訳用のファイルがどうやって読み込まれているのかを調べてみた

WordPressは、世界中で使われていますが、翻訳ファイルと翻訳者のおかげで、コアファイルを触ることなく、色々な言語で使うことができてとっても便利!

テーマやプラグインの制作者が、翻訳ファイルなどの仕組みをちゃんと整えて、色々な国の言葉に簡単に翻訳できるようにすることを国際化、逆に、そうした仕組みを利用して翻訳を行うことを地域化といいます。

ちなみに国際化は、internationalizationinの間に18文字あることからi18nと書き、地域化はlocalizationで、lnの間に10文字あることからL10nと書きます(Lは小文字にすると数字の1と見間違えるので大文字)。面白いですよね。

テーマやプラグインの国際化や地域化の具体的で詳しくて分かりやすくて自分もやってみたくなってしまうやり方・方法については、2014年7月18日に発売されることになったプラグインの開発方法を説明する書籍に書かれています(宮内さん岡本さん三好さんと僕の共著です)。

今回は、ちょっとソースを見て、翻訳ファイルがどういうルールで読み込まれているのかを調べてみましたので、共有したいと思います。

テキストドメイン、ロケール、パス

前提として、テキストドメインとロケールとパスについて。

 日本語  英語  内容
 テキストドメイン  textdomain  翻訳すべき文字列を取得するために必要な、テーマやプラグインごとにユニークな識別用のIDみたいなもの
 ロケール  locale  en_USは英語でアメリカ、jaは日本語、といったように言語と地域を組み合わせたもの。WPLANGで指定するのでおなじみ。
 パス  path  翻訳ファイルがどこにあるのかを示します。

WordPressは上記の3つを使って、翻訳ファイルを読み込んで翻訳します。プラグインで言うと、

  1. textdomain: どのプラグイン用の、
  2. locale: 何語用のファイルが、
  3. path: どこに置いてあるのか

がWordPressに伝わればいいということになります。このうち、localeについては、wp-config.phpファイルに書かれている、WPLANGの値(指定がなければen_US)が使われます。

プラグイン用の書き方を例に、基本的な動き

プラグインを国際化するためには、以下のload_plugin_textdomainを使います。

load_plugin_textdomain( $domain, $abs_rel_path, $plugin_rel_path )

2つ目の引数は非推奨になったけど消すこともできずに残っている切ないものなので、falseにしておきます。

book-stealthというプラグインを作っているとすると、以下のようにプラグインの中に書きます(フックの仕方も含めて)。

add_action( 'plugins_loaded', 'book_stealth_load_textdomain' );
function book_stealth_load_textdomain() {
    load_plugin_textdomain( 'book-stealth', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}

一方、プラグインの中で翻訳したい文字列は翻訳関数の__()や_e()などを使って、<?php _e( ‘This is a nice book!’, ‘book-stealth’ ); ?>というふうに書いておきます。

content_php

これはテーマの場合だけど、こんな感じで__()や_e()を使って書く。

そして、翻訳ファイルには、’This is a nice book!’という翻訳されるべき文字列(英語です)と’これはスゴイ本だなぁ!’という翻訳のペアが、たくさん並べておきます。

TwentyTwelveの翻訳ファイルをPoeditという翻訳ファイル編集ソフトで開いているところ。

TwentyTwelveの翻訳ファイルをPoeditという翻訳ファイル編集ソフトで開いているところ。

なので、WordPressは、

  1. book-stealthというテキストドメインを持つ翻訳を探そう
  2. locale: WPLANGがjaだからbook-stealth-ja.moだな
  3. path: プラグインディレクトリの中のlanguagesフォルダにあるのね

という風にして、翻訳ファイルの読み込みと翻訳をしてくれます。

テーマの場合は以下のようになります。

add_action( 'after_setup_theme', 'my_theme_setup' );
function my_theme_setup(){
    load_theme_textdomain( 'my_theme', get_template_directory() . '/languages' );
}

 読み込みの順序

さて、指定された場所に翻訳ファイルがなかった場合にはどうなるのか、が今回調べたことです。

プラグインの場合

さきほどのプラグインの例では、日本語であれば/wp-content/plugins/book-stealth/languages/book-stealth-ja.moを探すわけですが、これがない場合はどうなるか。/wp-includes/l10n.phpにある、load_plugin_textdomain()を見ると(コメント訳した)、

function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {

	// WPLANGからlocaleを取得
	$locale = get_locale();
	$locale = apply_filters( 'plugin_locale', $locale, $domain );

	// path を以下の順で決める。
	// 1. 第三引数で指定された場所
	// 2. 非推奨を伝えつつ、第二引数も見てあげる
	// 3. なければpluginsディレクトリ
	if ( false !== $plugin_rel_path	) {
		$path = WP_PLUGIN_DIR . '/' . trim( $plugin_rel_path, '/' );
	} else if ( false !== $deprecated ) {
		_deprecated_argument( __FUNCTION__, '2.7' );
		$path = ABSPATH . trim( $deprecated, '/' );
	} else {
		$path = WP_PLUGIN_DIR;
	}

	// さっそく読んでみる
	$mofile = $domain . '-' . $locale . '.mo';
	// load_textdomain()は実際に読み込んで$l10nというグローバル変数に$l10n[$domain]をセットして成功すればtrueを帰す
	if ( $loaded = load_textdomain( $domain, $path . '/'. $mofile ) )
		return $loaded;

	// 読めなかったら、/wp-content/languages/plugins/book-stealth-ja.moを探してみる
	$mofile = WP_LANG_DIR . '/plugins/' . $mofile;
	return load_textdomain( $domain, $mofile );
}

となっていますので、以下の順です。

  1.  第三引数が普通に渡されていれば、そこ。上の例では、/wp-content/plugins/book-stealth/languages/book-stealth-ja.mo
  2. 第二引数が渡されていれば、そこ
  3. なければ、/wp-content/plugins/book-stealth-ja.mo
  4. それでもなければ、/wp-content/languages/plugins/book-stealth-ja.mo

となります。WordPress日本語版をインストールすると、akismetの翻訳ファイルが/wp-content/languages/plugins/に入っているのですが、この4番目を利用していたということだったんですね。

akismetの翻訳ファイルの在処

akismetの翻訳ファイルの在処

なるほど〜。

テーマの場合

テーマの場合もだいたい同じです。引数は、load_theme_textdomain( $domain, $path = false )。順番は、以下です。

  1. 第二引数が普通に渡されていれば、/wp-content/themes/theme-name/languages/ja.mo
  2. なければ、/wp-content/themes/theme-name/ja.mo
  3. それでもなければ、/wp-content/languages/themes/theme-name-ja.mo

注意点は、wp-content/languages/themes/に入れるときはテキストドメインをファイル名に入れておかないといけないこと。当たり前ですけど。

ソースは以下で、面白いのは子テーマを作っている時の動きだと思いました。

function load_theme_textdomain( $domain, $path = false ) {

    $locale = get_locale();
    $locale = apply_filters( 'theme_locale', $locale, $domain );

    // 1. 指定された場所
    // 2. 親テーマのディレクトリ
    if ( ! $path )
        $path = get_template_directory();

    // 読んでみる
    $mofile = "{$path}/{$locale}.mo";
    if ( $loaded = load_textdomain( $domain, $mofile ) )
        return $loaded;

    // それでもなければ/wp-content/languages/themes/book-stealth-ja.moを探してみる
    $mofile = WP_LANG_DIR . "/themes/{$domain}-{$locale}.mo";
    return load_textdomain( $domain, $mofile );
}

パスが指定されていればそれが読まれますが、パスが指定されていない場合、親テーマのja.moを探しに行っています(get_template_directory())。ということは、子テーマでパスまで指定してやれば、親テーマの翻訳に追加ができるということですね。

親テーマと子テーマから同じテキストドメインを指定?

親テーマにもload_theme_textdomainがあり、子テーマでもload_theme_textdomainがあり、両方から同じテキストドメインで読み込んだ場合には、翻訳が被ってしまうのだけど、後述するように、2つのファイルがマージされます。先に読まれたほうが採用されるようなので、多分、子テーマの方が先に読まれます。

また、テーマよりもプラグインの方が先に読まれますので、プラグインから指定することもできます。

それと、これはtwentyfourteenなどのデフォルトテーマシリーズの場合ですが、翻訳ファイルは<code>/wp-content/languages/themes/twentyfourteen-ja.mo</code>にあるので、子テーマでload_theme_textdomain()をしてやれば、翻訳を上書きできるということですね。デフォルトの翻訳ファイルをコピーしてきて編集したものを、子テーマ内に置いておけば、親テーマがアップデートされたとしても大丈夫だ、という工夫なのかな。

 最後にload_textdomain( $domain, $mofile )について

というわけで、プラグインとテーマでの読み込みの順番についてみてみました。読み込みを実際に行っているload_textdomainを見て終わりにします。

ソースのコメントの翻訳から。

/**
* Load a .mo file into the text domain $domain.
* .moファイルを読み込む。(テキストドメインの中に?)
*
* If the text domain already exists, the translations will be merged. If both
* sets have the same string, the translation from the original value will be taken.
* もし、テキストドメインがすでにあったら、翻訳はマージされます。
* もし、被ったらオリジナルのほうが取られます。
*
* On success, the .mo file will be placed in the $l10n global by $domain
* and will be a MO object.
* 成功したら、.moファイルはグローバル変数$l10nに$domain付きで置かれて、MOオブジェクトになる
*
* @since 1.5.0
*
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
* @param string $mofile Path to the .mo file.
* @return bool True on success, false on failure.
*/

$moオブジェクトというのがあるのですね。以下の定義を見ると、作成された$moオブジェクトは、$l10nというグローバル変数にテキストドメインをキーとして格納されるようです。

読み込みの順番で面白いと思ったのは、41行目で読み込むファイルを変えることができる点です。

プラグインの翻訳ファイルを、自分の環境だけで変えて、アップデートしてもそれを持ち続けたい、というような場合には、ここで差し替えることができそうです。

function load_textdomain( $domain, $mofile ) {
	global $l10n;

	/**
	 * textdomainやmoファイルパスを上書きするかどうかをしている
	 * Filter text domain and/or MO file path for loading translations.
	 *
	 * @since 2.9.0
	 *
	 * @param bool   $override Whether to override the text domain. Default false.
	 * @param string $domain   Text domain. Unique identifier for retrieving translated strings.
	 * @param string $mofile   Path to the MO file.
	 */
	$plugin_override = apply_filters( 'override_load_textdomain', false, $domain, $mofile );

	// 上書きするなら返しちゃう。別途自分で読み込まないといかないかな?
	if ( true == $plugin_override ) {
		return true;
	}

	/**
	 * ここでアクションをひとつ
	 * Fires before the MO translation file is loaded.
	 *
	 * @since 2.9.0
	 *
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
	 * @param string $mofile Path to the .mo file.
	 */
	do_action( 'load_textdomain', $domain, $mofile );

	/**
	 * 読み込むべきmoファイルをフィルターできる
	 * Filter MO file path for loading translations for a specific text domain.
	 *
	 * @since 2.9.0
	 *
	 * @param string $mofile Path to the MO file.
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
	 */
	$mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain );

	// 読めないときはfalseが返る
	if ( !is_readable( $mofile ) ) return false;

	// MOクラスのインスタンス。MOクラス、/wp-includes/pomo/mo.phpにある
	$mo = new MO();

	// 読めなかったらfalse
	if ( !$mo->import_from_file( $mofile ) ) return false;

	// すでに同じtextdomainがあった場合にはマージ
	if ( isset( $l10n[$domain] ) )
		$mo->merge_with( $l10n[$domain] );

	// textdomainをキーにした配列として$l10nに格納して
	$l10n[$domain] = &$mo;

	// trueを返す
	return true;
}

以下は、$l10nをダンプしてみた例

array(2) {
["twentytwelve"]=>
 &object(MO)#184 (4) {
  ["_nplurals"]=>
  int(1)
  ["entries"]=>
  array(72) {
   ["% Replies"]=>
   object(Translation_Entry)#1705 (9) {
    ["is_plural"]=>
    bool(false)
    ["context"]=>
    NULL
    ["singular"]=>
    string(9) "% Replies"
    ["plural"]=>
    NULL
    ["translations"]=>
    array(1) {
     [0]=>
     string(13) "%件の返信"
    }
    ["translator_comments"]=>
    string(0) ""
    ["extracted_comments"]=>
    string(0) ""
    ["references"]=>
    array(0) {
    }
    ["flags"]=>
    array(0) {
    }
   }
 }
 // 他にも翻訳されるべき文字列をキーにした配列がたくさん。
}
// 他にもテキストドメインをキーにしたMOオブジェクトが配列として格納されてる
}

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

スクリーンショット-2014-04-30-22.36.13

WordPressのユーザーの権限グループについて。管理画面のスクショ、権限の追加など。

WordPressのプラグインの作り方、WordPress内部の色々なAPIを解説をする本を書いています。

宮内さん岡本さん三好さんとの共著です。7月18日に発売みたいです。

その中で、WordPressのユーザーに関する章があったのですが、調べれば分かる内容が含まれているということで、簡素化しましたので、また、検索しても意外に見当たらないのでペタリとコピペであります。

この記事の概要

要するに、以下のことが書いてあります。

  • WordPressのユーザーには、いろんな権限がある
  • 会員登録させることもできる
  • 権限グループという概念があって、最初は5つ(読者、寄稿者、投稿者、編集者、管理者)がある
  • 5つの権限グループをまずは簡単に、次に、それぞれの権限グループでログインした場合の様子を管理画面付きで紹介する
  • 既存の権限グループに含まれる権限を変更したり、新しい権限グループを作る方法もある

といったところです。それでは、以下、コピペです。
(レビューとかしていない原稿なので、誤字とかありましたらご了承ください)

権限グループと権限

ユーザー登録

WordPressのユーザー機能は充実しており、複数のユーザーを持つことができますし、ユーザーに権限を割り当てることで管理画面でできることのレベルを指定することもできます。

また、会員機能のベースとして利用することもでき、管理画面から有効化することでウェブサイトの訪問者がウェブサイトに自分のアカウントを作成することも可能です。

サイト訪問者による会員登録は、デフォルトでは無効化されています。

デフォルトでは会員登録が無効化されている
デフォルトでは会員登録が無効化されている

管理者としてログインして、設定、一般と進むと、メンバーシップの欄があります。「だれでもユーザー登録ができるようにする」という項目にチェックを入れると、サイト訪問者による会員登録が有効化されます。

また、その直下の「新規ユーザーのデフォルト権限グループ」という欄では、管理者から購読者まで、どの権限グループを割り当てるのかを指定することもできます。デフォルトでは購読者という権限グループになります。

管理画面>一般の「メンバーシップ」で会員登録の可否を設定できる
管理画面>一般の「メンバーシップ」で会員登録の可否を設定できる

次の画像は会員登録を有効化した場合の登録フォームです。

会員登録が有効になっている場合の登録画面
会員登録が有効になっている場合の登録画面

権限について

WordPressには、権限グループと権限いう概念があり、管理者はユーザーがサイト内で何ができて、何ができないのかをコントロールすることができるようになっています。

WordPressでは、投稿を新規作成する、固定ページを新規作成する、カテゴリーを作成する、コメントを承認する、プラグインやテーマをインストールしたり更新したりする、ユーザーを追加したり編集・削除する、といったいろいろなことができますが、権限がないとこうしたことはできないようになっています。

WordPressの本体を更新するためには、update_coreという権限が必要ですし、manage_categoriesという権限がないユーザーにはカテゴリーを増やしたり編集したりすることはできません。

権限グループとは、こうした権限をいくつかまとめたもので、デフォルトの状態では以下の6つが用意されています。

これらの6つの権限グループは、上から順番にできることが増えていき、上位レベルのユーザーは下位のレベルの権限をすべて含みます。

権限グループ(日本語) 権限グループ(英語) 概要
購読者 Subscriber WordPressにログインすることが可能。
寄稿者 Contributor レビュー待ちの投稿を作成することができる。レビュー待ちになった投稿の公開は、編集者以上の権限グループのユーザーが行う。
投稿者 Author 投稿を行うことはできるが、固定ページや他のユーザーが作成した投稿を扱うことはできない。
編集者 Editor 投稿や固定ページ、カテゴリーなどを、他の人が作成した投稿を含めて扱うことができる。テーマやプラグイン、ユーザー、サイト設定を扱うことはできない。
管理者 Administrator トップレベルの権限グループで、すべての権限を持つ

この表にあるようなデフォルトで設定されている権限グループと権限は、投稿と固定ページしかないシンプルなサイトを想定しています。

ウェブサイトやサービスによっては、複数のカスタム投稿タイプがあり、各投稿タイプの権限を分けたい場合や、会員制サイトを作成していて、課金の有無によってできることが異なるといった場合もあります。

そうした構成に対応する場合には、デフォルトの権限グループに加えて、オリジナルの権限グループや権限を作成することが可能です。

権限グループと権限の組み合わせを変更したり、プラグインによって追加した機能を利用できるユーザーを独自にコントロールすることができるのです。以下は、権限グループと権限を操作するための基本的な関数です。

関数名
add_cap() 権限グループに権限を追加する
remove_cap() 権限グループから権限を取り除く
add_role() 新しい権限グループを追加する
remove_role() 既存の権限グループを削除する

こうした関数を使って権限や権限グループをカスタマイズすると、デフォルトで想定されている権限グループの上位、下位といった概念が無くなり、権限グループによってできることやできないことが異なるだけ、という形になっていくでしょう。

プラグインを開発する時に、権限グループと権限についてよく理解していることは、とても重要です。

うっかりすると、テーマの変更やプラグインの追加、サイト全体にかかわる設定変更を、レベルの低いユーザーにも実行できるようにしてしまい、混乱を生じるからです。

実装する機能がどのような性質のもので、どういうユーザーに権限を渡すべきなのか、自覚的に作成を行いましょう。

デフォルトで用意されている権限グループと権限は、以下のようになっています。

購読者 Subscriber

  • read

WordPressにログインして、自分のプロフィールを編集することができます。

Codexによれば、readという権限は、コアファイルでは、何かを判別するためには使われていません。

購読者権限でログインした管理画面の様子
購読者権限でログインした管理画面の様子

寄稿者 Contributor

購読者の権限に追加して、

  • delete_posts
  • edit_posts

自分の投稿は作れますが、他の人の投稿を編集できません。

また、投稿の公開もできず、編集者以上の権限グループに属する(あるいはpublish_posts権限を持つ)ユーザーに公開を託すことになります。

公開されたあとの投稿の編集もできません。

投稿画面の公開メタボックスでは、「レビュー待ちとして保存」ボタンが表示されます。
投稿画面の公開メタボックスでは、「レビュー待ちとして保存」ボタンが表示されます。

投稿者 Author

寄稿者の権限に追加して、

  • delete_published_posts
  • edit_published_posts
  • publish_posts
  • upload_files

寄稿者と違って、自分の投稿については、管理できるようになります。

また、画像のアップロードもできるようになります。

他のユーザーの投稿を編集することなどはできません。

投稿の一覧画面
投稿の一覧画面

編集者 Editor

投稿者の権限に追加して、

  • delete_others_pages
  • delete_others_posts
  • delete_pages
  • delete_posts
  • delete_private_pages
  • delete_private_posts
  • delete_published_pages
  • edit_others_pages
  • edit_others_posts
  • edit_pages
  • edit_posts
  • edit_private_pages
  • edit_private_posts
  • edit_published_pages
  • manage_categories
  • manage_links
  • moderate_comments
  • publish_pages
  • read_private_pages
  • read_private_posts
  • unfiltered_html

他の人が作成した投稿を編集したり削除したりすることができます。

また、投稿だけではなく固定ページや、privateが含まれる非公開記事も編集可能になります。

投稿者までのユーザーには使えなかったhtmlタグを含めることができるようになります。

編集者は、コンテンツに関する権限を持つが、サイトの設定に関しては権限がない
編集者は、コンテンツに関する権限を持つが、サイトの設定に関しては権限がない

管理者 Administrator

すべてのことに関する権限があります。

  • activate_plugins
  • delete_plugins
  • edit_dashboard
  • edit_files
  • edit_theme_options
  • export
  • import
  • list_users
  • manage_options
  • promote_users
  • remove_users
  • switch_themes
  • update_core
  • update_plugins
  • update_themes
  • install_plugins
  • install_themes
  • delete_themes
  • edit_plugins
  • edit_themes
  • edit_users
  • create_users
  • delete_users

コアの更新、テーマやプラグイン関連、設定関連、管理画面からのファイル編集、コンテンツのインポートやエクスポート、ユーザー関連についての操作は、デフォルトでは管理者のみが可能になっています。

すべての権限を持つため、管理画面のメニューもフルコース。編集者の画面に追加して、外観、プラグイン、ユーザー、設定のメニューが表示される
すべての権限を持つため、管理画面のメニューもフルコース。編集者の画面に追加して、外観、プラグイン、ユーザー、設定のメニューが表示される

add_cap() and remove_cap()

権限グループに割り当てられている、権限を変更するには、add_cap()とremove_cap()を利用します。

寄稿者に画像をアップロードする権限を追加しているのが次の例です。

ポイントは、データベースに保存された権限のリストを変更する処理であるため、アクティベーションフックを利用しているテント、無効化された際にもとに戻すためのディアクティベーションフックが利用されている点です。

そうしないと、WordPressが動くときに毎回データベースへのアクセスが発生してしまいます。


// add role on activation
register_activation_hook( __FILE__, 'nskw_activate' );
function nskw_activate() {

	$role = get_role( 'contributor' );
	$role->add_cap( 'upload_files' );
	
}

// remove role on deactivation
register_deactivation_hook( __FILE__, 'nskw_deactivate' );
function nskw_deactivate() {

	$role = get_role( 'contributor' );
	$role->remove_cap( 'upload_files' );

}

add_cap(), remove_cap()は、WP_Roles WP_WP_Roleのメソッドなのでこの例のような書き方で呼び出します。

あるいは、以下のような形でもOKです。


global $wp_roles;
$wp_roles->add_cap( 'contributor', 'upload_files' );

最初の例での$roleの中身は以下のようになっています。

object(WP_Role) (2) {
  ["name"]=>
  string(11) "contributor"
  ["capabilities"]=>
  array(5) {
    ["edit_posts"]=>
    bool(true)
    ["read"]=>
    bool(true)
    ["level_1"]=>
    bool(true)
    ["level_0"]=>
    bool(true)
    ["delete_posts"]=>
    bool(true)
  }
}

add_role()、remove_role()

デフォルトの権限グループにはない独自の権限グループを作成したり、権限グループを削除するには、add_role()、remove_role()を利用します。

まず、削除はとても簡単です。


// 購読者権限グループを削除
remove_role( 'subscriber' );

追加をするためには以下を利用します。


add_role( $role, $display_name, $capabilities );
引数名 内容
$role 権限グループの名前
$display_name 表示名
$capabilities 権限のリストを配列で渡す

次の例は、編集者権限グループにedit_theme_optionsという権限を追加した、Webmasterという権限を追加しているものです。


// add Role on activation
register_activation_hook( __FILE__, 'nskw_activate' );
function nskw_activate() {

	// editorのCapabilitiesを取得して、
	$editor_role  = get_role( 'editor' );
	$new_cap      = $editor_role->capabilities;
	
	// edit_theme_optionsを追加
	$new_cap['edit_theme_options'] = true;
	
	add_role( 'webmaster', 'Webmaster', $new_cap );
	
}

// remove Role on deactivation
register_deactivation_hook( __FILE__, 'nskw_deactivate' );
function nskw_deactivate() {

	remove_role( 'webmaster' );
	
}

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