さぁ!検索しよう!

ECサイトの仕組みが知りたい&WordPressでプラグインを利用せずにECサイトのCMSを構築してみたいと思い、このような記事を書くに至りました。

前回に続いて第5回は、ログイン機能を実装する方法です。

尚、index.phpやsingle.php等のテンプレートは既に用意されているものとします。

ログインページを作成する

ログインページは固定ページで作成します。なので始めに固定ページのテンプレートlogin.phpを作成します。

functions.php

<?php
/*
Template Name: login
*/
?>
<?php get_header(); ?>
<?php 
if(!isset($_SESSION['href'])) $_SESSION['href'] = esc_url($_SERVER['HTTP_REFERER']);
?>
<div id="primary" class="content-area">
    <main id="main" class="site-main" role="main">
        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <header class="entry-header">
                <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
            </header><!-- .entry-header -->
            <div class="entry-content">
                <div class="mx-auto bg-light p-5 border rounded">
                    <form action="<?php the_permalink(); ?>" method="post">
                    <?php wp_nonce_field('login', 'login_nonce') ?>
                    <?php if(!empty($error['global'])): ?>
                        <p class="text-danger"><?php echo $error['global']; ?></p>
                    <?php elseif(!empty($error['login'])): ?>
                        <p class="text-danger"><?php echo $error['login']; ?></p>
                    <?php endif; ?>
                        <div class="form-group mb-2">
                            <input type="email" name="lemail" id="loginemail" value="<?php echo @$email; ?>" placeholder="メールアドレス" class="form-control">
                            <?php if(!empty($error['email'])): ?>
                                <p class="text-danger"><?php echo $error['email']; ?></p>
                            <?php endif; ?>
                        </div>
                        <div class="form-group">
                            <input type="password" name="lpassword" id="loginpassword" placeholder="パスワード" class="form-control" value="<?php echo @$password; ?>">
                            <?php if(!empty($error['password'])): ?>
                                <p class="text-danger"><?php echo $error['password']; ?></p>
                            <?php endif; ?>
                        </div>
                        <input type="submit" name="submit" value="ログイン" class="btn btn-primary">
                    </form>
                    <div class="mt-1">
                        <a href="<?php echo esc_url( home_url( '/' ). "signup") ?>">初めてご利用の方(新規会員登録)</a>
                    </div>
                </div>      
            </div><!-- .entry-content -->
        </article><!-- #post-## -->
    </main><!-- .site-main -->
</div><!-- .content-area -->
<?php get_footer(); ?>

セッション変数$_SESSION['href']にはログインページの一つ前に表示していたページのURL$_SERVER['HTTP_REFERER']を保存しておきます。このURLはログイン後の処理で必要となります。

固定ページのテンプレート名はloginとするので、ページ先頭で「/* Template Name: login */」とコメントアウトします。

formの部分がログインフォームです。

ログインはメールアドレスとパスワードで行い、入力エラーがあったときはinput要素の下にエラーメッセージを表示します。更に、input要素のvalue属性には、入力した値を指定してエラー後も入力した内容を残すようにしています。

エラーメッセージやvalue属性に指定されている$error['email']@$emailなどの変数は後で定義します。また、$email$passwordの前に@が付いていますが、これは警告の表示を防ぐためのものです。何も代入されていない変数を指定すると警告が表示されるのです。

会員登録がまだお済みでない方のために、「初めてご利用の方(新規会員登録)」ボタンをログインフォームの下に表示します。このボタンのリンク先はecho esc_url( home_url( '/' ). "signup")で「http://[ドメイン名]/signup」となります。signup.phpについては新規会員登録機能の実装方法をご覧ください。

最後に、固定ページでログインページを作成します。

タイトルは「ログイン」でテンプレートは先ほど作成した「login」を選択します。更に、パーマリンクを「http://[ドメイン名]/login」とします。

これでログインページは完成です。

完成したログインページ

ログイン認証を行う

ここからが本題となります。ログインフォームにログイン情報を入力してログインボタンを押した後にログイン認証を行い、認証に成功した場合はログインし、失敗した場合はログインをキャンセルする処理を行います。

functions.phpにlogin_init関数を定義しますが、その前にadd_action()login_init関数をtemplate_redirectフックに登録します。

functions.php

add_action('template_redirect', 'login_init');

ログインフォームformaction属性にはthe_permalink()を指定しているため、ログイン情報を入力してログインボタンが押されるとページがリロードされますが、template_redirectフックによってリロードが終わる前に(ページが表示される前に)login_init関数が実行されるようになります。この間にログイン認証を行うのです。

ではlogin_init関数を定義します。

functions.php

function login_init() {

    if(!is_page('login')) {
        return;
    }

    if(@$_SERVER['HTTP_REFERER'] === null) {
        wp_safe_redirect(home_url('/'));
        exit;
    }

    global $email, $error, $password;
    $email = "";
    $error = array();

    if(isset($_POST['login_nonce'])) {
        if(!wp_verify_nonce($_POST['login_nonce'], 'login')) {
            $error['global'] = '不正な遷移です。';
        }else{
            if(!empty($_POST['lemail'])) {
                $email = esc_attr($_POST['lemail']);
            }else{
                $error['email'] = "メールアドレスを入力してください。";
            }
            if(!empty($_POST['lpassword'])) {
                $password = esc_attr($_POST['lpassword']);
            }else{
                $error['password'] = "パスワードを入力してください。";
            }
        }
        if(empty($error)) {
            $user = get_user_by('email', $email);
            if($user === false) {
                $error['email'] = "メールアドレスが間違っています。";
            }else{
                $creds = array();
                $creds['user_login'] = $user->data->user_login;
                $creds['user_password'] = $password;
                $creds['remember'] = true;
                $user = wp_signon($creds, false);
                if(is_wp_error($user)) {
                    if(array_key_exists('incorrect_password', $user->errors)) {
                        $error['password'] = "パスワードが間違っています。";
                    }else{
                        $error['login'] = "ログインに失敗しました。";
                    }
                }else{
                    if($_SESSION['href'] == home_url('login') || $_SESSION['href'] == home_url('signup') || $_SESSION['href'] == home_url('mypage') || $_SESSION['href'] == home_url('mypage/update')) {
                        wp_safe_redirect(home_url( '/' ));
                    }else {
                        wp_safe_redirect($_SESSION['href']);
                    }
                }
            }
        }
    }
}

解説

ログインページでのみ処理を行うようにする

表示するページがログインページでなければlogin_init関数から抜けてそれ以降の処理は行いません。

if(!is_page('login')) {
    return;
}

直接URLでアクセスできないようにする

遷移前のページが見当たらない、つまりアドレスバーからURLを入力してログインページを表示した場合は、トップページにリダイレクトさせてlogin_init関数を終了させます。

if(@$_SERVER['HTTP_REFERER'] === null) {
    wp_safe_redirect(home_url('/'));
    exit;
}

グローバル変数を宣言する

実は、先程ログインページのテンプレートlogin.phpの中で使用されていた$email$error等の変数はこの関数内で宣言されたものです。ですが、ただ宣言された変数ではありません。

$email$error等の変数はこの関数内だけでなく関数外でも使用するものであるため、グローバル変数として宣言する必要があるのです。

関数内でグローバル変数を宣言する場合は、始めに変数の前でglobalという命令を使います。その後、それぞれの変数に値を設定します。

global $email, $error, $password;
$email = "";
$error = array();

宣言した変数はそれぞれ、入力したメールアドレスが入る$emailと入力したパスワードが入る$password、そしてエラーメッセージが入る$errorとなっています。

また、初期設定として$email$passwordには空文字、$errorには空の配列を設定しています。

入力した値を判定する

ログインボタンが押されたときに、入力した値をあらゆる方法で判定し、誤りがあれば入力欄の下にエラーメッセージを表示してログインをキャンセルする処理を行います。

判定方法は以下の通りです。

nonceの判定

入力した値の前に送られてきたnonceの判定を行います。nonceに値が入っているが、設定したnonceでなければエラーとなり「不正な遷移です。」と表示されるようにします。

if(isset($_POST['login_nonce'])) {
    if(!wp_verify_nonce($_POST['login_nonce'], 'login')) {
        $error['global'] = '不正な遷移です。';
    }

逆であれば正しいnonceということで次の判定に進みます。

入力しているかどうかの判定

送られてきた入力した値が空であればメールアドレス(パスワード)を入力していないということなので、「メールアドレス(パスワード)を入力してください。」と表示されるようにします。

else{
    if(!empty($_POST['lemail'])) {
        $email = esc_attr($_POST['lemail']);
    }else{
        $error['email'] = "メールアドレスを入力してください。";
    }
    if(!empty($_POST['lpassword'])) {
        $password = esc_attr($_POST['lpassword']);
    }else{
        $error['password'] = "パスワードを入力してください。";
    }
}

反対に空でなければ入力したということなので、入力した値を$email($password)に代入します。

登録されたメールアドレスかどうかの判定

ここまでエラーがなければ、get_user_by関数でメールアドレスからユーザー情報を取得し、これを$userとします。

if(empty($error)) {
        $user = get_user_by('email', $email);

そして、get_user_by関数はユーザー情報が見つからなければfalseが返るため、このユーザー情報がfalseである場合は、登録されていないメールアドレスを入力したということであるため、「メールアドレスが間違っています。」と表示されるようにします。

if($user === false) {
                $error['email'] = "メールアドレスが間違っています。";
            }

反対に、trueが返りユーザー情報が見つかった場合は、登録したメールアドレスということで次の判定に進みます。

正しいパスワードであるかどうかの判定

先程メールアドレスから取得したユーザー情報からユーザーのログイン名'user_login'と入力したパスワード$passwordとログイン情報保持の有無'remember'$creds配列に格納し、wp_signon関数でユーザー認証を行います。

else{
    $creds = array();
    $creds['user_login'] = $user->data->user_login;
    $creds['user_password'] = $password;
    $creds['remember'] = true;
    $user = wp_signon($creds, false);

wp_signon関数はユーザー認証が失敗するとWP_Errorオブジェクトを返すので、is_wp_error関数に$userを渡して判定します。
is_wp_error($user)がtrueであればユーザー認証に失敗したということになります。更にユーザー情報のエラー情報が格納されている$user->errors内に'incorrect_password'キーがあれば、パスワードが間違っているという事であるため、これをarray_key_exists()で調べます。array_key_exists()がtrueが返ればパスワードが間違っているということになるため、「パスワードが間違っています。」と表示します。

if(is_wp_error($user)) {
    if(array_key_exists('incorrect_password', $user->errors)) {
        $error['password'] = "パスワードが間違っています。";
    }

そうでなければ、別の原因でユーザー認証に失敗したということで「ログインに失敗しました。」と表示されるようにします。

}else{
    $error['login'] = "ログインに失敗しました。";
}

ログインに成功した場合の処理

これまでの判定を全て潜り抜けた場合はユーザー認証に成功、つまりログインに成功したということになります。おめでとうございます。

ログイン成功後は、ログインのリンクを踏んだときに表示されていたページ(ログインページの前に表示していたページ)にリダイレクトしますが、ログインページの前に表示していたページがログインページや新規会員登録ページのようなログイン後に表示すべきではないページであればトップページにリダイレクトさせます。

if($_SESSION['href'] == home_url('login') || $_SESSION['href'] == home_url('signup')) {
    wp_safe_redirect(home_url( '/' ));
}else {
    wp_safe_redirect($_SESSION['href']);
}

login_init関数については以上です。

ログイン後に「ようこそ○○さん」と表示させる

ログイン後はサイトロゴの下に「ようこそ○○さん」と表示させます。

header.phpのロゴとして定義している要素の下に以下のコードを追加します。

header.php

<?php 
    $current_user = wp_get_current_user(); 
    if($current_user->display_name):
?>
    <p class="mt-1">ようこそ、<a href="<?php echo home_url().'/mypage'; ?>"><?php echo $current_user->display_name; ?></a>様</p>
<?php endif; ?>

wp_get_current_user()で現在ログイン中のユーザー情報を取得し、その情報の中にある表示名display_nameの項目に値が設定されていれば、その表示名を出力しています。リンク先はマイページを指定します。

メニューにログインページのリンクを追加する

先程作成したログインページへのリンクをメニューの項目に追加します。まだメニューを作成していない方は先に【WordPress】カスタムメニュー機能を利用してメニューを作成して表示する方法でメニューを作成・表示してください。

これで、メニューの項目にログインページのリンクが追加されました。

ログイン後はログイン中のメニューに切り替える

ログイン後であるにも関わらずメニューに「ログイン」や「新規会員登録」といった項目が含まれていてはマズいので、ログイン時のメニューを別に作成し、ログイン後はそのメニューに切り替えます。

先程記述したregister_nav_menus()にログイン時のメニューの「場所」を追加するコードを追加します。

functions.php

register_nav_menus( array(
    'main' => 'メインメニュー',
    'main-logged-in' => 'メインメニュー(ログイン時)'//追加
) );

これで「メニュー設定」にログイン時のメニューの「場所」が新たに追加されました。

次に、「新規メニューを作成」のリンクから以下のようにログイン時のメニューを作成します。

そして、「メニュー名」、「メニュー構造」、そして「メニューの位置」を決めてメニューを保存します。

ログイン時のメニューで必要最低限の項目は固定ページの「マイページ」と「カート」なので、それぞれメニュー構造に追加します。

最後に作成したメニューを表示させますが、このメニューはログイン中のみ表示するため、以下のコードでログアウト時・ログイン時でメニューを切り替えます。

先程sidebar.phpに記述したログアウト時のメニューを表示するコードを以下に変更します。

sidebar.php

<?php
    if(is_user_logged_in()) {
        wp_nav_menu(array(
            'theme_location' => 'main-logged-in'
        ));
?>
<?php if($_SERVER['REQUEST_URI'] == '/mypage' || $_SERVER['REQUEST_URI'] == '/mypage/update' || $_SERVER['REQUEST_URI'] == '/mypage/update/confirm'): ?>
    <p><a href="<?php echo wp_logout_url(home_url('login')); ?>">ログアウト</a></p>
<?php else: ?>
    <p>
        <a href="<?php echo wp_logout_url(); ?>&redirect_to=<?php echo esc_attr($_SERVER['REQUEST_URI']) ?>">ログアウト</a>
    </p>
<?php endif; ?>         
<?php
    }else{
        wp_nav_menu(array(
            'theme_location' => 'main'
        ));
    }
?>

ログイン時のメニューを表示する

if(is_user_logged_in()) {}で、ログイン中であればログイン時のメニューを表示します。

<?php
if(is_user_logged_in()) {
    wp_nav_menu(array(
        'menu_class' => 'nav-menu',
        'theme_location' => 'logged-in-menu',
    ));
?>

また、メニューの下に「ログアウト」のリンクを表示し、クリックするとログアウトされるようにします。

<?php 
    if($_SERVER['REQUEST_URI'] == '/mypage' || $_SERVER['REQUEST_URI'] == '/mypage/update' || $_SERVER['REQUEST_URI'] == '/mypage/update/confirm'):
?>
    <p>
        <a href="<?php echo wp_logout_url(home_url('login')); ?>">ログアウト</a>
    </p>
<?php else: ?>
    <p>
        <a href="<?php echo wp_logout_url(); ?>&redirect_to=<?php echo esc_attr($_SERVER['REQUEST_URI']) ?>">ログアウト</a>
    </p>
<?php endif; ?>

ログアウト後は、ログアウト直前まで表示していたページにその後も表示しても支障がなければリダイレクトします。そうではなくログアウト直前のページがマイページや購入画面などログイン時でなければ表示できないページであれば、ログインページにリダイレクトします。

表示されたログイン中のメニュー

ログアウト時のメニューを表示する

if(is_user_logged_in())がfalseであれば、ログアウト時のメニューを表示します。

<?php
    }else{
        // Primary navigation menu.
        wp_nav_menu( array(
            'menu_class'     => 'nav-menu-d',
            'theme_location' => 'primary',
        ) );
    }
?>

以上でログイン機能を実装する方法を終わります。

参考文献