워드프레스는 기본적으로 사용자 이름(username)과 이메일을 이용해서 로그인을 할 수 있도록 하고 있죠. 사용자 이름(username)도 복잡하다고 이메일 하나로 달랑 하나로 로그인하도록 만든 사이트도 많이 있습니다.
그런데 우리나라는 이메일 사용 및 중요도가 상대적으로 떨어진다고 할 수 있습니다. 이메일 계정없이도 편하게 살 수 있는 곳이 우리나라죠. 외국의 경우 이메일 계정이 없으면 불편한 일이 있습니다.
그리고 외국은 비밀번호 재설정 등을 모두 이메일을 통해서 합니다. 별도 페이지를 만들어 관리하는 경우가 많지 않죠.
1. 한국, 이메일보다는 전화번호
아무튼 이러한 외국과 달리 우리나라는 이메일대신 전하번호가 더 중요시 되기 때문에, 그리고 더욱 중요한 것은 모든 전화전화는 대부분 신원 조회가 완료된 것이기 때문에 보다 안전하게 사용하 수 있다는 장점도 있습니다.
그렇기 때문에 쇼핑몰을 비롯한 회원제 사이트를 고민하다보니 전화번로를 이용한 로그인 방법에 대한 니즈가 많이 있습니다.
우리나라 일부 회원제 사이트에서는 오히려 전화번호를 이용한 로그인을 적극적으로 사용하고 있습니다. 물론 개인정보 이슈가 있기는 하지만 고객 동의를 많이 강요하는 우리나라에서 그리 어려운 문제도 아닌 듯 합니다.
2. 전화번호 이용, 로그인 방법을 찾아보다..
그래서 여기에서는 워드프레스에서 전화번호를 이용해 로그인하는 방법(?)에 대해서 살펴봅니다.
워드프레스에서 전화번호를 이용해 로그인하는 방법은 니즈가 많음에도 불구하고 명확한 방법을 알려주는 문서를 찾지는 못했습니다.
간혹 전화번호를 이용한 로그인방법을 설명하는 자료는 있지만 저의 경우 작동하지 않았습니다.
그래서 워드프레스에서 전화번호로 로그인 가능토록 커스텀 서비스를 요청할까 생각도 해보았습니다. 그러나 생각외로 이런 커스텀 서비스를 하겠다는 곳을 찾지는 못했습니다.
몇군데 커스텀 서비스를 요청했는데 답을 주지 않더군요.
3. AJAX Login and Registration modal popup PRO
그러다가 발견한 것이 AJAX Login and Registration modal popup PRO라는 플러그인인데요.
물론 이 플러그인은 무료버젼도 있습니다. 하지만 전화번호 필드 추가와 같은 기능을 사용하려면 프로 버젼을 구매해야 합니다.
1 사이트당 가격은 19.9$로 나름 경쟁력 있다고 보여지구요. 팝업 로그인을 지원하는 플러그인은 15~25$ 정도에 가격이 형성되어 있는데요. 기능과 서비스를 고려하면 아주 괜찮은 플러그인이라고 생각합니다.
이 플러그인을 사용하기전에 몇가지 로그인 및 회원가입 플러그인을 구입하고 환불도 해봤는데요. 단가가 낮아서 그런제 모르지만 대부분 고객 응대 수준이 매우 낮았습니다.
반면 오늘 언급하는 AJAX Login and Registration modal popup PRO의 경우는 굉장히 적극적으로 요청 사항에 처리해 주었습니다.
원래 이 플러그인에는 전화번호 로그인 방법이 없었는데 관련 문의를 하니 가능한 코드를 제공해 주었습니다.
오늘 소개하는 코드는 AJAX Login and Registration modal popup PRO에서 작동하지만 그 원리를이용하면 다른 플러그인 또는 플러그인없이 회원가입폼을 만든 경우에도 적용할 수 있지 않을까 싶습니다.
3.1. 전화번호 로그인을 가능케하는 php code
AJAX Login and Registration modal popup PRO는 아래와 같은 php code를 사용해 전화번호 로그인을 가능케 해줍니다.
/* * 전화번호로 로그인하기 * phone 필드 추가 * 자료 : https://gist.github.com/max-kk/83c30c3e0be6d967d0c0e733e4e1a338#file-login_via_phone-php */ // ONLY IF you want the user login via phone-number // COPY after add_filter('lrm/login_info_filter', function ($info) { $user_login = $info['user_login']; // Optionally $user_login = str_replace(["+", "-", " "], '', $info['user_login']); // Commnets this to allow phone duplicates $users = get_users(array( 'meta_key' => 'phone', 'meta_value' => $user_login, )); if ( $users ) { $info['user_login'] = $users[0]->user_login; } return $info; });
3.2. 전화번호 필드 추가
AJAX Login and Registration modal popup PRO 플러그인은 다양한 방법으로 전화번호 필드를 추가할 수 있습니다.
우리가 많이하는 functions.php에 php code를 추가할 수도 있고, AJAX Login and Registration modal popup PRO 플러그인 내에 HTML 코드를 추가할 수도 있습니다. 심지어는 Advanced Custom Fields PRO 플러그인에서 만든 custom fields를 끌어올 수도 있습니다.
그렇지만 플러그인 제작자측에서는 HTML 코드를 추천하더군요.. 아무래도 안정성과 제어가 좀더 용이했던 것 같습니다. 저는 처음에는 php code를 functions.php에 올려 사용하다 나중엔 HTML 코드로 변경했습니다.
이에 대한 설명은 아래 매뉴얼에서 잘 설명하고 있습니다.
How to add custom registration fields
아무튼 전화번호 필드를 추가한 HTML 코드입니다.
<div class="fieldset fieldset--phone"> <label class="image-replace lrm-email lrm-ficon-phone" title="phone*"></label> <input name="phone" class="full-width has-padding has-border" type="text" placeholder="전화번호, 로그인 시 사용*" required=""> <span class="lrm-error-message"></span> </div>
3.3. 입력 정보를 검증하기
모든 회원가입 절차는 회원가입 단계에서 등록하는 정보에 대한 검증을 실시합니다. 뭐 검증이라는게 대단하게 아니라 반드시 입력이 필요한 필드에 제대로 입력이 되어 있는지 등을 검증합니다.
물론 프로그램 실력이 뛰어나면 검증 조건을 더욱 복잡하게 만들 수 있겠죠.
저는 단순하게 전화번호칸이 비어있으면 전화번호를 입력하라고 메세지를 주고, 전화번호가 입력되면 이전에 입력한 전화번호인지 확인해 이미 등록되어 있다면 다른 전화번호를 사용하라는 메세지를 줍니다.
이는 플러그인에서 알려주는 기본 검증 방법입니다.
/* * 회원가입시 등록 정보 검증하기 * 자료 : https://docs.maxim-kaminsky.com/lrm/kb/how-to-add-custom-registration-fields/ */ // How to add html: https://monosnap.com/file/2MVkjClgYE3W3U9cgPEjy4VXXCzXf2 // COPY AFTER add_filter( 'registration_errors',function ( $errors, $sanitized_user_login, $user_email ) { if ( empty( $_POST['phone'] ) ) { $errors->add( 'phone_error', __( '<strong>ERROR</strong>: 휴대전화번호를 입력해 주세요!', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow phone duplicates $users = get_users(array( 'meta_key' => 'phone', 'meta_value' => $_POST['phone'], )); if ( $users ) { $errors->add( 'phone_error', __( '<strong>ERROR</strong>: 전화번화가 이미 등록되어 있네요. 다른 전화번호를 이용해 주세요!', 'LANG_DOMAIN' ) ); } } return $errors; }, 10, 3 ); add_action( 'user_register', function ( $user_id ) { if ( ! empty( $_POST['phone'] ) ) { update_user_meta( $user_id, 'phone', sanitize_text_field($_POST['phone'] ) ); } });
3.4. 백엔드에서 보여주는 모습
다음으로는 백엔드 즉 워드프레스 계정으로 로그인해서 사용자 확인 등에서 표현되는 모습을 정의하는 것입니다.
워드프레스 사용자에서 Personal Information이라는 항목 아래 나타나도록 합니다.
// COPY AFTER /** * 백엔드에서 보여주는 모습 */ add_action( 'show_user_profile', 'lrm_show_extra_profile_fields' ); add_action( 'edit_user_profile', 'lrm_show_extra_profile_fields' ); function lrm_show_extra_profile_fields( $user ) { ?> <h3><?php esc_html_e( 'Personal Information', 'cn' ); ?></h3> <table class="form-table"> <tr> <th><label for="phone"><?php esc_html_e( 'Phone', 'LANG_DOMAIN' ); ?></label></th> <td><?php echo esc_html( get_user_meta( $user->ID, 'phone', true ) ); ?></td> </tr> </table> <?php }
4. 마치며 – 전체 코드
위에서는 전화번호 입력에 주안점을 두었기에 전화번호만 예를 들었지만 저는 이외에 다양한 정보를 수집하고 추가로 관련 동의도 받아야 했기 때문에 아래 이미지처럼 다소 복잡한 회원가입 폼을 만들었습니다.
이를 위해 사용한 코드를 아래 공유한 참고하시기 바랍니다. 이런게 있었으면 저도 최소 2주의 시간은 아꼈을 것입니다.
아래는 HTML 코드입니다.
<div class="fieldset fieldset--phone"> <label class="image-replace lrm-email lrm-ficon-phone" title="phone*"></label> <input name="phone" class="full-width has-padding has-border" type="text" placeholder="전화번호, 로그인 시 사용*" required=""> <span class="lrm-error-message"></span> </div> <div> <p><span style="color: #2196f3;font-size: 1.1em;">더 나은 서비스를 위한 추가 정보</span>, "미공개"를 선택할 수 있어요!</p> </div> <div class="fieldset clearfix"> <div class="lrm-col-half-width lrm-col-first fieldset--region" > <label title="region" for="region" style="float:left">주요 활동지역은?</br></label> <select name="region" id="region" /> <option value="seoul">서울시</option> <option value="inchon">인천시</option> <option value="kyunggi_north">경기도 북부</option> <option value="kyunggi_south">경기도 남부</option> <option value="chungbuk">충북</option> <option value="chungnam">충남</option> <option value="kyungbuk">경북</option> <option value="kyungnam">경남</option> <option value="jonbuk">전북</option> <option value="jonnam">전남</option> <option value="jeju">제주</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </div> <div class="lrm-col-half-width lrm-col-last fieldset--ability" > <label title="ability" for="ability" style="float:left">나의 평균 타수는?</br></label> <select name="ability" id="ability" /> <option value="100">100대 </option> <option value="90">90대 </option> <option value="80">80대 </option> <option value="110">110대 </option> <option value="70">70대 이하</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </div> </div> <div class="fieldset clearfix"> <div class="lrm-col-half-width lrm-col-first fieldset--generations" > <label title="generations" for="generations" style="float:left">어느 세대인가?</br></label> <select name="generations" id="generations" /> <option value="90_year">90년생이 온다</option> <option value="80_year">80년대 </option> <option value="70_year">70년대 </option> <option value="60_year">60년대 </option> <option value="50_year">50년대 </option> <option value="00_year">00년대 이상</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </div> <div class="lrm-col-half-width lrm-col-last fieldset--gender" > <label title="gender" for="gender" style="float:left">나의 성별은?</br></label> <select name="gender" id="gender" /> <option value="female">원더우먼</option> <option value="male">슈퍼맨</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </div> </div> <div class="fieldset fieldset--terms"> <label class="lrm-nice-checkbox__label lrm-accept-terms-checkbox" style="float:left">[필수] <a href="https://ownergolf.net/privacy-policy/" target="_blank">개인정보 보호정책</a> 및 <a href="https://ownergolf.net/terms-of-service/" target="_blank">이용 약관</a> 동의 <input name="terms" class="lrm-nice-checkbox lrm-accept-terms" type="checkbox" checked="checked" value="yes" > <span class="lrm-error-message"></span> <div class="lrm-nice-checkbox__indicator"></div> </label> </div> <div class="fieldset fieldset--geo_terms"> <label class="lrm-nice-checkbox__label lrm-accept-geo_terms-checkbox" style="float:left">[필수] 서비스 거리 산출 등 <a href="https://ownergolf.net/위치-정보-이용-정책/" target="_blank">위치 정보 이용</a> 동의 <input name="geo_terms" class="lrm-nice-checkbox lrm-accept-geo_terms" type="checkbox" checked="checked" value="yes" > <span class="lrm-error-message"></span> <div class="lrm-nice-checkbox__indicator"></div> </label> </div> <div class="fieldset fieldset--communications"> <label class="lrm-nice-checkbox__label lrm-accept-communications-checkbox" style="float:left">[필수] 서비스관련 <a href="https://ownergolf.net/커뮤니케이션-정책/" target="_blank">제반 커뮤니케이션</a> 동의<input name="communications" class="lrm-nice-checkbox lrm-accept-communications" type="checkbox" checked="checked" value="yes" > <span class="lrm-error-message"></span> <div class="lrm-nice-checkbox__indicator"></div> </label> </div>
다음은 php 코드입니다.
<?php /* * 전화번호로 로그인하기 * phone 필드 추가 * 자료 : https://gist.github.com/max-kk/83c30c3e0be6d967d0c0e733e4e1a338#file-login_via_phone-php */ // ONLY IF you want the user login via phone-number // COPY after add_filter('lrm/login_info_filter', function ($info) { $user_login = $info['user_login']; // Optionally $user_login = str_replace(["+", "-", " "], '', $info['user_login']); // Commnets this to allow phone duplicates $users = get_users(array( 'meta_key' => 'phone', 'meta_value' => $user_login, )); if ( $users ) { $info['user_login'] = $users[0]->user_login; } return $info; }); /* * 회원가입시 등록 정보 검증하기 * 자료 : https://docs.maxim-kaminsky.com/lrm/kb/how-to-add-custom-registration-fields/ */ // How to add html: https://monosnap.com/file/2MVkjClgYE3W3U9cgPEjy4VXXCzXf2 // COPY AFTER add_filter( 'registration_errors',function ( $errors, $sanitized_user_login, $user_email ) { if ( empty( $_POST['phone'] ) ) { $errors->add( 'phone_error', __( '<strong>ERROR</strong>: 휴대전화번호를 입력해 주세요!', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow phone duplicates $users = get_users(array( 'meta_key' => 'phone', 'meta_value' => $_POST['phone'], )); if ( $users ) { $errors->add( 'phone_error', __( '<strong>ERROR</strong>: 전화번화가 이미 등록되어 있네요. 다른 전화번호를 이용해 주세요!', 'LANG_DOMAIN' ) ); } } if ( empty( $_POST['terms'] ) ) { $errors->add( 'terms_error', __( '<strong>ERROR</strong>: 개인보호정책과 이용 약관 동의에 체크해 주세요!', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow terms duplicates $users = get_users(array( 'meta_key' => 'terms', 'meta_value' => $_POST['terms'], )); } if ( empty( $_POST['geo_terms'] ) ) { $errors->add( 'geo_terms_error', __( '<strong>ERROR</strong>: 서비스 장소와 거리 산출 등 원활한 서비스 제공을 위해 위치 정보 이용에 동의해 주세요!', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow geo_terms duplicates $users = get_users(array( 'meta_key' => 'geo_terms', 'meta_value' => $_POST['geo_terms'], )); } if ( empty( $_POST['communications'] ) ) { $errors->add( 'communications_error', __( '<strong>ERROR</strong>: 서비스 관련 연락을 위한 메일, SMS, 전화 등의 제반 커뮤니케이션 정책에에 동의해 주세요!', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow communications duplicates $users = get_users(array( 'meta_key' => 'communications', 'meta_value' => $_POST['communications'], )); } if ( empty( $_POST['region'] ) ) { $errors->add( 'region_error', __( '<strong>ERROR</strong>: 고객님이 주로 활동하는 지역을 알려주세요! 공개하고 싶지 않아요를 선택할 수 있어요', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow phone duplicates $users = get_users(array( 'meta_key' => 'region', 'meta_value' => $_POST['region'], )); } if ( empty( $_POST['ability'] ) ) { $errors->add( 'ability_error', __( '<strong>ERROR</strong>: 고객님의 골프 타수를 알려주세요! 공개하고 싶지 않아요를 선택할 수 있어요', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow ability duplicates $users = get_users(array( 'meta_key' => 'ability', 'meta_value' => $_POST['ability'], )); } if ( empty( $_POST['generations'] ) ) { $errors->add( 'generations_error', __( '<strong>ERROR</strong>: 고객님은 어느 세대인지 알려주세요! 공개하고 싶지 않아요를 선택할 수 있어요', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow generations duplicates $users = get_users(array( 'meta_key' => 'generations', 'meta_value' => $_POST['generations'], )); } if ( empty( $_POST['gender'] ) ) { $errors->add( 'gender_error', __( '<strong>ERROR</strong>: 고객님의 성별을 알려주세요! 공개하고 싶지 않아요를 선택할 수 있어요', 'LANG_DOMAIN' ) ); } else { // Commnets this to allow gender duplicates $users = get_users(array( 'meta_key' => 'gender', 'meta_value' => $_POST['gender'], )); } return $errors; }, 10, 3 ); add_action( 'user_register', function ( $user_id ) { if ( ! empty( $_POST['phone'] ) ) { update_user_meta( $user_id, 'phone', sanitize_text_field($_POST['phone'] ) ); } if ( ! empty( $_POST['terms'] ) ) { update_user_meta( $user_id, 'terms', sanitize_text_field($_POST['terms'] ) ); } if ( ! empty( $_POST['geo_terms'] ) ) { update_user_meta( $user_id, 'geo_terms', sanitize_text_field($_POST['geo_terms'] ) ); } if ( ! empty( $_POST['communications'] ) ) { update_user_meta( $user_id, 'communications', sanitize_text_field($_POST['communications'] ) ); } // select box도 sanitize_text_field 사용, https://wordpress.stackexchange.com/questions/178762/how-to-sanitize-select-box-values-in-post-meta //if ( isset( $_POST['region'] )){ // $value = sanitize_text_field( $_POST['region'] ); // update_post_meta( $post->ID, 'region', $value ); //} if ( ! empty( $_POST['region'] ) ) { update_user_meta( $user_id, 'region', sanitize_text_field($_POST['region'] ) ); } if ( ! empty( $_POST['ability'] ) ) { update_user_meta( $user_id, 'ability', sanitize_text_field($_POST['ability'] ) ); } if ( ! empty( $_POST['generations'] ) ) { update_user_meta( $user_id, 'generations', sanitize_text_field($_POST['generations'] ) ); } if ( ! empty( $_POST['gender'] ) ) { update_user_meta( $user_id, 'gender', sanitize_text_field($_POST['gender'] ) ); } }); // COPY AFTER /** * Back end display */ add_action( 'show_user_profile', 'lrm_show_extra_profile_fields' ); add_action( 'edit_user_profile', 'lrm_show_extra_profile_fields' ); function lrm_show_extra_profile_fields( $user ) { ?> <h3><?php esc_html_e( 'Personal Information', 'cn' ); ?></h3> <table class="form-table"> <tr> <th><label for="phone"><?php esc_html_e( 'Phone', 'LANG_DOMAIN' ); ?></label></th> <td><?php echo esc_html( get_user_meta( $user->ID, 'phone', true ) ); ?></td> </tr> <tr> <th><label for="region"><?php esc_html_e( '주활동지역?', 'LANG_DOMAIN' ); ?></label></th> <td> <select name="region" id="region" /> <option value="seoul">서울시</option> <option value="inchon">인천시</option> <option value="kyunggi_north">경기도 북부</option> <option value="kyunggi_south">경기도 남부</option> <option value="chungbuk">충북</option> <option value="chungnam">충남</option> <option value="kyungbuk">경북</option> <option value="kyungnam">경남</option> <option value="jonbuk">전북</option> <option value="jonnam">전남</option> <option value="jeju">제주</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </td> </tr> <tr> <th><label for="ability"><?php esc_html_e( '나의 골프 수준은?', 'LANG_DOMAIN' ); ?></label></th> <td> <select name="ability" id="ability" /> <option value="100">100대 </option> <option value="90">90대 </option> <option value="80">80대 </option> <option value="110">110대 </option> <option value="70">70대 이하</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </td> </tr> <tr> <th><label for="generations"><?php esc_html_e( '어느 세대인가?', 'LANG_DOMAIN' ); ?></label></th> <td> <select name="generations" id="generations" /> <option value="90_year">90년생이 온다</option> <option value="80_year">80년대 </option> <option value="70_year">70년대 </option> <option value="60_year">60년대 </option> <option value="50_year">50년대 </option> <option value="00_year">00년대 이상</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </td> </tr> <tr> <th><label for="gender"><?php esc_html_e( '나의 성별은?', 'LANG_DOMAIN' ); ?></label></th> <td> <select name="gender" id="gender" /> <option value="female">원더우먼</option> <option value="male">슈퍼맨</option> <option value="not_open">공개하고 싶지 않아요!</option> </select> </td> </tr> <tr> <th><label for="terms"><?php esc_html_e( '개인정보 보호정책 및 약관 동의', 'LANG_DOMAIN' ); ?></label></th> <td> <label class="lrm-nice-checkbox__label lrm-accept-terms-checkbox">[필수] <a href="https://ownergolf.net/privacy-policy/" target="_blank">개인정보 보호정책</a> 및 <a href="https://ownergolf.net/terms-of-service/" target="_blank">이용 약관</a> 동의 <input name="terms" class="lrm-nice-checkbox lrm-accept-terms" type="checkbox" checked="checked" value="yes"> <span class="lrm-error-message"></span> <div class="lrm-nice-checkbox__indicator"></div> </label> </td> </tr> <tr> <th><label for="geo_terms"><?php esc_html_e( '위치 정보 이용 동의', 'LANG_DOMAIN' ); ?></label></th> <td> <label class="lrm-nice-checkbox__label lrm-accept-geo_terms-checkbox">[필수] 서비스 거리 산출 등 <a href="https://ownergolf.net/위치-정보-이용-정책/" target="_blank">위치 정보 이용</a> 동의 <input name="geo_terms" class="lrm-nice-checkbox lrm-accept-geo_terms" type="checkbox" checked="checked" value="yes"> <span class="lrm-error-message"></span> <div class="lrm-nice-checkbox__indicator"></div> </label> </td> </tr> <tr> <th><label for="communications"><?php esc_html_e( '위치 정보 이용 동의', 'LANG_DOMAIN' ); ?></label></th> <td> <label class="lrm-nice-checkbox__label lrm-accept-communications-checkbox">[필수] 서비스관련 <a href="https://ownergolf.net/커뮤니케이션-정책/" target="_blank">제반 커뮤니케이션</a> 동의<input name="communications" class="lrm-nice-checkbox lrm-accept-communications" type="checkbox" checked="checked" value="yes"> <span class="lrm-error-message"></span> <div class="lrm-nice-checkbox__indicator"></div> </label> </td> </tr> </table> <?php }