Craft CMS で会員限定ページを作成してみよう(ユーザー登録編)

これは Craft CMS Advent Calendar 2017 19日目の記事です。

Craft CMS では、特定ユーザーのみにページの閲覧制限を行う「会員制サイト」の作成を Pro ライセンスの購入テンプレートの調整 で比較的簡単に実現することができます。

そこで、今回はフロントエンドでユーザー登録を行い、ログイン・ログアウトできるようにするまでの流れを解説します。

Pro ライセンスへのアップグレード

はじめに、ユーザーアカウントやユーザーグループの追加が必須となるため、メインカラムのフッタにある PERSONAL ボタンをクリックし、ポップアップに表示されているボタンから Pro にアップグレードします。

ライセンスの選択画面

なお、ホスト名が下記のいずれかに該当すれば開発環境は無料で試用できますので、頭に入れておきましょう。

  • localhostcraftcms.mamp など、明らかに開発環境だとわかるホスト名
  • test-craft.example.jp のように、サブドメインに test など開発環境と認識される文字列が含まれる

ユーザーグループの作成

次に、新しいユーザーグループを追加します。

設定画面

グローバルナビゲーションから 設定 に移動し、ユーザー アイコンをクリックします。

ユーザー設定の一覧画面

ユーザー設定の一覧に移動したら、メインカラム下段にある 新しいユーザー・グループ ボタンをクリックします。

新しいユーザグループを作る

ユーザーグループの作成画面では、以下の内容を入力します。

ラベル
名前一般会員
ハンドルmember

今回はログイン中のみ閲覧可能になるページを用意するのが目的のため、 権利 から下にある権限に関する設定は必要ありません。

フロントエンドからのユーザー登録を許可

ユーザー設定のローカルナビゲーションにある 設定 をクリックします。

ユーザー設定の設定画面

公的登録を許可しますか? のチェックボックスを ON にし、デフォールトのユーザグループ のプルダウンで直前に作成したユーザーグループを選択して保存します。

これで自作のテンプレートファイルに定義したフォームから、ユーザー登録が可能な状態になりました。

テンプレートファイルの構成

テンプレートの編集にあたり、ディレクトリ構成を確認しておきましょう。

craft
├── config/
│   └── general.php              # 設定ファイル
└── templates/
    ├── index.twig               # 既存のトップページ
    ├── _layout_members.twig     # ユーザー登録関連の共通レイアウト
    └── members
        ├── forgotpassword.twig  # パスワードの再設定リクエスト
        ├── login.twig           # ログイン
        ├── register.twig        # 新規登録
        └── setpassword.twig     # 新しいパスワードの設定

ここでは、会員登録やログインに関するテンプレートを craft/templates/members にまとめることにしますので、あらかじめ members ディレクトリを作成しておいてください。

設定ファイルの調整

ログイン、ログアウト、新しいパスワードの設定画面の URL に members/ を付加するため、general.php を編集しパスを指定します。

return array(
  '*' => array(
    // (中略)
    'loginPath' => 'members/login',
    'logoutPath' => 'members/logout',
    'setPasswordPath' => 'members/setpassword',
    'enableCsrfProtection' => true,
  )
);

あわせて、 'enableCsrfProtection' => true で CSRF 対策を有効にしておきます。

テンプレートファイルの作成

次に、それぞれのテンプレートファイルを用意します。
サンプルには JS のバリデーション処理を含めていませんので、適宜調整してください。

ひな型:共通レイアウト

_layout_members.twig というファイル名で craft/templates 直下に保存します。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>会員サイト</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
  <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

最低限の見た目を整えるため、 Bootstrap 4 betajQuery 3.2.1 の読み込みに加え {% block content %} を定義しただけのシンプルな内容です。

フォーム:ユーザー登録

register.twig というファイル名で craft/templates/members 直下に保存します。
各フォームのマークアップはあくまでサンプルですが、各入力項目の name 属性値は固定となります。

{% extends "_layout_members" %}

{# エラー文の出力内容を macro に登録 #}
{% macro errorList(errors) %}
  {% if errors %}
    {% for error in errors %}
      <div class="alert alert-danger mt-1" role="alert">
        {{ error | replace('/Username/', 'ユーザーネーム') | replace('/Email/', 'メールアドレス') }}
      </div>
    {% endfor %}
  {% endif %}
{% endmacro %}

{# 直前でセットした macro を読込 #}
{% from _self import errorList %}

{% block content %}
  <div class="container mt-5">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <h1 class="h2">ユーザー登録</h1>

        <form method="post" accept-charset="UTF-8">
          {{ getCsrfInput() }}
          <input type="hidden" name="action" value="users/saveUser">
          <input type="hidden" name="redirect" value="/members/login">

          <div class="form-group">
            <label for="username">ユーザーネーム</label>
            <input id="username" type="text" name="username"{%- if account is defined %} value="{{ account.username }}"{% endif -%} class="form-control">

            {% if account is defined %}
              {{ errorList(account.getErrors('username')) }}
            {% endif %}
          </div>

          <div class="form-group">
            <label for="email">メールアドレス</label>
            <input id="email" type="text" name="email"{%- if account is defined %} value="{{ account.email }}"{% endif %} class="form-control">

            {% if account is defined %}
              {{ errorList(account.getErrors('email')) }}
            {% endif %}
          </div>

          <div class="form-group">
            <label for="password">パスワード</label>
            <input id="password" type="password" name="password" class="form-control">

            {% if account is defined %}
              {{ errorList(account.getErrors('password')) }}
            {% endif %}
          </div>

          <button type="submit" class="btn btn-primary btn-block">上記の内容でユーザー登録</button>
        </form>
      </div>
    </div>
  </div>
{% endblock %}

エラー表示用のマークアップは共通ですので、あらかじめ macro タグで登録し、実際の出力箇所で呼び出し。{% if account is defined %} は既存アカウントとの重複チェックの役割を果たしています。

{{ getCsrfInput() }} は CSRF 対策用のコードが自動挿入されるため、必須です。
また、<input type="hidden" name="redirect" value="/members/login"> には登録完了時のリダイレクト先を指定します。

フォーム:ログイン

login.twig というファイル名で craft/templates/members 直下に保存します。

{% extends "_layout_members" %}

{% block content %}
  <div class="container mt-5">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <h1 class="h2">ログイン</h1>

        {% if errorMessage is defined %}
          <div class="alert alert-danger" role="alert">
            {{ errorMessage }}
          </div>
        {% endif %}

        <form method="post" accept-charset="UTF-8">
          {{ getCsrfInput() }}
          <input type="hidden" name="action" value="users/login">
          <input type="hidden" name="redirect" value="/">

          <div class="form-group">
            <label for="loginName">ユーザーネーム、または、メールアドレス</label>
            <input id="loginName" type="text" name="loginName" value="{{ craft.session.rememberedUsername }}" class="form-control">
          </div>

          <div class="form-group">
            <label for="password">パスワード</label>
            <input id="password" type="password" name="password" class="form-control">
          </div>

          <div class="checkbox">
            <label>
              <input type="checkbox" name="rememberMe" value="1"> ユーザーネームを記憶する
            </label>
          </div>

          <button type="submit" class="btn btn-primary btn-block">ログイン</button>
        </form>

        <p class="mt-3"><a href="/members/forgotpassword">パスワードを忘れた方</a></p>
      </div>
    </div>
  </div>
{% endblock %}

ログイン画面で留意することは、記憶しているユーザーネームの出力用に input[name="loginName"] の value へ {{ craft.session.rememberedUsername }} をセットしておく点です。

フォーム:パスワード再設定のリクエスト

forgotpassword.twig というファイル名で craft/templates/members 直下に保存します。

{% extends "_layout_members" %}

{% block content %}
  <div class="container mt-5">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <h1 class="h2">パスワード再設定のリクエスト</h1>

        {% if errors is defined %}
          {% for error in errors %}
            <div class="alert alert-danger" role="alert">
              {{ error | replace('/ユーザー名/', 'ユーザーネーム') | replace('/email/', 'メールアドレス') }}
            </div>
          {% endfor %}
        {% endif %}

        <form method="post" accept-charset="UTF-8">
          {{ getCsrfInput() }}
          <input type="hidden" name="action" value="users/sendPasswordResetEmail">

          <div class="form-group">
            <label for="loginName">ユーザーネーム、または、メールアドレスを入力してください</label>
            <input id="loginName" type="text" name="loginName" value="{% if loginName is defined %}{{ loginName }}{% else %}{{ craft.session.rememberedUsername }}{% endif %}" class="form-control">
          </div>

          <button type="submit" class="btn btn-primary btn-block">パスワード再設定用のメールを送信</button>
        </form>
      </div>
    </div>
  </div>
{% endblock %}

パスワード再設定のリクエスト画面は、このままコードを流用する形になると思います。
なお、このファイルに限らず {{ error | replace('/foo/', 'bar') }} でエラー文を置換している箇所は、お好みで調整してください。

このフォームが送信されると、入力したユーザーにひもづくメールアドレスにパスワード再設定用の URL を記載したメールが届きます。

フォーム:新しいパスワードの設定

setpassword.twig というファイル名で craft/templates/members 直下に保存します。

{% extends "_layout_members" %}

{% block content %}
  <div class="container mt-5">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <h1 class="h2">新しいパスワードの設定</h1>

        {% if errors is defined %}
          {% for error in errors %}
            <div class="alert alert-danger" role="alert">
              {{ error | replace('/ユーザー名/', 'ユーザーネーム') | replace('/email/', 'メールアドレス') }}
            </div>
          {% endfor %}
        {% endif %}

        <form method="post" accept-charset="UTF-8">
          {{ getCsrfInput() }}
          <input type="hidden" name="action" value="users/setPassword">
          <input type="hidden" name="code" value="{{ code }}">
          <input type="hidden" name="id" value="{{ id }}">

          <div class="form-group">
            <label for="newPassword">新しいパスワードを入力してください</label>
            <input id="newPassword" type="password" name="newPassword" class="form-control">
          </div>

          <button type="submit" class="btn btn-primary btn-block">新しいパスワードを設定</button>
        </form>
      </div>
    </div>
  </div>
{% endblock %}

input[name="code"]input[name="id"] には、パスワード再設定用の URL に含まれるパラメータがセットされます。

トップページ

Craft CMS 2.x であれば craft/templates/index.twig がデフォルトで存在するハズですので、そちらを開き {% block content %} の任意の箇所に下記のコードを追記します。

{% if currentUser %}
  ようこそ {{ currentUser.username }} さん<br>
  <a href="{{ logoutUrl }}">ログアウト</a>
{% else %}
  <a href="{{ loginUrl }}">ログイン</a><br>
  <a href="/members/register">ユーザー登録</a>
{% endif %}

ログイン状況に応じて、リンクが表示されます。

動作の確認

ユーザー登録して、ログインが完了するまでの流れを見てみましょう。

ユーザー登録フォーム

ユーザー登録フォームを送信後 アカウントを有効にする という件名のメールが届きますので、記載された URL をクリックしておきます。

ログインフォーム

ログインしてみましょう。

ログイン後のトップページ

ログアウト後のトップページ

ログイン状態により、トップページの表示が切り替わっているのが確認できます。

まとめ

Craft CMS で会員制サイトを構築するにあたり、ユーザー登録に関わる設定方法やテンプレート制作のポイントについてまとめてみました。

初見では複雑そうに感じるかもしれませんが、流れをつかめば時間をかけずに実装できますので、ぜひチャレンジしてみてください。

comments powered by Disqus