コンサルティング事業部ブログ

トピックス

ワードプレスで寄稿者に画像アップ権限を付ける+自身の所有メディアしかリストさせない

こんにちは、水上亮です。前回の一部では高評価、そして大部分からは白目で見られたヤツからずいぶん日が空いてしまいました。

あれでもアレ系の記事は気を使うので自分的ボツ率が高いのですよ。なので今回はTIPS的なやつです。別に今日紹介するTIPSはポテトチップスくらいそこらじゅうで公開されているようなモノで珍しさはありませんが、その後にこういうTIPS自体をどうやって開拓・探すのかっていうところをメインに解説したいと思います。

寄稿者権限?

一応知らない人のために解説しますと、寄稿者権限とはWPデフォルトでは購読者の次に低い権限で、下書き状態での投稿はできますが、公開するには管理者や編集者の承認が必要な権限です。そして、一度公開された記事は自分の記事でも再編集権限がありません。ユーザーリストには下記のように表示されます。(某案件の開発中のものです)

ただし、やってみて「オイ!」と思ったと思いますが、デフォルトの寄稿者権限では、メディアのアップロード権限がありません。したがって、投稿できるのは文字のみです。これでは表現に限界がありますし、あとから画像を探す編集者の手間を考えれば、寄稿者がメディアをアップロードした方がいいじゃん!特に規模の大きいサイトになればなるほどそうでしょう。

この赤線部分にいつもどおりの「メディア」メニューと「メディアを追加」ボタンを追加しましょう。クソ簡単です。

寄稿者にメディアアップロード権限を追加する

寄稿者はWPではcontributorと言います。role(権限名と権限のグループ)とかcap(権限)とかlevel(roleの階級)とかいろいろな軸があるんですが、メディアアップロード権限であるupload_filesというcapをcontributorに追加すれば良いということです。

add_action('admin_init',function(){
  $contributor=get_role('contributor');
  $contributor->add_cap('upload_files');
})

終わり。ですが、注意が必要なのは、この処理はDBを書き換えます。どれだけ早く処理されるフックを通しても、メニュー生成の方が早いため、1回目はDBを書き換えるだけで出力時は反映されないのです。焦らず1回リロードしてみましょう。それでできてなければ失敗です。

ちなみにDBを書き換えるので、$contributor->remove_cap('upload_files');を実行しない限り、上記のフックを削除しても効果は継続します。逆に言えば、この処理はテーマを変えても継続するので、本来はプラグインで操作すべきものと言えます。

それはさておきこれで、メディアアップロードができるようになりましたね。

Oh Baby.

ところが・・・

いざ「メディア」を見てみるとこの有様です。これでは、他人のあげた画像(削除や編集はできない)も自分のあげたヤバイ画像も、全員に見られてしまいます。当然記事を書くにあたりアップしようとおもって「メディアの追加」をクリックしても、

こうです。自分のしかリストさせない方がいろんな意味で良いでしょう。

自分のメディアだけリストさせる

add_action('pre_get_posts',function($query){
  global $current_user;
  if(!is_admin()) return $query;
  if($current_user->user_level>=5) return $query;
  if($query->get('post_type')=='attachment'){
    $query->set('author',$current_user->data->ID);
  };
  return $query;
})

This WP gonna be good.

簡単に解説すると、pre_get_postsというアクションはメインクエリを書き換えるフックです。カスタムする人は多用するでしょう。if(!is_admin()) return $query;これは管理画面でない場合はスルー、if($current_user->user_level>=5) return $query;これは編集者権限より上の権限ではスルーという意味です。管理者や編集者は全てのメディアを見たいですからね。

if($query->get('post_type')=='attachment')これはお察し通り、post_type==attachmentつまりメディアの場合です。そして $query->set('author',$current_user->data->ID);これは所有者が現在のユーザーという条件をセットということです。これを追記すれば、投稿者・寄稿者は自分のメディアしかリストされなくなります。

同様にif($query->get('post_type')=='post')とすれば投稿一覧でも同じことができます。その場合はちょっと追加処理が必要です。

ここだけ別なのです。こういう時とか、これまでのプロセスで、ググったら誰かの答えが見つかるので何となくそれで解決しているけど、そもそもその答えってどうやって見つけるの?って思ったことアリませんか?たまたまどっかの誰かが暇で答えを書いておいてくれたからできてたけど、もし誰も書いてくれていなかったら積みますよね?

そうならないためにも、この問題は解決方法の探し方から解説したいと思います。ここからが本題の可能性があります!

ワードプレスカスタマイズで困ったときの打開策の探し方

初心者向けです。基本的にはコアファイルを書き換えることはNGです。フレームワークはどれもそうです。犯したものは3年以下の懲役もしくは50万円の罰金に処してもいいくらいだと思っています。しかし、ワードプレスには便利なフックであるapply_filtersdo_actionがあるのをご存知でしょうか?これらを駆使すればコアファイルに触ることなくコアファイルの処理をカスタマイズできます。上記の2つのadd_actionもすべてそのやり方です。

do_actionapply_filtersはコアファイルの随所にあり、特定の変数の書き換えや特定のタイミングでの処理の追加をできます。do_actionにはadd_actionapply_filtersにはadd_filterがそれぞれ対応しており、フックできます。つまり、当該箇所をカスタマイズした時は、WPコアファイルがその部分を出力する段階や変数の生成時にフックできるかどうか(=do_actionapply_filtersがないか)を調べればよいということです。

上記の赤で囲った部分が出力される場所をなんとかして特定しましょう。色んな方法がありますが、要素検証からHTMLを何となく見て、phpに影響されなそうなnodeをカンで検索にかけます。検索範囲はwp_adminやwp_includes内です。この場所の場合、<span class="count">というHTMLで探してみました。すると案の定ヒット、しかしいくつかありました。その中から当該部分のファイルを特定します(ここはがんばるしかない)。

今回はclass-wp-posts-list-table.phpの300行目付近だと思いました。不安だったら、その部分に一時的に追記してデバッグしてみましょう。表示に変化があれば正解です。すると当該コードはこうなっています。

$all_inner_html = sprintf(
  _nx(
    'All <span class="count">(%s)</span>',
    'All <span class="count">(%s)</span>',
    $total_posts,
    'posts'
  ),
  number_format_i18n( $total_posts )
);

数字部分は$total_postsなのでさらに追っていきます。

$num_posts = wp_count_posts( $post_type, 'readable' );
$total_posts = array_sum( (array) $num_posts );

wp_count_postsこれです。こういう関数がきたら、どっかに絶対funtion wp_count_posts(があるので「funtion wp_count_posts(」で再び全体検索します。すると今度はpost.phpの2200行目付近に飛びます。長いですがあえて全部関数を引用します。

function wp_count_posts( $type = 'post', $perm = '' ) {
	global $wpdb;

	if ( ! post_type_exists( $type ) )
		return new stdClass;

	$cache_key = _count_posts_cache_key( $type, $perm );

	$counts = wp_cache_get( $cache_key, 'counts' );
	if ( false !== $counts ) {
		/** This filter is documented in wp-includes/post.php */
		return apply_filters( 'wp_count_posts', $counts, $type, $perm );
	}

	$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
	if ( 'readable' == $perm && is_user_logged_in() ) {
		$post_type_object = get_post_type_object($type);
		if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
			$query .= $wpdb->prepare( " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
				get_current_user_id()
			);
		}
	}
	$query .= ' GROUP BY post_status';

	$results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
	$counts = array_fill_keys( get_post_stati(), 0 );

	foreach ( $results as $row ) {
		$counts[ $row['post_status'] ] = $row['num_posts'];
	}

	$counts = (object) $counts;
	wp_cache_set( $cache_key, $counts, 'counts' );

	/**
	 * Modify returned post counts by status for the current post type.
	 *
	 * @since 3.7.0
	 *
	 * @param object $counts An object containing the current post_type's post
	 *                       counts by status.
	 * @param string $type   Post type.
	 * @param string $perm   The permission to determine if the posts are 'readable'
	 *                       by the current user.
	 */
	return apply_filters( 'wp_count_posts', $counts, $type, $perm );
}

最後の行や途中でも、return前には必ずapply_filtersを介しているのがわかります。これはカスタマイズ可能ということです。apply_filtersは引数の1つ目がタグで、2つ目はデフォルト値(フックが1つもなければこれをそのまま返す)、3つ目以降がパラメータです。これを発見すれば、フックのさせ方もわかりますね。

add_filter('wp_count_posts',function($counts,$type,$perm){
	global $wpdb;
	$query="
		SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s
	";
	if('readable'==$perm&&is_user_logged_in()){
		$post_type_object=get_post_type_object($type);
		$query.= $wpdb->prepare(" AND post_author = %d",get_current_user_id());
	};
	$query.=' GROUP BY post_status';

	$results=(array)$wpdb->get_results($wpdb->prepare($query,$type),ARRAY_A);
	$counts=array_fill_keys(get_post_stati(),0);

	foreach($results as $row){
		$counts[$row['post_status']]=$row['num_posts'];
	};

	$counts=(object)$counts;
	return $counts;
},10,3);

wp_count_posts関数自体とパラメータがほぼ同じなので、あえてコードも同じようにしましたが、変えた部分は、$query.= $wpdb->prepare(" AND post_author = %d",get_current_user_id());ここです。これで無条件に自分のを数えます。他の部分はごちゃごちゃしていますが、元のwp_count_posts関数と同じです。

Jesus Christ!!

おわりに

まあ、こうすれば、めんどくさいけどもしググっても答えが見つからんかった時でも何とか打開できるんですよね。実際転がってる答えなんて当てにならんし、知っている側から見れば「こいつこんなめんどくさい方法紹介すんなよ」と思われるでしょう。↑これもその例にもれないかも!

しかし、他人を当てにするより自分を当てにしたほうが、何がどうなっているかも理解できるので、バグった時やさらなるお客さんの要望への実現可否判断スピードがギュインとあがることでしょう。というわけで、前回偉そうな記事を書いたので、一応まあまあ役立つであろうポテトチップスとポテトチップスのレシピを公開して、お別れにしたいと思います。

RecentPost