LoginSignup
0
0

a-blog cmsで、エントリーにパスワードを設定する

Posted at

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を修正して、パスワードの入力欄を追加します。

/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を作成。
カスタムフィールドのコード作成は、カスタムフィールドメーカーが便利です。

/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>

スクリーンショット 2023-12-07 16.33.15.png

これで、エントリーの編集画面に「エントリーのパスワード」欄が追加されました。

エントリー用テンプレートを修正

ENTRY_BODYモジュールを分割

パスワードの状態によってエントリーの出しわけをするため、エントリー表示用のインクルードファイル/include/entry/body.htmlを編集します。

/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に保存します。

/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を修正します。

/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_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を作成します。

/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に設定。

CMSインストールルート/config.server.php
define('HOOK_ENABLE', 1);

Hook.phpの修正

続いて/extension/acms/Hook.phpを修正します。
「グローバル変数の拡張」とコメントされている部分を探して、

CMSインストールルート/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}へ修正

/include/entry/password.html(抜粋)
	<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}に変更。

/include/entry/body.html(抜粋)
<!-- パスワードがある場合 -->
  <!-- BEGIN_IF [{entry_password}/eq/%{ENTRY_PASSWORD}] -->

入力されたパスワードがグローバル変数となり、ユーザーの見える部分にパスワードが出ることは無くなりました。
ただ、これでは毎回パスワードを入力しなければならない問題は解決していません。
そこで、入力されたパスワードをcookieに保存するよう修正を加えてみます。

cookieを設定するようHook.phpを修正

グローバル変数の出力と合わせて、cookieを設定するように修正します。
以下、その一例です。

CMSインストールルート/extension/acms/Hook.php
   /**
     * グローバル変数の拡張
     *
     * @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モジュールを使っている場合、本文の一部が一覧に表示される可能性があります。
上記のように条件分岐を一覧ページのテンプレートに入れることで、パスワード付きエントリーのみ制限した表示を出す、ということが可能です。
必要に応じてカスタマイズをしてみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0