Nguồn chỉnh sửa
https://www.google.com/search?q=Code+hi%E1%BB%83n+th%E1%BB%8B+s%E1%BB%91+%C4%91i%E1%BB%87n+tho%E1%BA%A1i+kh%C3%A1ch+h%C3%A0ng+trong+woocommerce+%3D%3E+customer+trong+wp-admin&sca_esv=81ee190d3896ba19&rlz=1C1YTUH_viVN1001VN1001&sxsrf=APpeQnsUSALQoHxoAPvGjah7nhRfjl_cwA%3A1792476475481&source=chrome.ob&fbs=ABfTbFVyMZGZf1hfvX9uKjN_-G8cxpBkeIeqYwoCbfNVc4vKE9LbDnU6KQGy7t-K8nDAEfyjbsqYMIS6pkSA1Hjj7Nr9j3KkRcomOAbfl8OIcMY5G0hcFJwwDMesy7aR4ZhachrzHuCJOKY-UXFROayMonzBkES_oApz4i19mA0un_5jp9pBetQC5d3uqkuWiXdoO3yumldP-xmODnbd4v3i3-GyhBgdRA&vsint=&aep=1&ntc=1&cs=0&sa=X&ved=2ahUKEwiY_Nqc98eXAxXG3TgGHVIeJbIQ2J8OegQIEhAD&biw=1280&bih=559&dpr=1.5&mstk=AUtExfCBabZaz7kP8cBo9utqHF2CdoeMfHVpJZmfj0-lrQsJwVafd2N8hs9QQOY82hQVAikvsIyYXmP94fyJoF1X89uOjINZUbhKLv2kL3dGcpuDxW75WqznOY484hO1uS7C-6cLOUNQf0jQoxuEjq5smv_-qBUBrnXB6lLDqlMMyiVKLbycQnm2CMQ17AohOSykvWBSU367EsHBsaln42E6Bwyun7LVSrzORec_OqzFduLlgjDrKkDk8688KcqB1cJSubN0pMJyWhp2L3OkM0_GEla00TeRQkjDgRhNiJYUgeAgYoZBowGU_92oaDBYQ5USYkoJH1Pt7rQ3sg&csuir=1&mtid=PQXXapDaGPK_juMP4vLTkQE&lns_mode=cvst&udm=50
// =========================================================================
// TẠO KHUNG ĐĂNG NHẬP BẰNG POPUP MỚI (HTML + CSS + JS)
// Nguồn chỉnh sửa: vuvo.top => WOO - Tạo khung đăng nhập popup
// =========================================================================
function custom_auth_popup_core_html() {
global $wp;
$current_url = home_url( add_query_arg( array(), $wp->request ) );
?>
<div class="auth-menu-wrapper">
<?php if ( ! is_user_logged_in() ) : ?>
<!-- Giao diện CHỈ HIỂN THỊ KHI CHƯA ĐĂNG NHẬP -->
<!-- Bấm vào thẻ A này sẽ kích hoạt script mở Popup -->
<a href="#authPopup" class="open-auth-popup data-auth-trigger" id="openAuthPopupLink">
Đăng nhập / Đăng ký
</a>
<!-- Nút kích hoạt dự phòng (Ẩn đi nếu bạn chỉ dùng thẻ A phía trên) -->
<button id="openAuthPopup" class="btn-trigger-auth data-auth-trigger" style="display:none;">
Đăng nhập / Đăng ký
</button>
<!-- Khung Popup chính -->
<div id="authPopupModal" class="auth-popup-overlay" style="display:none;">
<div class="auth-popup-container">
<span class="auth-popup-close" id="closeAuthPopup">×</span>
<!-- 1. KHUNG ĐĂNG NHẬP -->
<div id="loginFormArea" class="auth-form-wrapper active">
<h2>Đăng nhập hệ thống</h2>
<div id="loginPopupErrors" style="color:red; font-size:13px; margin-bottom:10px; text-align:center;"></div>
<form method="post" id="mainLoginForm">
<div class="input-group">
<label for="login_user">Số điện thoại hoặc Email</label>
<input type="text" id="login_user" name="username" placeholder="Nhập số điện thoại hoặc email..." required>
</div>
<div class="input-group">
<label for="login_pass">Mật khẩu</label>
<div class="password-wrapper">
<input type="password" id="login_pass" name="password" placeholder="Nhập mật khẩu..." required>
<button type="button" class="toggle-password-visibility" tabindex="-1">
<svg xmlns="http://w3.org" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<div class="auth-addon-link">
<!-- SỬA TẠI ĐÂY: Chuyển đổi thành nút bấm kích hoạt AJAX tab con -->
<a href="#lost-password" id="openLostPassForm" class="lost-pass-btn">Quên mật khẩu?</a>
</div>
<?php wp_nonce_field( 'woocommerce-login', 'woocommerce-login-nonce' ); ?>
<button type="submit" class="auth-submit-btn" name="login">Đăng nhập ngay</button>
</form>
</div>
<!-- 2. KHUNG ĐĂNG KÝ -->
<div id="registerFormArea" class="auth-form-wrapper">
<h2>Tạo tài khoản mới</h2>
<div id="regPopupErrors" style="font-size:13px; margin-bottom:10px; text-align:center;"></div>
<form method="post" id="mainRegisterForm">
<div class="input-group">
<label for="reg_user">Tên đăng nhập</label>
<input type="text" id="reg_user" name="reg_user" placeholder="Ví dụ: nguyenvana" required>
</div>
<div class="input-group">
<label for="reg_phone">Số điện thoại</label>
<input type="tel" id="reg_phone" name="reg_phone" placeholder="Ví dụ: 0912345678" required>
</div>
<div class="input-group">
<label for="reg_email">Địa chỉ Email</label>
<input type="email" id="reg_email" name="reg_email" placeholder="Ví dụ: email@domain.com" required>
</div>
<div class="input-group">
<label for="reg_pass">Mật khẩu</label>
<div class="password-wrapper">
<input type="password" id="reg_pass" name="reg_pass" placeholder="Tối thiểu 6 số hoặc chữ..." required>
<button type="button" class="toggle-password-visibility" tabindex="-1">
<svg xmlns="http://w3.org" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<div class="input-group">
<label for="reg_pass_confirm">Nhập lại mật khẩu</label>
<div class="password-wrapper">
<input type="password" id="reg_pass_confirm" name="reg_pass_confirm" placeholder="Xác nhận lại mật khẩu..." required>
<button type="button" class="toggle-password-visibility" tabindex="-1">
<svg xmlns="http://w3.org" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<button type="submit" class="auth-submit-btn">Đăng ký ngay</button>
</form>
</div>
<!-- 3. KHUNG KHÔI PHỤC MẬT KHẨU QUA AJAX -->
<div id="lostPasswordFormArea" class="auth-form-wrapper">
<h2>Khôi phục mật khẩu</h2>
<p style="font-size: 13px; color: #666; margin-bottom: 15px; text-align: center;">
Vui lòng nhập Email. Hệ thống sẽ gửi một liên kết đổi mật khẩu qua Email của bạn.
</p>
<!-- Nơi hiển thị thông báo lỗi hoặc thành công -->
<div id="lostPopupErrors" style="font-size:13px; margin-bottom:15px; text-align:center; font-weight: 500;"></div>
<form method="post" id="mainLostPasswordForm">
<div class="input-group">
<label for="lost_user">Địa chỉ Email</label>
<input type="text" id="lost_user" name="user_login" placeholder="Nhập tên đăng nhập hoặc email..." required>
</div>
<button type="submit" class="auth-submit-btn">Gửi liên kết khôi phục</button>
<!-- Nút quay lại tab đăng nhập nhanh -->
<div style="text-align: center; margin-top: 15px;">
<a href="#login" id="backToLoginFromLost" style="font-size: 13px; color: #0073aa; text-decoration: none;">← Quay lại Đăng nhập</a>
</div>
</form>
</div>
<!-- THANH ĐIỀU HƯỚNG DƯỚI CÙNG -->
<div class="auth-popup-footer">
<div class="or-separator">— Hoặc —</div>
<button id="toggleAuthBtn" class="toggle-switch-btn">Chưa có tài khoản? Đăng ký ngay</button>
</div>
</div>
</div>
<?php endif; ?>
</div>
<!-- STYLE CSS ĐÃ TỐI ƯU CHIỀU CAO RESPONSIVE -->
<style>
.btn-trigger-auth { padding: 10px 20px; background-color: #446084; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; text-decoration: none; display: inline-block; }
.btn-trigger-auth:hover { background-color: #334963; color: #fff; }
.auth-popup-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 999999; justify-content: center; align-items: center; padding: 15px; box-sizing: border-box; }
.auth-popup-container { background-color: #fff; width: 100%; max-width: 420px; max-height: calc(100vh - 40px); overflow-y: auto; padding: 30px 25px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); position: relative; box-sizing: border-box; font-family: sans-serif; scrollbar-width: thin; }
.auth-popup-close { position: absolute; top: 10px; right: 15px; font-size: 24px; cursor: pointer; color: #aaa; line-height: 1; z-index: 10; }
.auth-popup-close:hover { color: #333; }
.auth-form-wrapper { display: none; }
.auth-form-wrapper.active { display: block; }
.auth-form-wrapper h2 { font-size: 22px; margin-top: 0; margin-bottom: 20px; color: #333; text-align: center; font-weight: 700; }
.input-group { margin-bottom: 12px; display: flex; flex-direction: column; }
.input-group label { font-size: 13px; margin-bottom: 5px; color: #444; text-align: left; font-weight: 600; }
.input-group input { padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; width: 100%; box-sizing: border-box; height: auto !important; }
.auth-addon-link { text-align: right; margin-top: -5px; margin-bottom: 12px; }
.lost-pass-btn { font-size: 13px; color: #446084; text-decoration: none; }
.auth-submit-btn { width: 100%; padding: 11px; background-color: #446084; color: white; border: none; border-radius: 4px; font-size: 15px; font-weight: bold; cursor: pointer; margin-top: 5px; }
.auth-submit-btn:hover { background-color: #334963; }
.auth-popup-footer { margin-top: 15px; text-align: center; }
.or-separator { font-size: 13px; color: #aaa; margin-bottom: 10px; }
.toggle-switch-btn { background: none; border: 1px solid #446084; color: #446084; padding: 10px 20px; border-radius: 4px; font-size: 14px; font-weight: 600; cursor: pointer; width: 100%; }
.toggle-switch-btn:hover { background-color: #f4f9fc; }
.password-wrapper { position: relative; width: 100%; }
.password-wrapper input { padding-right: 45px !important; }
.toggle-password-visibility { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none !important; border: none !important; padding: 0 !important; margin: 0 !important; cursor: pointer; color: #888; display: flex; align-items: center; justify-content: center; box-shadow: none !important; height: auto !important; }
.toggle-password-visibility:hover { color: #333; }
.toggle-password-visibility svg { display: block; pointer-events: none; }
.input-group input.input-error { border-color: #ff4d4d !important; background-color: #fff6f6 !important; box-shadow: 0 0 5px rgba(255, 77, 77, 0.3) !important; animation: shakeError 0.3s ease-in-out; }
@keyframes shakeError { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-6px); } 75% { transform: translateX(6px); } }
@media (max-width: 480px) { .auth-popup-container { padding: 20px 15px; } .auth-form-wrapper h2 { font-size: 18px; margin-bottom: 15px; } .input-group input { padding: 8px; font-size: 13px; } }
</style>
<!-- JAVASCRIPT EXECUTION WITH AJAX -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const modal = document.getElementById("authPopupModal");
const openBtn = document.getElementById("openAuthPopup");
const closeBtn = document.getElementById("closeAuthPopup");
const toggleAuthBtn = document.getElementById("toggleAuthBtn");
const loginFormArea = document.getElementById("loginFormArea");
const registerFormArea = document.getElementById("registerFormArea");
const lostPasswordFormArea = document.getElementById("lostPasswordFormArea");
const openLostPassForm = document.getElementById("openLostPassForm");
if (!modal) return;
// --- HÀM MỞ POPUP CHUNG ---
function openAuthPopup() {
modal.style.display = "flex";
}
// --- KÍCH HOẠT MỞ POPUP TỪ TẤT CẢ PHẦN TỬ ĐƯỢC CHỈ ĐỊNH ---
if (openBtn) {
openBtn.onclick = function(e) { e.preventDefault(); openAuthPopup(); };
}
document.addEventListener('click', function(e) {
const targetAnchor = e.target.closest('a[href="#authPopup"], a.open-auth-popup, a[href*="show_auth=1"], #openAuthPopup, .btn-trigger-auth');
if (targetAnchor) {
e.preventDefault();
openAuthPopup();
}
});
// --- TỰ ĐỘNG BẬT POPUP KHI BỊ CHUYỂN HƯỚNG TỪ TRANG TÀI KHOẢN (?show_auth=1) ---
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('show_auth')) {
openAuthPopup();
if (window.history && window.history.replaceState) {
const cleanUrl = window.location.protocol + "//" + window.location.host + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
}
}
// Đóng Popup khi bấm nút X hoặc bấm ra ngoài vùng màn hình popup
if (closeBtn) {
closeBtn.onclick = function() {
modal.style.display = "none";
resetToLoginTab(); // Reset trạng thái về tab Đăng nhập khi đóng popup
};
}
window.onclick = function(e) {
if (e.target === modal) {
modal.style.display = "none";
resetToLoginTab();
}
};
// Hàm đưa popup về tab Đăng nhập ban đầu
function resetToLoginTab() {
if(loginFormArea) loginFormArea.classList.add("active");
if(registerFormArea) registerFormArea.classList.remove("active");
if(lostPasswordFormArea) lostPasswordFormArea.classList.remove("active");
if(toggleAuthBtn) {
toggleAuthBtn.style.display = "block";
toggleAuthBtn.innerText = "Chưa có tài khoản? Đăng ký ngay";
}
}
// Chuyển đổi qua lại giữa tab Đăng nhập / Đăng ký
if (toggleAuthBtn) {
toggleAuthBtn.onclick = function(e) {
e.preventDefault();
lostPasswordFormArea.classList.remove("active");
if (loginFormArea.classList.contains("active")) {
loginFormArea.classList.remove("active");
registerFormArea.classList.add("active");
toggleAuthBtn.innerText = "Đã có tài khoản? Đăng nhập ngay";
} else {
registerFormArea.classList.remove("active");
loginFormArea.classList.add("active");
toggleAuthBtn.innerText = "Chưa có tài khoản? Đăng ký ngay";
}
};
}
// Xóa viền đỏ cảnh báo khi người dùng bắt đầu gõ lại dữ liệu vào các ô input
const inputs = document.querySelectorAll('.input-group input');
inputs.forEach(input => { input.addEventListener('input', function() { this.classList.remove('input-error'); }); });
// Bật/tắt hiển thị mật khẩu bằng SVG Eye / Eye-off (Giữ nguyên của bạn)
const toggleButtons = document.querySelectorAll('.toggle-password-visibility');
toggleButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
const inputField = this.previousElementSibling;
if (inputField) {
if (inputField.type === 'password') {
inputField.type = 'text';
this.innerHTML = '<svg xmlns="http://w3.org" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye-off" aria-hidden="true"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"></path><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"></path><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"></path><path d="m2 2 20 20"></path></svg>';
} else {
inputField.type = 'password';
this.innerHTML = '<svg xmlns="http://w3.org" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0z"></path><circle cx="12" cy="12" r="3"></circle></svg>';
}
}
});
});
// 1. XỬ LÝ AJAX ĐĂNG NHẬP (BẢN ĐÃ SỬA LỖI ĐƯỜNG DẪN FETCH)
const logForm = document.getElementById("mainLoginForm");
if (logForm) {
logForm.onsubmit = function(e) {
e.preventDefault();
const msgDiv = document.getElementById("loginPopupErrors");
const userField = document.getElementById("login_user");
const passField = document.getElementById("login_pass");
msgDiv.style.color = "#333";
msgDiv.innerText = "Đang kiểm tra tài khoản...";
userField.classList.remove('input-error');
passField.classList.remove('input-error');
const formData = new FormData(logForm);
formData.append('action', 'custom_ajax_login');
// SỬA TẠI ĐÂY: Dùng đường dẫn động an toàn chạy được cả trong file .js và file .php
const ajaxUrl = window.wp_data ? window.wp_data.ajax_url : '/wp-admin/admin-ajax.php';
fetch(ajaxUrl, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(data => {
if (data.success) {
msgDiv.style.color = "green";
msgDiv.innerText = data.data.message;
setTimeout(() => { window.location.reload(); }, 800);
} else {
msgDiv.style.color = "red";
msgDiv.innerText = data.data.message;
// CẢI TIẾN: Chỉ tô đỏ ô bị lỗi dựa theo mã trả về từ code PHP mới
if (data.data && (data.data.code === 'invalid_username' || data.data.code === 'invalid_email')) {
userField.classList.add('input-error');
} else if (data.data && data.data.code === 'incorrect_password') {
passField.classList.add('input-error');
} else {
userField.classList.add('input-error');
passField.classList.add('input-error');
}
}
})
.catch(error => {
msgDiv.style.color = "red";
msgDiv.innerText = "Có lỗi xảy ra khi kết nối hệ thống.";
console.error("Chi tiết lỗi kết nối:", error);
});
};
}
// 2. XỬ LÝ AJAX ĐĂNG KÝ (CẢI TIẾN KIỂM TRA SỐ ĐIỆN THOẠI VN)
const regForm = document.getElementById("mainRegisterForm");
if (regForm) {
regForm.onsubmit = function(e) {
e.preventDefault();
const msgDiv = document.getElementById("regPopupErrors");
const phoneField = document.getElementById("reg_phone"); // Đảm bảo ô input số điện thoại có id="reg_phone"
const passField = document.getElementById("reg_pass");
const confirmField = document.getElementById("reg_pass_confirm");
// Reset trạng thái lỗi viền đỏ
if(phoneField) phoneField.classList.remove('input-error');
passField.classList.remove('input-error');
confirmField.classList.remove('input-error');
// 1. Kiểm tra định dạng số điện thoại Việt Nam ở Frontend
if (phoneField) {
const phoneValue = phoneField.value.trim();
// Regex chuẩn cho các đầu số di động tại Việt Nam hiện nay (10 số)
const vnf_regex = /^(03|05|07|08|09)+([0-9]{8})$/i;
if (phoneValue === '') {
msgDiv.style.color = "red";
msgDiv.innerText = "Vui lòng nhập số điện thoại!";
phoneField.classList.add('input-error');
return;
} else if (vnf_regex.test(phoneValue) === false) {
msgDiv.style.color = "red";
msgDiv.innerText = "Số điện thoại không đúng định dạng !";
phoneField.classList.add('input-error');
return;
}
}
// 2. Kiểm tra độ dài mật khẩu
if (passField.value.length < 6) {
msgDiv.style.color = "red";
msgDiv.innerText = "Mật khẩu phải chứa ít nhất 6 ký tự!";
passField.classList.add('input-error');
return;
}
// 3. Kiểm tra khớp mật khẩu
if (passField.value !== confirmField.value) {
msgDiv.style.color = "red";
msgDiv.innerText = "Mật khẩu nhập lại không khớp!";
confirmField.classList.add('input-error');
return;
}
msgDiv.style.color = "#333";
msgDiv.innerText = "Đang xử lý đăng ký...";
const formData = new FormData(regForm);
formData.append('action', 'custom_ajax_register_action');
const ajaxUrl = window.wp_data ? window.wp_data.ajax_url : '/wp-admin/admin-ajax.php';
fetch(ajaxUrl, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
msgDiv.style.color = "green";
msgDiv.innerText = data.data.message;
setTimeout(() => { window.location.reload(); }, 1000);
} else {
msgDiv.style.color = "red";
msgDiv.innerText = data.data.message;
// Tô đỏ ô tương ứng dựa theo mã lỗi backend trả về
if (data.data && data.data.code === 'phone_exists') {
if(phoneField) phoneField.classList.add('input-error');
}
}
})
.catch(error => {
msgDiv.style.color = "red";
msgDiv.innerText = "Có lỗi xảy ra, thử lại sau.";
});
};
}
// 3. XỬ LÝ AJAX GỬI LINK QUÊN MẬT KHẨU (BẢN CHUẨN HÓA BẢO MẬT)
// Định nghĩa đường dẫn AJAX an toàn của WordPress
const ajaxUrl = window.wp_data ? window.wp_data.ajax_url : '/wp-admin/admin-ajax.php';
//CHUYỂN SANG TAB QUÊN MẬT KHẨU KHI CLICK LINK
if (openLostPassForm) {
openLostPassForm.onclick = function (e) {
e.preventDefault();
if (loginFormArea) loginFormArea.classList.remove("active");
if (lostPasswordFormArea) lostPasswordFormArea.classList.add("active");
if (toggleAuthBtn) toggleAuthBtn.style.display = "none"; // Ẩn nút Đăng ký ở footer đi
};
}
//QUAY LẠI TAB ĐĂNG NHẬP KHI ẤN "QUAY LẠI"
if (backToLoginFromLost) {
backToLoginFromLost.onclick = function (e) {
e.preventDefault();
if (lostPasswordFormArea) lostPasswordFormArea.classList.remove("active");
if (loginFormArea) loginFormArea.classList.add("active");
if (toggleAuthBtn) toggleAuthBtn.style.display = "block"; // Hiện lại nút đăng ký ở footer
// Xóa sạch thông báo cũ khi quay về
const lostMsgDiv = document.getElementById("lostPopupErrors");
if (lostMsgDiv) { lostMsgDiv.innerText = ""; }
};
}
//XỬ LÝ AJAX GỬI LINK QUÊN MẬT KHẨU
const lostForm = document.getElementById("mainLostPasswordForm");
if (lostForm) {
lostForm.onsubmit = function (e) {
e.preventDefault();
const msgDiv = document.getElementById("lostPopupErrors");
const lostUserField = document.getElementById("lost_user");
if (!msgDiv || !lostUserField) return;
// Trạng thái chờ xử lý ban đầu
msgDiv.style.color = "#333";
msgDiv.innerText = "Đang xử lý gửi yêu cầu...";
lostUserField.classList.remove('input-error');
const formData = new FormData(lostForm);
formData.append('action', 'custom_ajax_lost_password');
fetch(ajaxUrl, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error("Lỗi kết nối máy chủ (HTTP " + response.status + ")");
}
return response.json();
})
.then(data => {
if (data.success) {
msgDiv.style.color = "green";
msgDiv.innerText = data.data.message;
lostForm.reset(); // Xóa sạch dữ liệu ô input vừa nhập thành công
} else {
msgDiv.style.color = "red";
msgDiv.innerText = data.data ? data.data.message : "Yêu cầu khôi phục mật khẩu thất bại.";
lostUserField.classList.add('input-error'); // Tô đỏ ô nhập liệu khi có lỗi
}
})
.catch(error => {
msgDiv.style.color = "red";
msgDiv.innerText = "Email bị sai hoặc không tồn tại.";
console.error("Chi tiết lỗi quên mật khẩu:", error);
});
};
}
});
</script>
<?php
}
// =========================================================================
// 2. KHAI BÁO SHORTCODE CHÍNH (TỰ ĐỘNG ẨN KHI ĐÃ ĐĂNG NHẬP)
// =========================================================================
add_shortcode('popup_auth', 'custom_auth_popup_shortcode_v4');
function custom_auth_popup_shortcode_v4() {
if ( is_user_logged_in() ) {
return '';
}
ob_start();
custom_auth_popup_core_html();
return ob_get_clean();
}
// =========================================================================
// 3. BACKEND PHP: XỬ LÝ AJAX ĐĂNG KÝ (VAI TRÒ KHÁCH HÀNG + CHẶN TRÙNG SĐT CHUẨN VN)
// =========================================================================
add_action('wp_ajax_nopriv_custom_ajax_register_action', 'custom_ajax_register_handler_v4');
add_action('wp_ajax_custom_ajax_register_action', 'custom_ajax_register_handler_v4');
function custom_ajax_register_handler_v4() {
$username = isset($_POST['reg_user']) ? sanitize_user($_POST['reg_user']) : '';
$phone = isset($_POST['reg_phone']) ? sanitize_text_field($_POST['reg_phone']) : '';
$email = isset($_POST['reg_email']) ? sanitize_email($_POST['reg_email']) : '';
$password = isset($_POST['reg_pass']) ? $_POST['reg_pass'] : '';
// 1. Kiểm tra rỗng các trường bắt buộc
if (empty($username) || empty($phone) || empty($email) || empty($password)) {
wp_send_json_error(array('code' => 'empty_fields', 'message' => 'Vui lòng điền đầy đủ các thông tin bắt buộc.'));
}
// 2. Kiểm tra độ dài mật khẩu
if (strlen($password) < 6) {
wp_send_json_error(array('code' => 'invalid_password', 'message' => 'Mật khẩu phải chứa ít nhất 6 ký tự.'));
}
// 3. Kiểm tra tính hợp lệ của Email
if (!is_email($email)) {
wp_send_json_error(array('code' => 'invalid_email', 'message' => 'Địa chỉ email không hợp lệ.'));
}
// 4.Kiểm tra định dạng số điện thoại chuẩn Việt Nam (10 số, bắt đầu bằng các đầu số di động hợp lệ)
$phone = str_replace(array(' ', '.', '-', '+84'), array('', '', '', '0'), $phone);
if (!preg_match('/^(03|05|07|08|09)+([0-9]{8})$/', $phone)) {
wp_send_json_error(array('code' => 'invalid_phone', 'message' => 'Số điện thoại không hợp lệ.'));
}
// 5. Kiểm tra trùng lặp tài khoản và email
if (username_exists($username)) {
wp_send_json_error(array('code' => 'username_exists', 'message' => 'Tên đăng nhập này đã tồn tại.'));
}
if (email_exists($email)) {
wp_send_json_error(array('code' => 'email_exists', 'message' => 'Email này đã được đăng ký.'));
}
// 6. Kiểm tra trùng lặp số điện thoại
$phone_query = new WP_User_Query(array(
'meta_key' => 'billing_phone',
'meta_value' => $phone,
'number' => 1,
'fields' => 'ID'
));
$existing_phones = $phone_query->get_results();
if (!empty($existing_phones)) {
wp_send_json_error(array('code' => 'phone_exists', 'message' => 'Số điện thoại này đã được đăng ký.'));
}
// 7.Tiến hành tạo user hệ thống WordPress
$user_id = wp_create_user($username, $password, $email);
if (is_wp_error($user_id)) {
wp_send_json_error(array('code' => 'create_failed', 'message' => $user_id->get_error_message()));
}
//8. Gán vai trò là Khách hàng (Customer) của WooCommerce
$user_obj = new WP_User($user_id);
$user_obj->set_role('customer');
// Lưu meta trường Số điện thoại chuẩn hóa billing/shipping cho WooCommerce
update_user_meta($user_id, 'billing_phone', $phone);
update_user_meta($user_id, 'shipping_phone', $phone);
// Tự động thiết lập cookie đăng nhập luôn tại trang hiện tại sau khi đăng ký
wp_set_current_user($user_id);
wp_set_auth_cookie($user_id, true);
wp_send_json_success(array('message' => 'Đăng ký thành công! Đang tải lại trang...'));
}
// 4.BACKEND PHP: XỬ LÝ AJAX ĐĂNG NHẬP (HỖ TRỢ USERNAME, EMAIL, SỐ ĐIỆN THOẠI)
add_action('wp_ajax_nopriv_custom_ajax_login', 'devvn_custom_ajax_login_handler');
add_action('wp_ajax_custom_ajax_login', 'devvn_custom_ajax_login_handler');
function devvn_custom_ajax_login_handler() {
// 1. Kiểm tra dữ liệu đầu vào
$username_input = isset($_POST['log']) ? sanitize_text_field($_POST['log']) : (isset($_POST['username']) ? sanitize_text_field($_POST['username']) : '');
$password = isset($_POST['pwd']) ? $_POST['pwd'] : (isset($_POST['password']) ? $_POST['password'] : '');
$remember = isset($_POST['rememberme']) ? true : false;
// Kiểm tra các trường rỗng
if (empty($username_input) || empty($password)) {
wp_send_json_error(array(
'code' => 'empty_fields',
'message' => 'Vui lòng điền đầy đủ tài khoản và mật khẩu.'
));
}
// Mặc định gán biến final bằng giá trị nhập vào
$final_username = $username_input;
// TRƯỜNG HỢP 1: NGƯỜI DÙNG ĐĂNG NHẬP BẰNG EMAIL
if ( is_email($username_input) ) {
$user_obj = get_user_by('email', $username_input);
if ( $user_obj ) {
$final_username = $user_obj->user_login;
} else {
wp_send_json_error(array(
'code' => 'invalid_email',
'message' => 'Địa chỉ Email này chưa được đăng ký tài khoản.'
));
}
}
// TRƯỜNG HỢP 2: NGƯỜI DÙNG ĐĂNG NHẬP BẰNG SỐ ĐIỆN THOẠI WOOCOMMERCE
// Nếu chuỗi nhập vào chỉ chứa ký tự số và dài từ 9 đến 11 ký tự
elseif (preg_match('/^[0-9]{9,11}$/', $username_input)) {
$user_query = new WP_User_Query(array(
'meta_key' => 'billing_phone',
'meta_value' => $username_input,
'number' => 1,
'fields' => 'all'
));
$users = $user_query->get_results();
if (!empty($users)) {
// Chuyển đổi Số điện thoại thành Username (user_login) thật trong database để WordPress nhận diện
$final_username = $users[0]->user_login;
} else {
// Trả về lỗi định dạng để JavaScript tô đỏ ngay ô Tài khoản/Số điện thoại
wp_send_json_error(array(
'code' => 'invalid_username',
'message' => 'Số điện thoại này chưa được đăng ký tài khoản.'
));
}
}
// Cấu hình thông tin gửi vào bộ xác thực mặc định của WordPress
$creds = array(
'user_login' => $final_username,
'user_password' => $password,
'remember' => $remember
);
// Tiến hành đăng nhập ngầm bảo mật
$user = wp_signon($creds, false);
// 3. Trả về kết quả khớp với Logic JavaScript
if (is_wp_error($user)) {
$error_code = $user->get_error_code();
$error_msg = 'Số điện thoại/email hoặc mật khẩu không chính xác.';
// Tùy biến thông báo cụ thể bảo mật/thân thiện
if ($error_code === 'invalid_username' || $error_code === 'invalid_email') {
$error_msg = 'Số điện thoại và email không tồn tại.';
} elseif ($error_code === 'incorrect_password') {
$error_msg = 'Mật khẩu bạn nhập chưa đúng. Vui lòng thử lại.';
}
wp_send_json_error(array(
'code' => $error_code,
'message' => $error_msg
));
}
wp_send_json_success(array(
'message' => 'Đăng nhập thành công! Đang chuyển hướng...'
));
}
// =========================================================================
// 7. BACKEND PHP: CHỈ CHUYỂN HƯỚNG KHI VÀO TRANG MY ACCOUNT MẶC ĐỊNH CỦA WOOCOMMERCE
// =========================================================================
add_action( 'template_redirect', 'devvn_redirect_only_my_account_page' );
function devvn_redirect_only_my_account_page() {
// CHỈ CHUYỂN HƯỚNG khi truy cập đúng trang tài khoản WooCommerce và người dùng CHƯA đăng nhập
if ( is_account_page() && ! is_user_logged_in() ) {
// Lưu lại đường dẫn trang tài khoản khách muốn vào để sau khi đăng nhập xong chuyển hướng ngược lại
$redirect_url = add_query_arg( 'show_auth', '1', home_url( '/' ) );
wp_safe_redirect( $redirect_url );
exit;
}
}
// Chuyển đổi toàn bộ link "Tài khoản của tôi" mặc định trên hệ thống thành hashtag gọi Popup
add_filter( 'woocommerce_get_myaccount_page_permalink', 'devvn_custom_myaccount_to_popup_link', 999 );
function devvn_custom_myaccount_to_popup_link( $permalink ) {
if ( ! is_user_logged_in() ) {
// Nếu chưa đăng nhập, ép link tài khoản chuyển về hashtag để JS kích hoạt popup trực tiếp
return '#authPopup';
}
return $permalink;
}
// =========================================================================
// 8. BACKEND PHP: XỬ LÝ AJAX GỬI LINK KHÔI PHỤC MẬT KHẨU WOOCOMMERCE
// =========================================================================
add_action('wp_ajax_nopriv_custom_ajax_lost_password', 'devvn_custom_ajax_lost_password_handler');
add_action('wp_ajax_custom_ajax_lost_password', 'devvn_custom_ajax_lost_password_handler');
function devvn_custom_ajax_lost_password_handler() {
// Nhận diện dữ liệu nhập vào (có thể là username hoặc email)
$user_login = isset($_POST['user_login']) ? sanitize_text_field(trim($_POST['user_login'])) : '';
if (empty($user_login)) {
wp_send_json_error(array('message' => 'Vui lòng nhập tên tài khoản hoặc địa chỉ email.'));
}
// ƯU TIÊN 1: Gọi hàm xử lý lấy lại mật khẩu lõi của WooCommerce (Gửi email template HTML đẹp mắt)
if ( class_exists('WC_Shortcode_My_Account') && method_exists('WC_Shortcode_My_Account', 'retrieve_password') ) {
$_POST['user_login'] = $user_login;
$result = WC_Shortcode_My_Account::retrieve_password();
if ( $result === true ) {
wp_send_json_success(array('message' => 'Liên kết đặt lại mật khẩu đã được gửi thành công. Vui lòng kiểm tra hộp thư Email của bạn.'));
} else {
wp_send_json_error(array('message' => 'Tên đăng nhập, email hoặc số điện thoại không tồn tại trên hệ thống.'));
}
} else {
// ƯU TIÊN 2: Phương án dự phòng nếu không cài WooCommerce (Sử dụng cổng mail mặc định WordPress core)
$user_data = is_email($user_login) ? get_user_by('email', $user_login) : get_user_by('login', $user_login);
if ( ! $user_data ) {
wp_send_json_error(array('message' => 'Tài khoản hoặc địa chỉ Email này không tồn tại.'));
}
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
// Tạo khóa bảo mật đặt lại mật khẩu của WordPress
$key = get_password_reset_key( $user_data );
if ( is_wp_error( $key ) ) {
wp_send_json_error(array('message' => 'Hệ thống không thể tạo mã khôi phục mật khẩu lúc này.'));
}
// Biên soạn nội dung thư điện tử khôi phục mật khẩu chuẩn hệ thống
$message = __('Có một yêu cầu thay đổi mật khẩu từ tài khoản của bạn tại:') . "\r\n\r\n";
$message .= network_home_url( '/' ) . "\r\n\r\n";
$message .= sprintf(__('Tên đăng nhập: %s'), $user_login) . "\r\n\r\n";
$message .= __('Nếu đây là sự nhầm lẫn, bạn chỉ cần bỏ qua email này.') . "\r\n\r\n";
$message .= __('Để tiến hành thay đổi mật khẩu mới, vui lòng nhấp vào đường dẫn bên dưới:') . "\r\n\r\n";
$message .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') . "\r\n";
$subject = wp_specialchars_decode( sprintf( __('[%s] Khôi phục mật khẩu tài khoản'), wp_title_rss() ) );
if ( wp_mail( $user_email, $subject, $message ) ) {
wp_send_json_success(array('message' => 'Một Email khôi phục mật khẩu đã được gửi đi thành công!'));
} else {
wp_send_json_error(array('message' => 'Hệ thống gửi thư gặp sự cố. Vui lòng liên hệ quản trị viên để kiểm tra lại SMTP.'));
}
}
}
//////////////
// 1. TẠO VÀ DI CHUYỂN CỘT SỐ ĐIỆN THOẠI XUẤT HIỆN NGAY SAU CỘT TÊN
add_filter('manage_users_columns', 'custom_add_phone_user_column');
function custom_add_phone_user_column($columns) {
$new_columns = array();
foreach ( $columns as $key => $value ) {
$new_columns[$key] = $value;
// Khi vòng lặp chạy đến cột 'name' (Tên), ta chèn ngay cột số điện thoại vào sau nó
if ( $key === 'name' ) {
$new_columns['billing_phone'] = 'Số điện thoại';
}
}
return $new_columns;
}
// 2. HIỂN THỊ DỮ LIỆU SỐ ĐIỆN THOẠI VÀO CỘT VỪA TẠO
add_filter('manage_users_custom_column', 'custom_show_phone_user_column_value', 10, 3);
function custom_show_phone_user_column_value($output, $column_name, $user_id) {
// Kiểm tra đúng tên cột billing_phone của WooCommerce
if ($column_name === 'billing_phone') {
$phone = get_user_meta($user_id, 'billing_phone', true);
// Nếu có số điện thoại thì hiển thị, không thì để dấu gạch ngang
if (!empty($phone)) {
$output = '<strong>' . esc_html($phone) . '</strong>';
} else {
$output = '<span style="color:#aaa;">—</span>';
}
}
return $output;
}
// 3. TÍCH HỢP TÍNH NĂNG CHO PHÉP TÌM KIẾM THEO SỐ ĐIỆN THOẠI
add_filter('user_search_columns', 'custom_search_user_by_phone', 10, 3);
function custom_search_user_by_phone($search_columns, $search, $wp_user_query) {
// Cho phép thanh tìm kiếm trong wp-admin quét qua cả dữ liệu meta dữ liệu số điện thoại
if (!in_array('billing_phone', $search_columns)) {
global $wpdb;
// Hook can thiệp vào câu lệnh truy vấn tìm kiếm thành viên
$wp_user_query->query_where = str_replace(
') AND',
" OR {$wpdb->users}.ID IN (SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key='billing_phone' AND meta_value LIKE '%" . esc_sql($search) . "%')) AND",
$wp_user_query->query_where
);
}
return $search_columns;
}
