【WordPress】業務ブログを作るエンジニアが実装した機能

前回投稿した記事で業務向けページの変更をしたと書きました。今回はそのやり方について共有したいと思います。まだご覧になっていない方は前回の記事を参照ください

この記事では実現するために行ったことを目次形式で並べていきます。もちろん他サイト様を参考にして修正しなかったものや結果が画像で示されている項目はリンクだけ貼っておきます。

寄稿者が投稿した記事をレビューする編集者に自動でメールを送信して通知する

上記の記事で述べたように登録したコンサルの方々の権限は寄稿者になりますので記事は管理者または編集者のレビューを通して公開されます。その記事がレビュー待ちになったら予め登録しておいた編集に自動でメール送信される機能です。

編集者に自動で送信される機能は下記のリンク(WordPressカスタマイズ事典)を参考にさせて頂いております。→ 2022/02/11 リンク切れしてましたのでソース貼ります。

レビュー待ちの投稿がされた時に管理者にメールで通知する

// レビュー待ちの投稿がされた場合に編集者にメールを送信します。
function mail_for_pending( $new_status, $old_status, $post ) {
// 投稿がレビュー待ち以外からレビュー待ちに変わった(新規の場合は$old_statusが'new'、$new_statusが'pending'になります)
	if ( $old_status != 'pending' && $new_status == 'pending' ) {
		// ブログ名(サイト名)
		$blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
		// 投稿名
		$post_title = wp_specialchars_decode($post->post_title, ENT_QUOTES);
		// 投稿者
		$post_author = get_userdata($post->post_author);
		
		// 件名
		$subject = "[{$blogname}] 承認待ちのレポートが投稿されました({$post_title})";
		// 本文
		$message = "{$post_author->display_name}さんのレポート「{$post_title}」が承認待ちになりました。確認をお願いします。\r\n";
		$message .= "\r\n";
		$message .= "編集および公開: \r\n";
		$message .= wp_specialchars_decode(get_edit_post_link( $post->ID ), ENT_QUOTES) . "\r\n";
		
		//編集者がいれば編集者全員にメールを送る
		$editor_mails=get_editor_mails();
		if(sizeof($editor_mails)>0){
			for($i=0; $i<sizeof($editor_mails); $i++){
				$to = $editor_mails[$i];
				$r = wp_mail( $to, $subject, $message );
			}
			
		}else{
			// 送信先(管理者メールアドレス)
			$to = get_option('admin_email');
			// メールを送信
			$r = wp_mail( $to, $subject, $message );
		}
	}
}
add_action( 'transition_post_status', 'mail_for_pending', 10, 3 );

寄稿者のダッシュボードの投稿数や投稿表示はそのユーザのみのものに変更する

こちらも画像付きでWordPressカスタマイズ事典にありましたのでリンクします。→ 2022/02/11 リンク切れしてましたので今回のソース貼ります。

管理画面の投稿一覧の投稿をログイン中のユーザーの投稿のみにする

function pre_get_author_posts( $query ) {
    // 管理画面 かつ 非管理者 かつ メインクエリ 
    // かつ authorパラメータがないかauthorパラメータが自分のIDの場合、
    // 投稿者を絞った状態を前提として表示を調整します。
    if ( is_admin() && (!current_user_can('administrator') && !current_user_can('editor')) && $query->is_main_query()
            && ( !isset($_GET['author']) || $_GET['author'] == get_current_user_id() ) ) {        // クエリの条件を追加
        $query->set( 'author', get_current_user_id() );
        // 値があると WP_Posts_List_Table#is_base_request() がfalseになり
        // 「すべて」のリンクが選択表示にならないため削除
        unset($_GET['author']);
    }
}
add_action( 'pre_get_posts', 'pre_get_author_posts' );

今回の仕様の場合、管理者アカウントの他にレビュー及び記事を公開する編集者役の人がおりますので編集者のアカウントは投稿制御の対象外にします。

// 管理画面 かつ 非管理者 かつ メインクエリ
// かつ authorパラメータがないかauthorパラメータが自分のIDの場合、
// 投稿者を絞った状態を前提として表示を調整します。
if ( is_admin() && (!current_user_can('administrator') && !current_user_can('editor')) && $query->is_main_query()

上記コードのようにeditor(編集者アカウント)も追加すればOKです。

ダッシュボードも寄稿者でも行える権限でも使わないものは非表示にしてシンプルにする

管理者以外(編集者・寄稿者アカウント)の人には触れてほしくないメニューにする方法です。

こちらはアド・エータイプ様のサイトを参考にさせて頂いてます。

WordPressの管理画面で使用しないメニューを非表示にする方法

こちらのサイト様も画像付きで紹介されていますのでこちらからの補足や追加はありませんが、構文上問題ないハズなのにWordPress上のエディタで確定できなかった時がありました。

最新版はわかりませんがWordPressのエディタはバグも多いと聞いていますので個別に直したものをアップロードする方が確実だと思います。

メディアライブラリもユーザ毎に表示する

投稿数、投稿一覧、ダッシュボードの項目に続いてメディアライブラリの表示対象もユーザ毎にするようにします。こちらのページもこちらも画像付きでWordPressカスタマイズ事典にありましたのでリンクします。 → 2022/02/11 リンク切れしてましたので今回のソース貼ります。

他人がアップロードしたメディア(画像)を見せないよう制限する

// メディアライブラリのアカウント制限
function display_only_self_uploaded_medias( $query ) {
    if ( ( $user = wp_get_current_user() ) && ! current_user_can( 'administrator' ) && ! current_user_can( 'editor' )) {
        $query['author'] = $user->ID;
    }
    return $query;
}
add_action( 'ajax_query_attachments_args', 'display_only_self_uploaded_medias' );

こちらも今までと同じように編集者権限も全画像も見れるようにしてあります。

tokenを発行して外部からのアクセスははじく(会員だけが見れるブログだったので)

題名にはtoken(トークン)と書いてありますが、要は認証が必要なページにするということです。

もちろん一般に使われるベーシック認証プラグインやIPアドレスではじくものは使用できません

スタッフ+会員だけ見れるページでも会員のIPアドレスはそれぞれ違いますし、ベーシック認証はWordPress管理者のユーザ・パスワードが設定されるからセキュリティ漏れますし、会員一人ひとりにユーザとパスワードをいれてね~。と通知するわけにもいきません。(お客様にとっては使い勝手が悪すぎますし、外部に漏れたら会員以外も見れてしまいます

なので Webアプリからクリックしたときにtokenを発行し、そのtokenがWordPress内の初期化処理で一致するか判断し、一致しなければ401エラーを表示させるようにします。この画像はデフォルトのエラー画面ですが、自作のエラー用ページに遷移させることも可能です。こちらは興味があれば検索をしてみてください。

やり方はちょっとだけ複雑ですがザックリ以下のような感じに実施してました。(AWS環境なので)

tokenの発行方法

バッチでPerlを使ってuuidを使ったランダムの文字列をWordPressのサーバ+Webアプリで使用するDB両方に書き込むものです。WordPress側は渡されたURLからtokenを取り出して一致していれば読み込めるようにします。

functions.php

// get_token_val関数の定義
function get_token_val() {
  // URLパラメーター「token」の値を取得
  $val = (isset($_GET['token']) && $_GET['token'] != '') ? $_GET["token"] : '';
  // エスケープ処理
  $val = htmlspecialchars($val, ENT_QUOTES);

  // $valを戻り値として設定
  return $val;
}

// アクセス制限用ソース
add_action('init', 'access_restriction');
function access_restriction() {
	// 管理画面経由のアクセス または 訪問者が何かしらの権限でログイン中の場合
はアクセス制限から除外
	if (is_admin() || is_user_logged_in()) {
		return;
	}
	
	// ログイン画面はアクセス制限から除外
	$server = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null;
	if (preg_match("|wp-login\.php|", $server)) {
		return;
	}

	global $wpdb;
	$responce = $wpdb->get_var("トークンを取得するSQL");
	// 渡されたトークンがDBに保存されているトークンと一致するか
	if ( get_token_val() != $responce ) {
		// リファラのホストがサイト内でないか判定
		$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;		
		if (!preg_match("|^https?://自分のサイト|", $referer)) {
			// 疑似401表示
			wp_die('<h1>401 Unauthorized</h1><br>認証に失敗しました。', '401 Unauthorized', array('response' => 401));
		}
	}
}

このソースのミソはtokenを持てるのはWebアプリから直接リンクされた時だけですから、サイト内を回る時はtokenが付与されてなくても見れるようにしなければなりません。そのためにリファラのホストがサイト内が含まれているかどうかをチェックしています。また、ログイン画面や管理画面を見ている方はスタッフなので会員以外の制限はかからないようにして実現しました。

特殊な運用の仕方のためかトークンを持たせたらEmanonのテーマだと正常にページングができない問題が発生しました。テーマを直接変更して関数みたいな設定から自分のサイト名固定で渡すようにしてなんとか解決しましたのでトークン運用に興味ある方は参考までに。

編集者が記事を公開したらWebアプリアイコン上に通知を表示

これはどういう機能かと言いますとWebアプリケーション上のアイコンから参照するブログページに新しい記事が投稿されたらアイコンにNEWアイコンをつけるというものです。これはブログ上に保持されている一番目の記事の最新投稿日時がWebサイトを最後に開いた日時より新しければNEWマークをつけるという制御を行っています。普通に呼ぶだけでは最新日付を取ってくるはできません。ではどうするか?

WordPressサイトをJSON化する

ここではJSONの解説は行いませんが、Feed JSONというプラグインを使うとブログをJSON形式にしてくれます。プラグインを利用するとhttp://自分のサイトURL/feed/json に格納されますのでこのURLをアプリ側で呼びます。

取ってきたJSONをデシリアライズしたものの最終更新日時を保存します。

下の例はC#.NETですが、items[0](記事の先頭)のdate_modified(最終更新日時)を参照しています。

DateTime.Parse((string)obj.items[0].date_modified

これによりWordPressで作られたブログから最終更新日時を知る事ができました。

後はアプリ側で最後に開いた日時をユーザ毎に保管しておいたものを比較すればどっちが新しいかわかります。

いかがでしたでしょうか。個人で運用するにはアクセス制限も更新通知の処理もあまり考えなくてよいのでかなり業務向けの情報になったと思います。それでは~