WP0円マッチングサイト作成プロジェクト第1弾の作り方⑤
【WP-Membersに足りない機能】
WP-Membersは非常に優秀なプラグインで標準機能でほぼほぼ会員制サイトを構築出来る。
・120以上のアクションフックとフィルターフックで強力なカスタマイズが可能
・拡張性のあるAPI関数のライブラリ
・標準機能は無料で有料プラン:$125/年がある
しかし有料プランを使って必ずしも全て思い通りにカスタマイズ出来る訳ではない。
①会員退会機能がない
②会員検索&一覧表示機能がない
※WP-Members有料プランに「User List」のExtensionsがあるが独自実装することにする。
③特定のバリデーション機能がない
①会員退会機能
そもそもWP-Membersには会員退会機能がない。
有料のPremium Extensionsがあるがそこにも退会機能は見当たらない。
他の会員系プラグインも会員退会機能がないものが多い印象である。
実際にユーザーを削除する方法とアクセス出来なくする方法があるが、ここではユーザーを削除する方法にする。
またWP-Membersフィールドで画像をアップロードしたDataが残るので別途実装する必要がある。
【主な仕様】
- 退会理由を保存する
- 退会するユーザーのDataを削除する
- 退会後のリダイレクト先URLを指定する
【手順】
1)固定ページ(slug: signout)を新規追加する
2)page-signout.phpに退会フォームと退会処理を記述する
会員退会のコード
//page-signout.php
if ( isset( $_POST['cancel_subscription'] ) ) {
//退会理由を取得
$cancel_reason = sanitize_text_field( $_POST['cancel_reason'] );
$detailed_reason = sanitize_textarea_field( $_POST['detailed_reason'] );
//退会理由を記録(ユーザーメタデータに保存)
update_user_meta( $current_user->ID, 'cancel_reason', $cancel_reason );
update_user_meta( $current_user->ID, 'detailed_reason', $detailed_reason );
if ( $current_user->ID > 0 ) {
require_once( ABSPATH . 'wp-admin/includes/user.php' ); // ユーザー削除に必要なファイルをインクルード
wp_delete_user( $current_user->ID );
}
// 退会後のリダイレクト先URL
wp_redirect( home_url( '/thanks_memb/' ) );
}
会員検索&一覧表示機能がない
WP-Membersの有料プランに会員検索機能のExtensionsはあるがマッチングサイトには不向きなので独自に実装することにする。
まず会員検索と一覧ページは別々に作る。
会員一覧→会員プロフ詳細→chatへ連携させる。
また会員検索のDB負担を減らす意味でも会員一覧←→会員プロフ詳細をAjaxで実装する。
②会員検索機能
今回はユーザーを検索する条件をユーザー検索設定としてDBに保存する仕様にした。
会員プロフに入力してもらったフィールド項目を検索するが
検索のしやすさや、またDBの処理速度やソートのことも考えて設計しないといけない。
【会員検索の主な仕様】
- WP-Membersのフィールド設定一覧で「Profile」にチェックが入っているフィールドが表示される
※一覧上での順番もそのまま反映される
- フィールドタイプを規制
※image、password、emailは除外する
text、textareaもDBの負担軽減のため今回は除外する
$excludeTypeArr = array(“textarea”, “image”, “password”, “email”); - メタキーを規制
※そもそも検索させないフィールドは除外する
$excludeFieldArr = array(“user_avatar_img”, “nickname”, “description”); - 複数項目を検索可能にする
※radioは複数チェック可能にする
・性別 radio→multicheckbox
・居住地(place) - 範囲指定で検索可能にする(number->Wrange)
※・「体型」を検索する場合
細め〜グラマー
・「年齢(歳)」「身長(cm)」を検索する場合
WP-Members オプションの「フィールドを編集」の最小最大値を使う
最小値(19)〜最大値(70)
最小値(140)〜最大値(250) - ソート順
※・登録日(最近が上)かログイン日(最近が上)?
登録日時(user_registered)→有る
最新ログイン日時(user_last_login)←後日追加する?
会員検索のコード1
//page-usersearch.php
$current_user = wp_get_current_user();
$user_id = $current_user->ID;
$usearchArr = get_user_meta( $user_id,'user_search_para',true);
$rangeFieldArr = array("age", "height");//number->Wrange
$excludeTypeArr = array("textarea", "image", "password", "email");
$excludeFieldArr = array("user_avatar_img", "nickname", "description");
$prof_fieldsArr = array();
$mem_fieldsArr = wpmem_fields();
foreach( $mem_fieldsArr as $k => $v ){
$options = array();
$max = $min = $label = "";
$type = "text";
$prof = false;
if (in_array($k, $excludeFieldArr)) {continue;}
foreach( $v as $k2 => $v2 ){
if ( $k2 == "options" ){
$options = $v2;
}
if ( $k2 == "label" ){
$label = $v2;
}
if ( $k2 == "type" ){
if (in_array($v2, $excludeTypeArr)) {continue 2;}
$type = $v2;
}
if ( $k2 == "min" ){
$min = $v2;
}
if ( $k2 == "max" ){
$max = $v2;
}
if ( $k2 == "profile" && $v2){
$prof = true;
}
}
if ( $prof ){$prof_fieldsArr[$k] = array($label, $type, $options, $min, $max);}
}
会員検索のコード2
//page-usersearch.php
$admin_url = admin_url() . 'admin-ajax.php';
$current_user = wp_get_current_user();
$html = '<form id="form_usersearch" name="usersearch" method="post" action="" data-admin_url="' . $admin_url .'" data-current_user="' . $current_user->ID .'"><fieldset>';
foreach( $prof_fieldsArr as $k => $v ){
$label = $v[0];
if($v[1] == "select") {
$html .= '<label for="'.$k.'" class="text">'.$label.'</label>';
$html .= '<div class="div_select">';
if ( !empty($v[2]) ) {
$html .= '<select name="'.$k.'" id="'.$k.'" class="dropdown">';
foreach( $v[2] as $k2 => $v2 ){
if ($usearchArr && array_key_exists($k, $usearchArr)) {
$html .= '<option'. selected( $usearchArr[$k], $k2, false) . ' value="'.$k2.'">'.$v2.'</option>';
}else{
$html .= '<option value="'.$k2.'">'.$v2.'</option>';
}
}
$html .= '</select>';
}
$html .= '</div>';
}elseif($v[1] == "checkbox") {
$html .= '<div class="div_checkbox"><label for="'.$k.'" class="text">';
if ($usearchArr && array_key_exists($k, $usearchArr)) {
$html .= '<input name="'.$k.'" type="checkbox"'. checked( $usearchArr[$k], $k, false) . ' value="'.$k.'" id="'.$k.'"> ';
}else{
$html .= '<input name="'.$k.'" type="checkbox" value="'.$k.'" id="'.$k.'"> ';
}
$html .= $label . '</label></div>';
}elseif($v[1] == "multicheckbox") {
$html .= '<label for="'.$k.'" class="text">'.$label.'</label>';
$html .= '<div class="div_multicheckbox">';
if ( !empty($v[2]) ) {
foreach( $v[2] as $k2 => $v2 ){
if ($usearchArr && array_key_exists($k, $usearchArr)) {
if (in_array($k2, $usearchArr[$k])) {$html .= '<input name="'.$k.'[]" type="checkbox" checked value="'.$k2.'" id="'.$k.'['.$k2.']">';
}else{$html .= '<input name="'.$k.'[]" type="checkbox" value="'.$k2.'" id="'.$k.'['.$k2.']">';}
}else{$html .= '<input name="'.$k.'[]" type="checkbox" value="'.$k2.'" id="'.$k.'['.$k2.']">';}
$html .= '<label for="'.$k.'['.$k2.']" id="'.$k.'['.$k2.']" class="multicheckbox">'.$v2.'</label>';
}
}
$html .= '</div>';
}elseif($v[1] == "radio") {
$html .= '<label for="'.$k.'" class="text">'.$label.'</label>';
$html .= '<div class="div_radio">';
if ( !empty($v[2]) ) {
foreach( $v[2] as $k2 => $v2 ){
if ($usearchArr && array_key_exists($k, $usearchArr)) {
$html .= '<input'. checked( $usearchArr[$k], $k2, false) . ' type="radio" name="'.$k.'" id="'.$k.'['.$k2.']" value="'.$k2.'"> ';
}else{$html .= '<input type="radio" name="'.$k.'" id="'.$k.'['.$k2.']" value="'.$k2.'"> ';}
$html .= '<label for="'.$k.'['.$k2.']" id="'.$k.'['.$k2.']" class="radio">'.$v2.'</label><br>';
}
}
$html .= '</div>';
}elseif($v[1] == "number") {
$range_val = $min_val = $max_val = $value = "";
$v3 = isset($v[3]) ? $v[3] : "";
$v4 = isset($v[4]) ? $v[4] : "";
if (isset($usearchArr[$k]) && $usearchArr[$k] != '') {
$value = $usearchArr[$k];
$vals = explode(",", $usearchArr[$k]);
$min_val = $vals[0];
$max_val = $vals[1];
$range_val = $min_val . ',' . $max_val;
}
if (in_array($k, $rangeFieldArr)) {
$html .= '<label for="'.$k.'" class="range">'.$label.' <span class="amount"></span></label>';
$html .= '<div class="slider-range" data-min="' . $v3 .'" data-max="' . $v4 .'" data-min_val="' . $min_val . '" data-max_val="' . $max_val . '"></div>';
$html .= '<input name="'.$k.'" type="hidden" class="input_range" value="' . $range_val . '">';
}else{
$html .= '<div class="div_number"><input name="'.$k.'" type="number" id="'.$k.'" value="'.$value.'" class="numberbox" min="'.$v3.'" max="'.$v4.'"></div>';
}
}else{
$value = isset($usearchArr[$k]) ? $usearchArr[$k] : "";
$html .= '<label for="'.$k.'" class="text">'.$label.'</label>';
$html .= '<div class="div_text"><input name="'.$k.'" type="text" id="'.$k.'" value="'.$value.'" class="textbox"></div>';
}
}
$html .= '<div class="button_submit">
<input name="usersearch_submit" type="submit" value="検索設定を保存する" class="buttons"></div>';
$html .= '</fieldset></form>';
echo do_shortcode('[wpmem_form login]' . $html . '[/wpmem_form]');
会員検索のコード3
//functions.php
// [ Ajax save user search setting ]
add_action('wp_ajax_save_usersearch_setting', 'save_usersearch_setting_callback');
add_action('wp_ajax_nopriv_save_usersearch_setting', 'save_usersearch_setting_callback');
function save_usersearch_setting_callback() {
$html = array();
if ( isset($_POST['uid'])) {
$uid = $_POST['uid'];
$html = $_POST;
unset($html["action"]);
unset($html["uid"]);
foreach ($html as $key => $val) {
if (empty($val)) {unset($html[$key]);}
}
//$htmlArr = array_filter($html, 'empty');// 空の要素を削除
update_user_meta( $uid, 'user_search_para', $html );
$response = array(
'html' => $html,
'show_button' => true,
);
} else {
$response = array(
'show_button' => false,
);
}
$response = json_encode($response);
echo $response;
wp_die();
}
②一覧表示機能
ユーザー一覧表示自体はWP_User_Query()を使って簡単に出来る。
しかしユーザー数が膨大になった際のDBの処理速度やソートのことを考えると。。
今回はWordPressの標準DBをそのまま使うことにするが。。
一覧表示のコード
//page-userlist.php
$rangeFieldArr = array("age", "height");//number->Wrange
$current_user = wp_get_current_user();
$user_id = $current_user->ID;
if ( !$user_id ) {// User is not logged in.
echo '<p class="center"><a href="/login/">ログインする</a></p>';
} else {// User is logged in.
$user_search_para = get_user_meta( $user_id,'user_search_para',true);
if (!$user_search_para) {
print('<p class="center">ユーザー検索設定のDataがありません!<br><a href="/usersearch/">ユーザー検索設定へ</a></p>');
}
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$users_per_page = 10;
$args = array(
'order' => 'desc', //登録が新しい順
'orderby' => 'user_registered', //登録日
'paged' => $paged,
'number' => $users_per_page,
'exclude' => $user_id,
'role__in' => array( 'subscriber' ), // 編集者または投稿者権限を持つユーザー
//'role__not_in' => array( 'administrator' ), // 管理者以外のユーザーを検索
);
if ( !empty($user_search_para) ) {
$meta_query = array('relation' => 'AND');
foreach ($user_search_para as $key => $val) {
if (is_array($val)) {
$compare = 'IN';
} elseif (in_array($key, $rangeFieldArr)) {
$compare = 'BETWEEN';
} else {
$compare = '=';
}
if ($compare == 'BETWEEN') {
$vals = explode(",", $val);
$meta_query[] = array(
'key' => $key,
'value' => $vals,
'type' => 'numeric',
'compare' => $compare,
);
} else {
$meta_query[] = array(
'key' => $key,
'value' => $val,
'compare' => $compare,
);
}
}
$args['meta_query'] = $meta_query;
}
if ($user_search_para) {
$wp_user_query = new WP_User_Query($args);
$total_users = $wp_user_query->get_total();
$num_pages = ceil($total_users / $users_per_page);
$user_query = $wp_user_query->get_results();
$proFieldsArr = [
['place', '', 'select'],
['gender_so', '', 'multicheckbox'],
['age', '歳', 'number'],
['height', 'cm', 'number']
];
}
if (!empty( $user_query ) ) {
$admin_url = admin_url() . 'admin-ajax.php';
$html = '<ul id="userlist1" class="userlist1" data-admin_url="' . $admin_url .'">';
foreach( $user_query as $user ){
$html .= '<li class=""><a class="userProf" data-uid="' . $user->ID .'">';
$user_avatar_img = get_user_meta( $user->ID,'user_avatar_img',true);
$imgurl1 = wp_get_attachment_url( $user_avatar_img );
$html .= '<p><img alt="user img" class="userface" src="' . $imgurl1 .'"></p><div>';
$html .= '<p class="nickname">' . get_the_author_meta( 'nickname', $user->ID ) . '</p><div class="pf_flex">';
$i = 0;
foreach( $proFieldsArr as $v){
if(get_the_author_meta( $v[0], $user->ID ) == '') {continue;}
$i++;
if($i>=2){
$html .= '<p class="' . $v[0] . '">/';
}else{
$html .= '<p class="' . $v[0] . '">';
}
switch ($v[2]) {
case 'text':
$html .= get_the_author_meta( $v[0], $user->ID );
break;
case 'select':
$html .= wpmem_get_user_meta_select( $user->ID, $v[0] );
break;
case 'multicheckbox':
$html .= wpmem_get_user_meta_multi( $user->ID, $v[0] );
break;
default:
$html .= get_the_author_meta( $v[0], $user->ID );
break;
}
$html .= $v[1] . '</p>';
}
$description = mb_strimwidth(get_the_author_meta( 'description', $user->ID ), 0, 80, "…","UTF-8");
$html .= '</div><p class="description">' . $description . '</p>';
$html .= '</div></a></li>';
}
echo $html . '</ul>';
}
if ( isset($num_pages) ) {
echo '<div id="pagination" class="clearfix um-members-pagi">';
echo '<span class="pages"></span>';
echo paginate_links(array(
'base' => get_pagenum_link(1) . '%_%',
'format' => 'page/%#%/',
'current' => max( 1, get_query_var('paged') ),
'total' => $num_pages,
'prev_next' => true,
'show_all' => true,
'type' => 'plain',
));
echo '</div>';
}
}
③特定のバリデーション機能がない
もちろんWP-Membersにフォーム バリデーション機能はある。
しかし文字数制限など特定のバリデーション機能がない。
文字種を制限をするには「パターン」
文字種で制限をする場合はフィールド設定の「パターン」を使う。
WP-Membersのフィールド設定でinputのpattern属性を使うことが出来る。
例)全角カタカナ→[\u30A1-\u30F6]*
【参照】WP-Members:項目に独自のバリデーションを追加する
https://securavita.net/wp-members-field-validation/
文字数を制限をする場合はまずクラスを追加する
文字数を制限をするバリデーション機能はWP-Membersにはない。
というかUI/UXを考慮するとJSで実装するのが良い。
そこで文字数を制限をする場合はクラスを追加する。そして別途JSでバリデーションする。
※inputのmaxlength属性はtype属性やスマホ環境では正しく動作しない場合があるらしい。
※WP-Membersの有料版ではフィールド設定にクラス項目があるらしい。
フィールドにクラスを追加するコード
ここでは[‘description’]に「maxlength500」クラスを追加している。
//functions.php
add_filter( 'wpmem_register_form_rows', 'my_register_form_rows_filter', 10, 2 );
function my_register_form_rows_filter( $rows, $tag ) {
if ( 'new' == $tag ) {
$old = 'class="textbox"';
$new = 'class="textbox email"';
$rows['user_email']['field'] = str_replace( $old, $new, $rows['user_email']['field'] );
}
if ( 'edit' == $tag ) {
$old = 'class="textarea"';
$new = 'class="textarea maxlength500"';
$rows['description']['field'] = str_replace( $old, $new, $rows['description']['field'] );
}
return $rows;
}
どうしてもmaxlength属性をつけたい場合は下記のようにするとよい。
$old = ‘class=”textarea”‘;
$new = ‘class=”textarea” maxlength=”500″‘;
500文字以上入力出来なくするJSコード
//max length
jQuery("form .maxlength500").on('input', function(e) {
var value = jQuery(this).val();
if (value.length > 500) {
jQuery(this).val(value.slice(0, 500));
}
});
※2025年8月時の情報