WordPressのパスワード保護機能を用いて、特定の訪問者だけが閲覧できるページを作成したい。
その際に、同じパスワードを使いまわしたくないため、訪問者ごとにパスワードを割り当てる方法を教えて欲しい。
こんなお悩みを解決します。
WordPressでは、特定の記事をパスワードで保護できますよね。
ただ、パスワードの使いまわしはセキュリティ上好ましくないことと、多人数が参照している場合は変更による影響が大きくなってしまいます。
これを避けるには、対象となる訪問者ごとにパスワードを発行し、管理する必要があります。
今回は、上記のケースのように、パスワードで保護した特定の記事に複数のパスワードを割り当てる方法について解説します。
導入方法を詳しく解説していくので、興味がある方は、ぜひ最後までご覧ください。
効率良く技術習得したい方へ
短期間でプログラミング技術を習得したい場合は、経験者からフォローしてもらえる環境下で勉強することをおすすめします。
詳細は、以下の記事をご覧ください。
【比較】プログラミングスクールおすすめランキング6選【初心者向け】
続きを見る
参考サイト
今回の方法を実装するにあたり、以下のサイトを参考にしました。
こちらで紹介されているマイ・プラグインを利用する方が、function.phpを直接編集するよりも安全だと判断し、利用しております。
また、実装にあたっても同一のサイトの以下のページを参考にしました。
こちらで紹介されている記事は投稿ページだけが対象となっていますが、固定ページでも利用したかったため、実装結果の一部を改変しております。
複数のパスワードの管理と認証の仕組み
正確性に欠けますが、イメージを持ってもらうために、どのような感じで管理・認証されるかを図示しました。
管理者がやることとしては、投稿画面(WordPressで記事を書く画面)で、必要な数だけパスワードを登録します。
そして、アクセスを許可したい訪問者にメールなどを用いて該当先のリンクとパスワードを通知します。
訪問者は、通知されたリンクにアクセスし、パスワードを入力することで、パスワード保護された記事を閲覧できます。
今回は、上記のような仕組みをWordPressのプラグイン機能として実現します。
メタデータとして保存する情報
投稿ごとにパスワードによる認証を行いたいため、各投稿に以下に示すメタデータを付与します。
項目 | 内容 |
---|---|
有効期限 | パスワードの有効期限を指定 |
パスワード | 照合時に参照するパスワード |
メモ | メタデータに関する補足説明 |
登録日 | パスワードの登録日 |
パスワード認証部分の実装
まず、パスワード認証部分(上記の訪問者④部分)の実装を行います。
認証成功の場合のみとなりますが、処理ステップは、以下のようになります。
ここで、関数の使われ方の都合上、照合成功や照合不要の場合はfalseを返却し、照合失敗の場合はtrueを返却しています。
- 照合の要否を判定
パスワード保護されていないページ:照合不要なため、falseを返却
パスワード保護されているページ:2.以降を実行 - メタデータを取得する。
- Cookieから、送信されたパスワード(上記の訪問者③部分)のハッシュ値を取得する。
- メタデータに含まれているパスワードをハッシュ化し、3.で取得したパスワードのハッシュ値と比較する。
パスワードが一致しなかった場合:照合失敗のため、trueを返却
パスワードが一致しなかった場合:5.以降を実行 - 有効期限を確認する。
有効期限内の場合:照合成功のため、falseを返却
有効期限外の場合:照合失敗のため、trueを返却
上記の実装例は、以下のようになります。
ここで、get_post_meta
関数で指定しているmulti-password
は、後述するmyself_multi_password_callback
関数内のdiv要素のID(<div id="multi-password">
)と対応付きます。
/**
* パスワード認証
* Filters whether a post requires the user to supply a password.
*
* @since WordPress 4.7.0
*
* @param bool $required Whether the user needs to supply a password. True if password has not been
* provided or is incorrect, false if password has been supplied or is not required.
* @param WP_Post $post Post data.
* License: GPLv2 or later
*/
function myself_post_password_required($required, $post) {
if (!$required) {
return false;
}
require_once( ABSPATH . 'wp-includes/class-phpass.php' );
// パスワードデータ読込
$multi_password = maybe_unserialize(get_post_meta($post->ID, 'multi-password', true));
$hasher = new PasswordHash(8, true);
// 追加照合
$hash = isset($_COOKIE['wp-postpass_' . COOKIEHASH]) ? wp_unslash($_COOKIE['wp-postpass_' . COOKIEHASH]) : '';
if ((0 === strpos($hash, '$P$B')) && !empty($multi_password)) {
// check valid password
foreach ($multi_password as $password_table) {
if ($hasher->CheckPassword($password_table['target_password'], $hash)) {
// check if the password has expired
if (strtotime(date_i18n('Y/m/d')) <= strtotime($password_table['target_exp_date'])) {
return false;
}
}
}
}
return true;
}
add_filter('post_password_required', 'myself_post_password_required', 10, 2);
上記の対応により、パスワードによる承認部分の実装は完了しました。
パスワード登録・編集画面の実装
次に、パスワードを含むメタデータを登録する部分を実装します。
最終的には、以下に示すものが出来上がります。(これはWordPressの投稿編集画面に追加されます)
カスタムフィールドの実装
WordPressの投稿編集画面にカスタムフィールド(今回の場合、メタデータ部分)を追加するには、add_meta_boxというものを使います。
今回は、add_meta_boxによりカスタムフィールドを追加する関数を定義し、アクションとして登録します。
実装結果は以下のようになります。
/**
* 投稿ページ/固定ページの編集画面に「パスワード」の項目を追加
* Adds a box to the main column Multi Password
*
* License: GPLv2 or later
*/
function myself_multi_password_meta_boxes($post_type, $post) {
$titles = array(
'post' => '投稿',
'page' => '固定ページ',
);
// Check whether the $post_type exists or not in associative array keys.
if (array_key_exists($post_type, $titles)) {
add_meta_box(
'myself_multi_password_area', // メタボックスのID(div要素のidとなる)
__('個別パスワード(' . $titles[$post_type] . ')'), // タイトル
'myself_multi_password_callback', // カスタムフィールドの内容を定義する関数
$post_type, // 表示先のページの種類(今回は、投稿/固定ページのいずれかが対象)
'normal', // メタボックスの表示位置
'default' // 表示の優先度
);
}
}
add_action('add_meta_boxes', 'myself_multi_password_meta_boxes', 10, 2);
カスタムフィールドの内容部分の実装
先ほど作成した関数のコールバックにあたる関数myself_multi_password_callbackを実装します。
この部分の設定は、以降で説明するメタデータを保存する関数myself_multi_password_save_meta_box_dataと関連付けて管理する必要があります。
実装結果を示した上で、具体的な対応関係を図示します。
/**
* Prints the box Multi Password.
*
* @param WP_Post $post The object for the current post/page.
* License: GPLv2 or later
*/
function myself_multi_password_callback($post) {
// Add a nonce field so we can check for it later.
wp_nonce_field('myself_multi_password_save_meta_box_data', 'myself_multi_password_nonce');
// デフォルトパスワード期限(今日から1ヶ月後の月末)
$password_exp_date = date('Y/m/d', strtotime(date('Y/m/d', strtotime(date_i18n('Y-m-1') . ' 2 month')) . ' -1 day'));
// 投稿パスワード取得(配列)
$multi_password = maybe_unserialize(get_post_meta($post->ID, 'multi-password', true));
if (!$multi_password) {
$multi_password = array();
}
else {
// 登録日でソート
$targets = array();
foreach ($multi_password as $key => $value) {
$targets[$key] = $value['target_date'];
}
array_multisort($targets , SORT_DESC , $multi_password);
}
// 登録されているパスワードの数
$max_id = count($multi_password);
// CSS
echo '<style type="text/css">';
echo ' #multi-password{max-height: 200px;overflow: auto;}';
echo ' .multi-password-table .header{background: #f3f3f3 none repeat scroll 0 0;}';
echo ' .multi-password-table th{text-align: center;}';
echo ' .multi-password-table td input[type="text"]{width: 100%;}';
echo ' .multi-password-table .expiration-date{width: 15%;}';
echo ' .multi-password-table .target-password{width: 30%;}';
echo ' .multi-password-table .target-memo{width: 40%;}';
echo ' .multi-password-table .registration-date{width: 15%;text-align: center;}';
echo '</style>';
// 表示テーブル
echo '<div id="multi-password">';
echo '<table class="multi-password-table">';
echo '<tr class="header">';
echo '<th>期限</th>';
echo '<th>パスワード</th>';
echo '<th>メモ</th>';
echo '<th>登録日</th>';
echo '</tr>';
// 新規
echo '<tr>';
echo '<input type="hidden" name="max_id" value="' . $max_id . '">';
echo '<td class="expiration-date"><input type="text" name="target_exp_date_' . $max_id . '" value="' . $password_exp_date . '" size="10" autocomplete="off"></td>';
echo '<td class="target-password"><input type="text" name="target_password_' . $max_id . '" value="" size="20" autocomplete="off"></td>';
echo '<td class="target-memo"> <input type="text" name="target_memo_' . $max_id . '" value="" size="60" autocomplete="off"></td>';
echo '<td class="registration-date">新規パスワード</td>';
echo '</tr>';
// 修正・削除
if (!empty($multi_password)) {
$i = 0;
foreach ($multi_password as $data) {
echo '<tr>';
echo '<td class="expiration-date"> <input type="text" name="target_exp_date_' . $i . '" value="' . $data['target_exp_date'] . '" size="10"></td>';
echo '<td class="target-password"> <input type="text" name="target_password_' . $i . '" value="' . $data['target_password'] . '" size="20"></td>';
echo '<td class="target-memo"> <input type="text" name="target_memo_' . $i . '" value="' . $data['target_memo'] . '" size="60"></td>';
echo '<td class="registration-date"><input type="hidden" name="target_date_' . $i . '" value="' . $data['target_date'] . '">' . date('Y/m/d', $data['target_date']) . '</td>';
echo '</tr>';
$i++;
}
}
echo '</table>';
echo '</div>';
echo ' ※パスワードを空欄にして保存すると、その行は削除されます。';
}
上記のソースコードのうち、画面上の対応関係は以下のようになります。
また、オレンジ色部分では、メタデータを保持する連想配列(foreach ($multi_password as $data)の$data部分)とhtmlタグのname属性が対応するようになっており、対応関係は以下のようになります。
項目 | メタデータを保持する連想配列($data) | htmlタグ($i行目のname属性) |
---|---|---|
有効期限 | $data['target_exp_date'] | target_exp_date_$i |
パスワード | $data['target_password'] | target_password_$i |
メモ | $data['target_memo'] | target_memo_$i |
登録日 | $data['target_date'] | target_date_$i |
この対応関係は、次に示すmyself_multi_password_save_meta_box_dataでも利用します。
パスワードの保存・更新
投稿記事の保存時に、上記で説明したカスタムフィールドの情報もあわせて保存・更新するための関数を定義します。
処理自体は単純で、保存時に送信されるデータをから有効なもの(パスワードが設定されているもの)のみを連想配列に格納し、メタデータとして保存します。
実装結果は以下のようになります。
/**
* When the post is saved, saves our custom data Multi Password.
*
* @param int $post_id The ID of the post being saved.
* License: GPLv2 or later
*/
function myself_multi_password_save_meta_box_data($post_id) {
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Validate nonce, post_type, and max_id
if (!isset($_POST['myself_multi_password_nonce'], $_POST['post_type'], $_POST['max_id'])) {
return;
}
// Verify that the nonce is valid.
if (!wp_verify_nonce($_POST['myself_multi_password_nonce'], 'myself_multi_password_save_meta_box_data')) {
return;
}
// Check the user's permissions.
$valid_post_types = array(
'post' => 'edit_post',
'page' => 'edit_page',
);
$post_type = $_POST['post_type'];
if (!array_key_exists($post_type, $valid_post_types) || !current_user_can($valid_post_types[$post_type], $post_id)) {
return;
}
// 登録パスワード数
$max_id = $_POST['max_id'];
$multi_password = array();
$j = 0;
for ($i = 0; $i <= $max_id; $i++) {
$target_exp_date = isset($_POST['target_exp_date_' . $i]) ? sanitize_text_field($_POST['target_exp_date_' . $i]) : '';
$target_password = isset($_POST['target_password_' . $i]) ? sanitize_text_field($_POST['target_password_' . $i]) : '';
$target_memo = isset($_POST['target_memo_' . $i]) ? sanitize_text_field($_POST['target_memo_' . $i]) : '';
$target_date = isset($_POST['target_date_' . $i]) ? sanitize_text_field($_POST['target_date_' . $i]) : current_time('timestamp');
// target_password Max: 255 characters
$target_password = substr($target_password, 0, 255);
if ($target_password) {
$multi_password[$j] = array(
'target_exp_date' => $target_exp_date,
'target_password' => $target_password,
'target_memo' => $target_memo,
'target_date' => $target_date,
);
$j++;
}
}
// Update the meta field in the database.
update_post_meta($post_id, 'multi-password', $multi_password);
}
add_action('save_post', 'myself_multi_password_save_meta_box_data');
先ほど示した対応関係は、コード上の以下の部分に関係します。
$j = 0;
for ($i = 0; $i <= $max_id; $i++) {
$target_exp_date = isset($_POST['target_exp_date_' . $i]) ? sanitize_text_field($_POST['target_exp_date_' . $i]) : '';
$target_password = isset($_POST['target_password_' . $i]) ? sanitize_text_field($_POST['target_password_' . $i]) : '';
$target_memo = isset($_POST['target_memo_' . $i]) ? sanitize_text_field($_POST['target_memo_' . $i]) : '';
$target_date = isset($_POST['target_date_' . $i]) ? sanitize_text_field($_POST['target_date_' . $i]) : current_time('timestamp');
// target_password Max: 255 characters
$target_password = substr($target_password, 0, 255);
if ($target_password) {
$multi_password[$j] = array(
'target_exp_date' => $target_exp_date,
'target_password' => $target_password,
'target_memo' => $target_memo,
'target_date' => $target_date,
);
$j++;
}
}
設定方法・使い方
紹介した機能の有効方法と使い方について説明します。
機能を有効化する方法
上記に示したスクリプトをmy-plugin.php
に記載し、FTPやファイルマネージャー経由でサーバにアップロードしてください。
使い方
記事公開時に「表示状態」を「パスワード保護」とし、管理者用のパスワードを入力します。
その後、内容を確認後に「公開」を押下します。
その後、投稿記事の編集画面を開き、画面下部にある以下のエリアを探し、個別にパスワードを設定します。
パスワードは1つずつ設定することになるため、設定の度に保存&リロードが必要になります。
個別に保存したパスワードで限定ページにアクセスできていれば成功です。
まとめ
今回は、パスワード保護された記事に対し、訪問者ごとにパスワードを割り当てる方法について解説しました。
小規模であれば、今回の方法が利用できると思います。
また、大規模なケースでは、個別にパスワードを設定するのではなく、ユーザ登録機能を利用して、ユーザ単位でアクセス権を付けるなど、工夫する必要があります。
状況に応じて使い分けてみてください。
効率良く技術習得したい方へ
今回の話の中で、プログラミングについてよく分からなかった方もいると思います。
このような場合、エラーが発生した際に対応できなくなってしまうため、経験者からフォローしてもらえる環境下で勉強することをおすすめします。
詳細は、以下の記事をご覧ください。
【比較】プログラミングスクールおすすめランキング6選【初心者向け】
続きを見る