a-blog cms3.1がリリースされて、登録会員限定のエントリーの作成が以前よりも容易になりました。
ただ、それでも会員登録は必要になるので、WordPressのようにカジュアルにエントリーにパスワードを設定するにはカスタマイズが必要です。
今回は、a-blog cms付属の公式テーマ「utsuwa」を元に、エントリーにパスワードをかけるカスタマイズを試してみました。
この記事で作成、修正を行なったファイルです。
参考にご覧ください。
実用にあたっては、パスワードをハッシュ化するなどの対策を検討してください。
utsuwaテーマを継承して子テーマを作る
直接utsuwaテーマの中身を直接修正してもいいのですが、変更箇所がわかりにくくなり、何か問題あった場合に原因を突き止めるのが難しくなります。
そこで、元のファイルを残したまま修正を加えられるよう、utsuwaテーマを継承して子テーマpassword@utsuwa
を作成します。
CMSのインストールディレクトリーにあるthemes
ディレクトリーにpassword@utsuwa
というディレクトリーを作成してください。
次に、修正を加えるファイルを、utsuwaテーマから子テーマのディレクトリーにコピーしておきます。
- /admin/entry/field.html
- /include/entry/body.html
子テーマにあるファイルは親テーマのものより優先して読み込まれるため、差分のみを子テーマに持っておくことで、親テーマを変更することなくカスタマイズが可能です。
以下、基本的にこのディレクトリー内での作業になります。
子テーマのディレクトリーを作成したら、テーマを「password@utsuwa」に変更しておきます。
パスワードのカスタムフィールドを追加
パスワードがエントリーのカスタムフィールドとして保存されるようにしたいため、エントリーのカスタムフィールドを設定するファイル/admin/entry/field.html
を修正して、パスワードの入力欄を追加します。
<!-- SEOの設定 -->
@include("/admin/entry/field-seo.html")
<!-- URLコンテキスト別 -->
@include("/admin/entry/bcd/%{BCD}.html")
@include("/admin/entry/rccd/%{RCCD}.html")
@include("/admin/entry/ccd/%{CCD}.html")
<!-- パスワード設定 -->
@include("/admin/entry/password.html")
読み込むファイル/admin/entry/password.html
を作成。
カスタムフィールドのコード作成は、カスタムフィールドメーカーが便利です。
<!-- パスワード欄 -->
<table class="acms-admin-table-admin-edit">
<tr>
<th>エントリーのパスワード</th>
<td>
<input type="text" name="entry_password" value="{entry_password}" class="acms-admin-form-width-full" />
<input type="hidden" name="field[]" value="entry_password" />
<input type="hidden" name="entry_password:v#regex" value="^[a-zA-Z0-9]*$" id="entry_password-v-regex" />
<!-- BEGIN entry_password:validator#regex -->
<p class="acms-admin-text-error">半角英数を設定してください</p>
<!-- END entry_password:validator#regex -->
<input type="hidden" name="entry_password:c" value="a" />
</td>
</tr>
</table>
これで、エントリーの編集画面に「エントリーのパスワード」欄が追加されました。
エントリー用テンプレートを修正
ENTRY_BODYモジュールを分割
パスワードの状態によってエントリーの出しわけをするため、エントリー表示用のインクルードファイル/include/entry/body.html
を編集します。
<!-- BEGIN_MODULE Entry_Body id="{{module_id}}" -->
<!-- BEGIN notFound -->
<section class="acms-entry entry">
<h2 class="not-found-title">Not Found</h2>
<p>ただいまページを準備しております。もうしばらくお待ちください。</p>
</section>
<!-- END notFound -->
<!-- 1記事▼▼ -->
<!-- BEGIN entry:loop -->
<!-- BEGIN_SetRendered id="tag" -->
<!-- BEGIN tag:loop -->
<li class="list-inline-item"><a href="{url}" class="entry-tag-label">#{name}</a></li>
<!-- END tag:loop -->
<!-- END_SetRendered -->
(中略)
</article>
<!-- END entry:loop -->
<!-- 1記事▲▲ -->
<!-- ページャー -->
@include("/include/parts/pager.html")
<!-- END_MODULE Entry_Body -->
このファイルの<!-- BEGIN entry:loop -->
から<!-- END entry:loop -->
、entry:loopブロックの中身をカットし、別ファイル/include/entry/ex_body-content.html
に保存します。
<!-- BEGIN_SetRendered id="tag" -->
<!-- BEGIN tag:loop -->
<li class="list-inline-item"><a href="{url}" class="entry-tag-label">#{name}</a></li>
<!-- END tag:loop -->
<!-- END_SetRendered -->
(中略)
</article>
カスタムフィールドは、モジュールの特定のブロックまたはフィールドモジュール内で表示できます。
ENTRY_BODYモジュールの場合、entry:loopブロック内で{カスタムフィールド名}
として利用できるため、今回はその部分を切り出して別ファイルにし、エントリーのカスタムフィールドを条件分岐(IFブロック)で使用しやすいようにしました。
パスワードの状況によって表示が変わるよう、表示部分をカットしたbody.htmlを修正します。
<!-- BEGIN_MODULE Entry_Body id="{{module_id}}" -->
<!-- BEGIN notFound -->
<section class="acms-entry entry">
<h2 class="not-found-title">Not Found</h2>
<p>ただいまページを準備しております。もうしばらくお待ちください。</p>
</section>
<!-- END notFound -->
<!-- 1記事▼▼ -->
<!-- BEGIN entry:loop -->
<!-- パスワードがない場合 -->
<!-- BEGIN_IF [{entry_password}/em] -->
@include("/include/entry/ex_body-content.html", {"tag":"{{tag}}", "sns":"{{sns}}"})
<!-- ELSE -->
<!-- パスワードがある場合 -->
<!-- BEGIN_IF [{entry_password}/eq/%{input_password}] -->
<!-- パスワードが正解の場合 -->
@include("/include/entry/ex_body-content.html", {"tag":"{{tag}}", "sns":"{{sns}}"})
<!-- ELSE -->
<!-- パスワードが違う、または未入力の場合 -->
@include("/include/entry/password.html")
<!-- END_IF -->
<!-- END_IF -->
<!-- END entry:loop -->
<!-- 1記事▲▲ -->
<!-- ページャー -->
@include("/include/parts/pager.html")
<!-- END_MODULE Entry_Body -->
パスワードが未設定、もしくはパスワードが正しかった場合には/include/entry/ex_body-content.html
が、それ以外の場合は/include/entry/password.html
が表示されます。
@include
の{"tag":"{{tag}}", "sns":"{{sns}}"}
について。
読み込み元の/include/entry/body.html
ファイルが_entry.html
からincludeされる際に@include("/include/entry/body.html", {"module_id": "body_date", "tag":"on", "sns":"on"})
と、body_date、tagパラメーターが渡されています。
これらを/include/entry/ex_body-content.html
ファイルにそのまま引き渡すため、上記のような書き方にしました。
インクルードについては、公式ドキュメントを参照してください。
パスワードをクエリ文字列として送信
パスワード入力フォームの作成
パスワード入力欄を表示するファイル/include/entry/password.html
を作成します。
<h2>この記事を見るには、パスワードの入力が必要です。</h2>
<div class="acms-entry">
<form action="" method="GET" class="acms-form">
<div><input type="password" value="" name="input_password" class="acms-form-large" placeholder="パスワードを入力"></div>
<button type="submit" name="" class="acms-btn">パスワードを送信する</button>
<!-- BEGIN_IF [%{input_password}/nem/_and_/{entry_password}/neq/%{input_password}] -->
<p class="acms-text-error">パスワードが違います。</p>
<!-- END_IF -->
</form>
</div>
入力されたパスワードinput_password
は、クエリ文字列として送信されます。
a-blog cmsは、クエリ文字列をグローバル変数として扱うことができるため、これを利用してパスワードの確認を行います。
先にあげた/include/entry/body.html
のコードの
<!-- BEGIN_IF [{entry_password}/eq/%{input_password}] -->
の部分です。
エントリーのパスワード{entry_password}
と、入力されたパスワード%{input_password}
を比較し、インクルードするファイルを決定します。
これで、ひとまずエントリーにパスワード認証をかけることができました。
ただ、この状態だと、URLにパスワードが平文でついてしまっているのと、ページ遷移などでURLにクエリがない場合に毎回パスワードの入力が必要という問題があります。
パスワードをグローバル変数にする
次に、ユーザーが入力したパスワードをa-blog cmsのグローバル変数にして、ユーザーの目に触れないようにしてみます。
これらのページを参考に、グローバル変数を作成します。
config.server.phpの修正
config.server.php
にあるHOOK_ENABLE
を1に設定。
define('HOOK_ENABLE', 1);
Hook.phpの修正
続いて/extension/acms/Hook.php
を修正します。
「グローバル変数の拡張」とコメントされている部分を探して、
/**
* グローバル変数の拡張
*
* @param array $globalVars
*/
public function extendsGlobalVars(&$globalVars)
{
$postKey = "input_password";
if (isset($_POST[$postKey])) {
$password = $_POST[$postKey];
$globalVars->set("ENTRY_PASSWORD", $password);
}
}
これで、name="input_password"
でポストされたデータが、%{ENTRY_PASSWORD}
というグローバル変数で出力され、テンプレートから参照できるようになります。
カスタムのグローバル変数を参照するよう、テンプレートの修正
パスワード送信フォームにあるform要素のmethod属性をPOSTに変更し、参照しているグローバル変数を%{input_password}
から%{ENTRY_PASSWORD}
へ修正
<form action="" method="POST" class="acms-form">
<div><input type="password" value="" name="input_password" class="acms-form-large" placeholder="パスワードを入力"></div>
<button type="submit" name="" class="acms-btn">パスワードを送信する</button>
<!-- BEGIN_IF [%{ENTRY_PASSWORD}/nem/_and_/{entry_password}/neq/%{ENTRY_PASSWORD}] -->
<p class="acms-text-error">パスワードが違います。</p>
<!-- END_IF -->
</form>
次に、IF文の条件の比較対象を%{input_password}
から新しいグローバル変数%{ENTRY_PASSWORD}
に変更。
<!-- パスワードがある場合 -->
<!-- BEGIN_IF [{entry_password}/eq/%{ENTRY_PASSWORD}] -->
入力されたパスワードがグローバル変数となり、ユーザーの見える部分にパスワードが出ることは無くなりました。
ただ、これでは毎回パスワードを入力しなければならない問題は解決していません。
そこで、入力されたパスワードをcookieに保存するよう修正を加えてみます。
cookieを設定するようHook.phpを修正
グローバル変数の出力と合わせて、cookieを設定するように修正します。
以下、その一例です。
/**
* グローバル変数の拡張
*
* @param array $globalVars
*/
public function extendsGlobalVars(&$globalVars)
{
// URL情報の整理
// ドメイン(ポート番号がある場合、削除する)
$hostWithPort = $_SERVER['HTTP_HOST'];
$hostArray = explode(':', $hostWithPort);
$domain = $hostArray[0];
// リクエストされたURIを取得
// クエリストリングを取り除く(存在する場合)
$requestUri = $_SERVER['REQUEST_URI'];
$requestPath = parse_url($requestUri, PHP_URL_PATH);
// POSTされる名前
$postKey = "input_password";
// cookie名
$entryCookieName = "entry_pass";
// ポストデータがある場合
if (isset($_POST[$postKey])) {
// パスワードのサニタイズ
$encFlags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5;
$password = htmlentities($_POST[$postKey], $encFlags, "UTF-8");
if (!empty($password)) {
$limit = time() + 60 * 60 * 24 * 7;
setcookie($entryCookieName, $password, $limit, $requestPath, $domain, true, true);
// グローバル変数の設定
$globalVars->set("ENTRY_PASSWORD", $password);
}
// ポストデータがない場合
} else {
// cookieを取得
if (isset($_COOKIE[$entryCookieName])) {
$userCookie = $_COOKIE[$entryCookieName];
// グローバル変数の設定
$globalVars->set("ENTRY_PASSWORD", $userCookie);
}
}
}
これで、ページごとにパスワードを一定期間保存して、アクセスするたびにパスワードの入力が求められないようになりました。
この例では、パスワードは平文のまま、エントリーごとに保存できるようになっています。
cookieの設定については、パスを指定しないようにしたり、ハッシュ化したりするなど、サイトの運用ポリシーに合わせて対応を検討してください。
やり残し……一覧ページの対応
個別のエントリーページについては対応できましたが、カテゴリートップや検索結果など、一覧ページでENTRY_SUMMARYやENTRY_BODYモジュールを使っている場合、本文の一部が一覧に表示される可能性があります。
上記のように条件分岐を一覧ページのテンプレートに入れることで、パスワード付きエントリーのみ制限した表示を出す、ということが可能です。
必要に応じてカスタマイズをしてみてください。