Changeset 3780739
- Timestamp:
- 11/02/2026 09:09:50 PM (22 hours ago)
- Location:
- naimur-email-otp-for-woocommerce
- Files:
-
- 42 added
- 8 deleted
- 38 edited
- 1 copied
-
assets/screenshot-1.png (modified) ( previous)
-
assets/screenshot-10.png (added)
-
assets/screenshot-11.png (added)
-
assets/screenshot-12.png (added)
-
assets/screenshot-2.png (modified) ( previous)
-
assets/screenshot-3.png (modified) ( previous)
-
assets/screenshot-4.png (modified) ( previous)
-
assets/screenshot-5.png (modified) ( previous)
-
assets/screenshot-6.png (modified) ( previous)
-
assets/screenshot-7.png (modified) ( previous)
-
assets/screenshot-8.png (modified) ( previous)
-
assets/screenshot-9.png (added)
-
tags/2.1.2 (copied) (copied from naimur-email-otp-for-woocommerce/trunk )
-
tags/2.1.2/assets/admin (added)
-
tags/2.1.2/assets/admin/admin.css (added)
-
tags/2.1.2/assets/admin/admin.js (added)
-
tags/2.1.2/assets/admin/logo.png (added)
-
tags/2.1.2/assets/css/checkout-otp.css (added)
-
tags/2.1.2/assets/css/override-myaccount.css (modified) (2 diffs)
-
tags/2.1.2/assets/js/boot.js (deleted)
-
tags/2.1.2/assets/js/checkout-otp.js (added)
-
tags/2.1.2/assets/js/main.js (modified) (15 diffs)
-
tags/2.1.2/assets/src/js/main.js (modified) (13 diffs)
-
tags/2.1.2/includes/admin (added)
-
tags/2.1.2/includes/admin/class-admin.php (added)
-
tags/2.1.2/includes/admin/views (added)
-
tags/2.1.2/includes/admin/views/dashboard.php (added)
-
tags/2.1.2/includes/admin/views/diagnostics.php (added)
-
tags/2.1.2/includes/admin/views/general.php (added)
-
tags/2.1.2/includes/admin/views/help.php (added)
-
tags/2.1.2/includes/admin/views/security.php (added)
-
tags/2.1.2/includes/admin/views/templates.php (added)
-
tags/2.1.2/includes/admin/views/verification.php (added)
-
tags/2.1.2/includes/class-ajax.php (modified) (10 diffs)
-
tags/2.1.2/includes/class-checkout.php (added)
-
tags/2.1.2/includes/class-emails.php (modified) (1 diff)
-
tags/2.1.2/includes/class-logs.php (modified) (2 diffs)
-
tags/2.1.2/includes/class-options.php (added)
-
tags/2.1.2/includes/class-security.php (modified) (12 diffs)
-
tags/2.1.2/includes/class-settings.php (modified) (1 diff)
-
tags/2.1.2/includes/class-templates.php (added)
-
tags/2.1.2/includes/helpers.php (modified) (8 diffs)
-
tags/2.1.2/languages/naimur-email-otp-for-woocommerce.pot (modified) (4 diffs)
-
tags/2.1.2/naimur-email-otp-for-woocommerce.php (modified) (7 diffs)
-
tags/2.1.2/readme.txt (modified) (6 diffs)
-
tags/2.1.2/templates/email-template-1.php (deleted)
-
tags/2.1.2/templates/email-template-2.php (deleted)
-
tags/2.1.2/templates/email-template-3.php (deleted)
-
tags/2.1.2/templates/modal.php (modified) (1 diff)
-
tags/2.1.2/templates/woocommerce/myaccount/form-login.php (modified) (3 diffs)
-
tags/2.1.2/uninstall.php (modified) (1 diff)
-
trunk/assets/admin (added)
-
trunk/assets/admin/admin.css (added)
-
trunk/assets/admin/admin.js (added)
-
trunk/assets/admin/logo.png (added)
-
trunk/assets/css/checkout-otp.css (added)
-
trunk/assets/css/override-myaccount.css (modified) (2 diffs)
-
trunk/assets/js/boot.js (deleted)
-
trunk/assets/js/checkout-otp.js (added)
-
trunk/assets/js/main.js (modified) (15 diffs)
-
trunk/assets/src/js/main.js (modified) (13 diffs)
-
trunk/includes/admin (added)
-
trunk/includes/admin/class-admin.php (added)
-
trunk/includes/admin/views (added)
-
trunk/includes/admin/views/dashboard.php (added)
-
trunk/includes/admin/views/diagnostics.php (added)
-
trunk/includes/admin/views/general.php (added)
-
trunk/includes/admin/views/help.php (added)
-
trunk/includes/admin/views/security.php (added)
-
trunk/includes/admin/views/templates.php (added)
-
trunk/includes/admin/views/verification.php (added)
-
trunk/includes/class-ajax.php (modified) (10 diffs)
-
trunk/includes/class-checkout.php (added)
-
trunk/includes/class-emails.php (modified) (1 diff)
-
trunk/includes/class-logs.php (modified) (2 diffs)
-
trunk/includes/class-options.php (added)
-
trunk/includes/class-security.php (modified) (12 diffs)
-
trunk/includes/class-settings.php (modified) (1 diff)
-
trunk/includes/class-templates.php (added)
-
trunk/includes/helpers.php (modified) (8 diffs)
-
trunk/languages/naimur-email-otp-for-woocommerce.pot (modified) (4 diffs)
-
trunk/naimur-email-otp-for-woocommerce.php (modified) (7 diffs)
-
trunk/readme.txt (modified) (6 diffs)
-
trunk/templates/email-template-1.php (deleted)
-
trunk/templates/email-template-2.php (deleted)
-
trunk/templates/email-template-3.php (deleted)
-
trunk/templates/modal.php (modified) (1 diff)
-
trunk/templates/woocommerce/myaccount/form-login.php (modified) (3 diffs)
-
trunk/uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
naimur-email-otp-for-woocommerce/tags/2.1.2/assets/css/override-myaccount.css
r3587442 r3780739 88 88 #ceovw-corporate-app .ceovw-footer-nav { text-align: center; margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid #e5e7eb; font-size: 0.875rem; color: #6b7280; } 89 89 90 /* OTP Modal */ 91 #ceovw-corporate-app .ceovw-modal-overlay { 92 position: fixed; top: 0; left: 0; width: 100%; height: 100%; 93 background: rgba(17, 24, 39, 0.7); backdrop-filter: blur(4px); 94 display: flex; justify-content: center; align-items: center; z-index: 99999; 95 } 96 #ceovw-corporate-app .ceovw-modal-content { 97 background: #fff; padding: 2.5rem; border-radius: 1rem; width: 100%; max-width: 24rem; 98 position: relative; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); text-align: center; 99 animation: ceovw-slide-up 0.3s ease-out; 100 } 101 @keyframes ceovw-slide-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } 102 #ceovw-corporate-app .ceovw-modal-close { 103 position: absolute; top: 1rem; right: 1rem; background: none; border: none; font-size: 1.5rem; 104 cursor: pointer; color: #9ca3af; transition: color 0.2s; 105 } 106 #ceovw-corporate-app .ceovw-modal-close:hover { color: #111827; } 107 #ceovw-corporate-app .ceovw-otp-inputs { display: flex; justify-content: center; gap: 0.5rem; margin: 2rem 0; } 108 #ceovw-corporate-app .ceovw-otp-box { 109 width: 3rem !important; height: 3.5rem !important; text-align: center; font-size: 1.5rem !important; 110 font-weight: 700; border: 2px solid #e5e7eb !important; border-radius: 0.5rem !important; padding: 0 !important; 111 } 112 #ceovw-corporate-app .ceovw-otp-box:focus { border-color: var(--ceovw-brand) !important; } 113 #ceovw-corporate-app .ceovw-error-msg { color: #dc2626; font-size: 0.875rem; margin-bottom: 1rem; height: 1.25rem; } 114 #ceovw-corporate-app .ceovw-resend-wrapper { margin-top: 1.5rem; font-size: 0.875rem; color: #6b7280; } 115 #ceovw-corporate-app #ceovw-btn-resend:disabled { color: #9ca3af; cursor: not-allowed; text-decoration: none; } 90 /* Inline form messages (replace browser alerts) */ 91 #ceovw-corporate-app .ceovw-form-msg { margin: 0 0 1rem; padding: 0.7rem 0.9rem; border-radius: 0.6rem; font-size: 0.875rem; line-height: 1.45; text-align: left; } 92 #ceovw-corporate-app .ceovw-form-msg[hidden] { display: none; } 93 #ceovw-corporate-app .ceovw-form-msg.is-error { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; } 94 #ceovw-corporate-app .ceovw-form-msg.is-success { background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; } 95 #ceovw-corporate-app input:focus-visible, #ceovw-corporate-app button:focus-visible, #ceovw-corporate-app a:focus-visible { outline: 2px solid var(--ceovw-brand, #2563eb); outline-offset: 2px; } 96 #ceovw-corporate-app .ceovw-btn:disabled { cursor: default; opacity: .85; } 97 98 /* OTP Modal: same visual language as the checkout popup */ 99 #ceovw-corporate-app .ceovw-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; padding: 16px; background: rgba(15, 23, 42, 0.55); display: flex; justify-content: center; align-items: center; z-index: 99999; box-sizing: border-box; } 100 #ceovw-corporate-app .ceovw-modal-content { background: #fff; padding: 2rem 1.75rem 1.25rem; border-radius: 18px; width: 100%; max-width: 420px; max-height: calc(100vh - 32px); overflow-y: auto; position: relative; box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28); text-align: center; animation: ceovw-slide-up 0.2s ease-out; } 101 @keyframes ceovw-slide-up { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } } 102 #ceovw-corporate-app .ceovw-modal-close { position: absolute; top: 10px; right: 12px; width: 36px; height: 36px; padding: 0; border: none; border-radius: 10px; background: transparent; font-size: 26px; line-height: 1; cursor: pointer; color: #6b7280; } 103 #ceovw-corporate-app .ceovw-modal-close:hover { background: #f3f4f6; color: #111827; } 104 #ceovw-corporate-app .ceovw-modal-icon { display: flex; align-items: center; justify-content: center; width: 64px; height: 64px; margin: 0 auto 14px; border-radius: 50%; color: var(--ceovw-brand, #2563eb); background: #eef2ff; background: color-mix(in srgb, var(--ceovw-brand, #2563eb) 12%, #fff); } 105 #ceovw-corporate-app .ceovw-modal-header h3 { margin: 0 0 8px; font-size: 21px; font-weight: 700; line-height: 1.3; color: #111827; } 106 #ceovw-corporate-app .ceovw-modal-header p { margin: 0; font-size: 14.5px; line-height: 1.55; color: #4b5563; overflow-wrap: anywhere; } 107 #ceovw-corporate-app .ceovw-otp-inputs { display: flex; justify-content: center; gap: 8px; margin: 1.25rem 0 0.25rem; } 108 #ceovw-corporate-app .ceovw-otp-box { width: 46px !important; height: 56px !important; margin: 0 !important; padding: 0 !important; text-align: center; font-size: 24px !important; font-weight: 700; color: #111827; background: #f9fafb !important; border: 1.5px solid #d1d5db !important; border-radius: 12px !important; box-shadow: none; } 109 #ceovw-corporate-app .ceovw-otp-box:focus { border-color: var(--ceovw-brand, #2563eb) !important; background: #fff !important; box-shadow: 0 0 0 3px rgba(37, 99, 235, .15); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ceovw-brand, #2563eb) 15%, transparent); outline: none; } 110 #ceovw-corporate-app .ceovw-error-msg { min-height: 1.4rem; margin: 10px 0; color: #b91c1c; font-size: 13.5px; line-height: 1.4; } 111 #ceovw-corporate-app .ceovw-error-msg.is-success { color: #047857; font-weight: 600; } 112 #ceovw-corporate-app #ceovw-btn-verify { height: 48px; border-radius: 12px; } 113 #ceovw-corporate-app .ceovw-resend-wrapper { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin-top: 1rem; font-size: 0.875rem; color: #6b7280; } 114 #ceovw-corporate-app #ceovw-btn-resend { padding: 4px 2px; border: 0; background: none; font: inherit; font-weight: 600; color: var(--ceovw-brand, #2563eb); cursor: pointer; } 115 #ceovw-corporate-app #ceovw-btn-resend:disabled { color: #9ca3af; cursor: not-allowed; text-decoration: none; font-weight: 500; } 116 #ceovw-corporate-app .ceovw-modal-tip { margin: 12px 0 0; padding-top: 12px; border-top: 1px solid #f1f5f9; font-size: 12.5px; line-height: 1.5; color: #6b7280; } 116 117 117 118 /* Responsive */ … … 119 120 #ceovw-corporate-app .ceovw-app-card { padding: 2rem 1.5rem; } 120 121 #ceovw-corporate-app .ceovw-field-row { flex-direction: column; gap: 0; } 121 #ceovw-corporate-app .ceovw-otp-box { width: 2.5rem !important; height: 3rem !important; font-size: 1.25rem !important; } 122 #ceovw-corporate-app .ceovw-modal-overlay { align-items: flex-end; padding: 0; } 123 #ceovw-corporate-app .ceovw-modal-content { max-width: none; border-radius: 18px 18px 0 0; padding: 1.75rem 1.1rem 1.1rem; } 124 #ceovw-corporate-app .ceovw-otp-inputs { gap: 6px; } 125 #ceovw-corporate-app .ceovw-otp-box { width: calc((100% - 30px) / 6) !important; max-width: 48px; height: 52px !important; font-size: 22px !important; } 122 126 } 127 @media (prefers-reduced-motion: reduce) { 128 #ceovw-corporate-app .ceovw-modal-content, #ceovw-corporate-app .btn-loader { animation: none; } 129 } -
naimur-email-otp-for-woocommerce/tags/2.1.2/assets/js/main.js
r3780003 r3780739 2 2 "use strict"; 3 3 4 let currentFlow = ''; 4 const T = (window.CEOVW_APP && CEOVW_APP.i18n) || {}; 5 let currentFlow = ''; 5 6 let targetEmail = ''; 6 7 let resendTimer = null; 7 8 let resetToken = ''; // Password reset authorization: kept in memory only, never persisted or logged 9 let lastFocus = null; 10 let verifying = false; 11 12 // Inline form messages (replace browser alerts; announced by screen readers) 13 const formMsg = ($form, text, kind) => { 14 const $m = $form.find('.ceovw-form-msg'); 15 if (!text) { $m.prop('hidden', true).text('').removeClass('is-error is-success'); return; } 16 $m.text(text).prop('hidden', false).toggleClass('is-success', kind === 'success').toggleClass('is-error', kind !== 'success'); 17 }; 18 const showView = (target, focus) => { 19 $('.ceovw-view-pane').hide(); 20 const $v = $(`#ceovw-view-${target}`).fadeIn(300); 21 $v.find('.ceovw-form-msg').prop('hidden', true).text(''); 22 if (focus !== false) { setTimeout(() => $v.find('input:not([type=hidden]):visible').first().trigger('focus'), 320); } 23 return $v; 24 }; 25 const missing = ($form) => $form.find('input[required]').filter(function(){ return !$.trim(this.value); }).first(); 26 const validEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($.trim(v)); 8 27 9 28 // View Switcher (Smooth Fade) … … 12 31 const target = $(this).data('target'); 13 32 if (target !== 'newpass') resetToken = ''; 14 $('.ceovw-view-pane').hide(); 15 $(`#ceovw-view-${target}`).fadeIn(300); 33 showView(target); 16 34 }); 17 35 18 36 const setBtnLoading = ($btn, isLoading) => { 19 37 if(isLoading) { 20 $btn.prop('disabled', true).addClass('loading') ;38 $btn.prop('disabled', true).addClass('loading').attr('aria-busy', 'true'); 21 39 } else { 22 $btn.prop('disabled', false).removeClass('loading') ;40 $btn.prop('disabled', false).removeClass('loading').attr('aria-busy', 'false'); 23 41 } 24 42 }; 25 43 26 const getCaptchaToken = ($form) => { 27 if (CEOVW_APP.settings.captcha === 'turnstile') { 28 return $form.find('[name="cf-turnstile-response"]').val() || ''; 29 } else if (CEOVW_APP.settings.captcha === 'recaptcha') { 30 return $form.find('[name="g-recaptcha-response"]').val() || ''; 31 } 32 return ''; 44 // Resend delay: server value when provided, otherwise the per-flow setting 45 const resendDelay = (res) => { 46 const s = res && res.data ? parseInt(res.data.resend_in, 10) : 0; 47 const cfg = (CEOVW_APP.settings && CEOVW_APP.settings.resend) || {}; 48 return s > 0 ? s : (parseInt(cfg[currentFlow], 10) || 60); 33 49 }; 34 50 … … 39 55 let pass = $form.find('input[name="password"]').val(); 40 56 let confirm = $form.find('input[name="password_confirm"]').val(); 41 42 if (pass !== confirm) {43 alert("Passwords do not match!");44 return;45 }46 47 57 let email = $form.find('input[name="email"]').val(); 48 58 let name = $form.find('input[name="ceovw_full_name"]').val(); 59 const $empty = missing($form); 60 61 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 62 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="email"]').trigger('focus'); return; } 63 if (pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="password_confirm"]').trigger('focus'); return; } 64 formMsg($form, ''); 65 49 66 let $btn = $('#ceovw-btn-reg'); 50 51 67 setBtnLoading($btn, true); 52 68 … … 54 70 action: 'ceovw_process_registration_req', 55 71 nonce: CEOVW_APP.nonce, 56 email: email, name: name, password: pass, 57 captcha: getCaptchaToken($form) 72 email: email, name: name, password: pass 58 73 }, function(res){ 59 74 setBtnLoading($btn, false); … … 62 77 targetEmail = email; 63 78 resetToken = ''; 64 openOTPModal(email); // JSON Success হলে এই পপআপটি ওপেন হবে 65 } else { 66 alert(res.data.message || CEOVW_APP.i18n.error); 67 } 68 }).fail(function(xhr, status, error) { 69 setBtnLoading($btn, false); 70 console.error("AJAX Error:", error); 71 alert(CEOVW_APP.i18n.error); 79 openOTPModal(email, resendDelay(res)); 80 } else { 81 formMsg($form, (res.data && res.data.message) || T.error); 82 } 83 }).fail(function() { 84 setBtnLoading($btn, false); 85 formMsg($form, T.error); 72 86 }); 73 87 }); … … 78 92 let $form = $(this); 79 93 let email = $form.find('input[name="reset_email"]').val(); 94 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="reset_email"]').trigger('focus'); return; } 95 formMsg($form, ''); 80 96 let $btn = $('#ceovw-btn-forgot'); 81 97 resetToken = ''; … … 86 102 action: 'ceovw_process_forgot_req', 87 103 nonce: CEOVW_APP.nonce, 88 email: email, 89 captcha: getCaptchaToken($form) 104 email: email 90 105 }, function(res){ 91 106 setBtnLoading($btn, false); … … 93 108 currentFlow = 'forgot'; 94 109 targetEmail = email; 95 openOTPModal(email );96 } else { 97 alert(res.data.message || CEOVW_APP.i18n.error);98 } 99 }).fail(function() { 100 setBtnLoading($btn, false); 101 alert(CEOVW_APP.i18n.error);110 openOTPModal(email, resendDelay(res)); 111 } else { 112 formMsg($form, (res.data && res.data.message) || T.error); 113 } 114 }).fail(function() { 115 setBtnLoading($btn, false); 116 formMsg($form, T.error); 102 117 }); 103 118 }); … … 106 121 const $modal = $('#ceovw-otp-modal'); 107 122 const $otpInputs = $('.ceovw-otp-box'); 108 109 function openOTPModal(email) { 123 const $otpMsg = $('#ceovw-otp-error'); 124 const otpMessage = (text, kind) => { $otpMsg.text(text || '').toggleClass('is-success', kind === 'success'); }; 125 const code = () => $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 126 127 function openOTPModal(email, wait) { 128 lastFocus = document.activeElement; 110 129 $('#ceovw-target-email').text(email); 111 130 $otpInputs.val(''); 112 $('#ceovw-otp-error').text(''); 131 otpMessage(''); 132 verifying = false; 113 133 $modal.fadeIn(200); 114 $otpInputs.first(). focus();115 startResendTimer( 60);134 $otpInputs.first().trigger('focus'); 135 startResendTimer(wait || resendDelay()); 116 136 } 117 137 118 $modal.find('.ceovw-modal-close').on('click', () => $modal.fadeOut(200)); 138 function closeOTPModal() { 139 $modal.fadeOut(200); 140 if (lastFocus && document.body.contains(lastFocus)) { lastFocus.focus(); } 141 } 142 143 $modal.find('.ceovw-modal-close').on('click', closeOTPModal); 144 $modal.on('click', function(e){ if (e.target === this) { closeOTPModal(); } }); 145 146 // Keyboard: Escape closes, Tab stays inside the dialog 147 $modal.on('keydown', function(e){ 148 if (e.key === 'Escape') { e.preventDefault(); closeOTPModal(); return; } 149 if (e.key !== 'Tab') return; 150 const $f = $modal.find('button:not(:disabled), input').filter(':visible'); 151 if (!$f.length) return; 152 const first = $f[0], last = $f[$f.length - 1]; 153 if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } 154 else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } 155 }); 156 157 // Spread several digits (paste or one-time-code autofill) across the boxes starting at index `from` 158 const fill = (digits, from) => { 159 digits.split('').slice(0, 6 - from).forEach((d, i) => { $otpInputs.eq(from + i).val(d); }); 160 const next = Math.min(from + digits.length, 5); 161 $otpInputs.eq(next).trigger('focus'); 162 }; 163 const maybeSubmit = () => { if (code().length === 6) $('#ceovw-btn-verify').trigger('click'); }; 119 164 120 165 $otpInputs.on('input', function() { 121 this.value = this.value.replace(/\D/g, ''); 122 if(this.value && $(this).next('.ceovw-otp-box').length) { 123 $(this).next('.ceovw-otp-box').focus(); 166 const digits = this.value.replace(/\D/g, ''); 167 const i = $otpInputs.index(this); 168 if (digits.length > 1) { this.value = ''; fill(digits, digits.length >= 6 ? 0 : i); } 169 else { 170 this.value = digits; 171 if (digits && i < 5) { $otpInputs.eq(i + 1).trigger('focus'); } 124 172 } 125 let fullCode = $otpInputs.map(function(){ return $(this).val(); }).get().join('');126 if(fullCode.length === 6) $('#ceovw-btn-verify').click();173 if (digits) otpMessage(''); 174 maybeSubmit(); 127 175 }); 128 176 129 177 $otpInputs.on('keydown', function(e) { 130 if(e.key === "Backspace" && !this.value && $(this).prev('.ceovw-otp-box').length) { 131 $(this).prev('.ceovw-otp-box').focus().val(''); 132 } 133 }); 178 const i = $otpInputs.index(this); 179 if (e.key === "Backspace" && !this.value && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).val('').trigger('focus'); } 180 else if (e.key === 'ArrowLeft' && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).trigger('focus'); } 181 else if (e.key === 'ArrowRight' && i < 5) { e.preventDefault(); $otpInputs.eq(i + 1).trigger('focus'); } 182 else if (e.key === 'Enter') { e.preventDefault(); $('#ceovw-btn-verify').trigger('click'); } 183 }); 184 185 $otpInputs.on('focus', function(){ this.select(); }); 134 186 135 187 $otpInputs.on('paste', function(e) { 136 188 e.preventDefault(); 137 let pasteData = (e.originalEvent || e).clipboardData.getData('text').replace(/\D/g, "").slice(0, 6); 138 $otpInputs.each(function(i) { 139 if(pasteData[i]) $(this).val(pasteData[i]); 140 }); 141 if(pasteData.length === 6) $('#ceovw-btn-verify').click(); 189 const pasteData = ((e.originalEvent || e).clipboardData.getData('text') || '').replace(/\D/g, "").slice(0, 6); 190 if (!pasteData) return; 191 fill(pasteData, pasteData.length >= 6 ? 0 : $otpInputs.index(this)); 192 maybeSubmit(); 142 193 }); 143 194 144 195 // Verify OTP 145 196 $('#ceovw-btn-verify').on('click', function(){ 146 let code = $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 147 if(code.length !== 6) return; 197 const value = code(); 198 if (verifying) return; 199 if (value.length !== 6) { otpMessage(T.enterCode); $otpInputs.filter(function(){ return !this.value; }).first().trigger('focus'); return; } 148 200 149 201 let $btn = $(this); 202 verifying = true; 150 203 setBtnLoading($btn, true); 151 $('#ceovw-otp-error').text('');204 otpMessage(''); 152 205 153 206 $.post(CEOVW_APP.ajax_url, { 154 207 action: 'ceovw_verify_otp', 155 208 nonce: CEOVW_APP.nonce, 156 email: targetEmail, code: code, flow: currentFlow 157 }, function(res){ 158 if(res.success) { 159 $btn.find('.btn-text').text(CEOVW_APP.i18n.verified); 209 email: targetEmail, code: value, flow: currentFlow 210 }, function(res){ 211 if(res.success) { 212 $btn.find('.btn-text').text(T.verified); 213 otpMessage(T.verified, 'success'); 160 214 setTimeout(() => { 161 215 $modal.fadeOut(200); … … 164 218 } else if (currentFlow === 'forgot') { 165 219 resetToken = (res.data && res.data.reset_token) || ''; 166 $('.ceovw-view-pane').hide(); 167 $('#ceovw-view-newpass').fadeIn(300); 220 showView('newpass'); 168 221 $('#ceovw-verified-email').val(targetEmail); 169 222 setBtnLoading($btn, false); 170 $btn.find('.btn-text').text('Verify Code'); 223 $btn.find('.btn-text').text(T.verifyBtn); 224 verifying = false; 171 225 } 172 226 }, 1000); 173 227 } else { 228 verifying = false; 174 229 setBtnLoading($btn, false); 175 $('#ceovw-otp-error').text(res.data.message || CEOVW_APP.i18n.error);230 otpMessage((res.data && res.data.message) || T.error); 176 231 $otpInputs.val(''); 177 $otpInputs.first().focus(); 178 } 179 }).fail(function() { 180 setBtnLoading($btn, false); 181 $('#ceovw-otp-error').text(CEOVW_APP.i18n.error); 232 $otpInputs.first().trigger('focus'); 233 } 234 }).fail(function() { 235 verifying = false; 236 setBtnLoading($btn, false); 237 otpMessage(T.error); 182 238 }); 183 239 }); … … 189 245 let $txt = $('#ceovw-timer-text'); 190 246 $btn.prop('disabled', true); 191 247 $txt.text(`(${seconds}s)`); 248 192 249 resendTimer = setInterval(() => { 193 250 seconds--; … … 205 262 let $btn = $(this); 206 263 $btn.prop('disabled', true); 207 $('#ceovw-timer-text').text(' ...');208 264 $('#ceovw-timer-text').text('…'); 265 209 266 $.post(CEOVW_APP.ajax_url, { 210 267 action: currentFlow === 'register' ? 'ceovw_process_registration_req' : 'ceovw_process_forgot_req', … … 214 271 }, function(res){ 215 272 if(res.success) { 216 startResendTimer(60); 217 $('#ceovw-otp-error').text('New code sent!').css('color', 'green'); 218 setTimeout(()=> $('#ceovw-otp-error').text('').css('color', '#dc2626'), 3000); 219 } else { 220 alert(res.data.message); 221 $btn.prop('disabled', false); 222 } 223 }); 273 startResendTimer(resendDelay(res)); 274 otpMessage(T.codeSent, 'success'); 275 $otpInputs.val(''); $otpInputs.first().trigger('focus'); 276 } else { 277 otpMessage((res.data && res.data.message) || T.error); 278 if (res.data && parseInt(res.data.resend_in, 10) > 0) { startResendTimer(parseInt(res.data.resend_in, 10)); } 279 else { $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); } 280 } 281 }).fail(() => { otpMessage(T.error); $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); }); 224 282 }); 225 283 … … 230 288 let pass = $form.find('input[name="new_password"]').val(); 231 289 let confirm = $form.find('input[name="new_password_confirm"]').val(); 232 233 if(pass !== confirm) { alert("Passwords do not match."); return; } 234 290 const $empty = missing($form); 291 292 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 293 if(pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="new_password_confirm"]').trigger('focus'); return; } 294 formMsg($form, ''); 295 235 296 let $btn = $('#ceovw-btn-setpass'); 236 297 setBtnLoading($btn, true); … … 246 307 setBtnLoading($btn, false); 247 308 $form[0].reset(); 248 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);249 $ ('.ceovw-view-pane').hide();250 $('#ceovw-form-login input[name="username"]').val(targetEmail);251 $('#ceovw-view-login').fadeIn(300);309 const $login = showView('login', false); 310 $login.find('input[name="username"]').val(targetEmail); 311 formMsg($login.find('form'), (res.data && res.data.message) || T.updated, 'success'); 312 setTimeout(() => $login.find('input[name="password"]').trigger('focus'), 320); 252 313 } else { 253 314 setBtnLoading($btn, false); 254 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);255 315 if (res.data && res.data.code === 'reset_expired') { 256 316 // Authorization expired or already used: restart the forgot password flow 257 317 resetToken = ''; 258 318 $form[0].reset(); 259 $('.ceovw-view-pane').hide(); 260 $('#ceovw-view-forgot').fadeIn(300); 319 const $forgot = showView('forgot'); 320 formMsg($forgot.find('form'), (res.data && res.data.message) || T.error); 321 } else { 322 formMsg($form, (res.data && res.data.message) || T.error); 261 323 } 262 324 } 263 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 264 }); 265 325 }).fail(() => { setBtnLoading($btn, false); formMsg($form, T.error); }); 326 }); 266 327 })(jQuery); -
naimur-email-otp-for-woocommerce/tags/2.1.2/assets/src/js/main.js
r3780003 r3780739 2 2 "use strict"; 3 3 4 let currentFlow = ''; 4 const T = (window.CEOVW_APP && CEOVW_APP.i18n) || {}; 5 let currentFlow = ''; 5 6 let targetEmail = ''; 6 7 let resendTimer = null; 7 8 let resetToken = ''; // Password reset authorization: kept in memory only, never persisted or logged 9 let lastFocus = null; 10 let verifying = false; 11 12 // Inline form messages (replace browser alerts; announced by screen readers) 13 const formMsg = ($form, text, kind) => { 14 const $m = $form.find('.ceovw-form-msg'); 15 if (!text) { $m.prop('hidden', true).text('').removeClass('is-error is-success'); return; } 16 $m.text(text).prop('hidden', false).toggleClass('is-success', kind === 'success').toggleClass('is-error', kind !== 'success'); 17 }; 18 const showView = (target, focus) => { 19 $('.ceovw-view-pane').hide(); 20 const $v = $(`#ceovw-view-${target}`).fadeIn(300); 21 $v.find('.ceovw-form-msg').prop('hidden', true).text(''); 22 if (focus !== false) { setTimeout(() => $v.find('input:not([type=hidden]):visible').first().trigger('focus'), 320); } 23 return $v; 24 }; 25 const missing = ($form) => $form.find('input[required]').filter(function(){ return !$.trim(this.value); }).first(); 26 const validEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($.trim(v)); 8 27 9 28 // View Switcher (Smooth Fade) … … 12 31 const target = $(this).data('target'); 13 32 if (target !== 'newpass') resetToken = ''; 14 $('.ceovw-view-pane').hide(); 15 $(`#ceovw-view-${target}`).fadeIn(300); 16 }); 17 18 // Helper: Button Loading State 33 showView(target); 34 }); 35 19 36 const setBtnLoading = ($btn, isLoading) => { 20 37 if(isLoading) { 21 $btn.prop('disabled', true).addClass('loading') ;38 $btn.prop('disabled', true).addClass('loading').attr('aria-busy', 'true'); 22 39 } else { 23 $btn.prop('disabled', false).removeClass('loading') ;40 $btn.prop('disabled', false).removeClass('loading').attr('aria-busy', 'false'); 24 41 } 25 42 }; 26 43 27 // Helper: Captcha Token 28 const getCaptchaToken = ($form) => { 29 if (CEOVW_APP.settings.captcha === 'turnstile') { 30 return $form.find('[name="cf-turnstile-response"]').val() || ''; 31 } else if (CEOVW_APP.settings.captcha === 'recaptcha') { 32 return $form.find('[name="g-recaptcha-response"]').val() || ''; 33 } 34 return ''; 35 }; 36 37 // Registration Form Submit 44 // Resend delay: server value when provided, otherwise the per-flow setting 45 const resendDelay = (res) => { 46 const s = res && res.data ? parseInt(res.data.resend_in, 10) : 0; 47 const cfg = (CEOVW_APP.settings && CEOVW_APP.settings.resend) || {}; 48 return s > 0 ? s : (parseInt(cfg[currentFlow], 10) || 60); 49 }; 50 51 // Registration Flow 38 52 $('#ceovw-form-register').on('submit', function(e){ 39 53 e.preventDefault(); … … 41 55 let pass = $form.find('input[name="password"]').val(); 42 56 let confirm = $form.find('input[name="password_confirm"]').val(); 43 44 if (pass !== confirm) {45 alert("Passwords do not match!");46 return;47 }48 49 57 let email = $form.find('input[name="email"]').val(); 50 58 let name = $form.find('input[name="ceovw_full_name"]').val(); 59 const $empty = missing($form); 60 61 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 62 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="email"]').trigger('focus'); return; } 63 if (pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="password_confirm"]').trigger('focus'); return; } 64 formMsg($form, ''); 65 51 66 let $btn = $('#ceovw-btn-reg'); 52 53 67 setBtnLoading($btn, true); 54 68 … … 56 70 action: 'ceovw_process_registration_req', 57 71 nonce: CEOVW_APP.nonce, 58 email: email, name: name, password: pass, 59 captcha: getCaptchaToken($form) 72 email: email, name: name, password: pass 60 73 }, function(res){ 61 74 setBtnLoading($btn, false); … … 64 77 targetEmail = email; 65 78 resetToken = ''; 66 openOTPModal(email); 67 } else { 68 alert(res.data.message || CEOVW_APP.i18n.error); 69 } 70 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 71 }); 72 73 // Forgot Password Form Submit 79 openOTPModal(email, resendDelay(res)); 80 } else { 81 formMsg($form, (res.data && res.data.message) || T.error); 82 } 83 }).fail(function() { 84 setBtnLoading($btn, false); 85 formMsg($form, T.error); 86 }); 87 }); 88 89 // Forgot Password Flow 74 90 $('#ceovw-form-forgot').on('submit', function(e){ 75 91 e.preventDefault(); 76 92 let $form = $(this); 77 93 let email = $form.find('input[name="reset_email"]').val(); 94 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="reset_email"]').trigger('focus'); return; } 95 formMsg($form, ''); 78 96 let $btn = $('#ceovw-btn-forgot'); 79 97 resetToken = ''; … … 84 102 action: 'ceovw_process_forgot_req', 85 103 nonce: CEOVW_APP.nonce, 86 email: email, 87 captcha: getCaptchaToken($form) 104 email: email 88 105 }, function(res){ 89 106 setBtnLoading($btn, false); … … 91 108 currentFlow = 'forgot'; 92 109 targetEmail = email; 93 openOTPModal(email); 94 } else { 95 alert(res.data.message || CEOVW_APP.i18n.error); 96 } 97 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 98 }); 99 100 // OTP Modal Logic & Auto-tab 110 openOTPModal(email, resendDelay(res)); 111 } else { 112 formMsg($form, (res.data && res.data.message) || T.error); 113 } 114 }).fail(function() { 115 setBtnLoading($btn, false); 116 formMsg($form, T.error); 117 }); 118 }); 119 120 // OTP Modal Logic 101 121 const $modal = $('#ceovw-otp-modal'); 102 122 const $otpInputs = $('.ceovw-otp-box'); 103 104 function openOTPModal(email) { 123 const $otpMsg = $('#ceovw-otp-error'); 124 const otpMessage = (text, kind) => { $otpMsg.text(text || '').toggleClass('is-success', kind === 'success'); }; 125 const code = () => $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 126 127 function openOTPModal(email, wait) { 128 lastFocus = document.activeElement; 105 129 $('#ceovw-target-email').text(email); 106 130 $otpInputs.val(''); 107 $('#ceovw-otp-error').text(''); 131 otpMessage(''); 132 verifying = false; 108 133 $modal.fadeIn(200); 109 $otpInputs.first(). focus();110 startResendTimer( 60); // 60s cooldown start134 $otpInputs.first().trigger('focus'); 135 startResendTimer(wait || resendDelay()); 111 136 } 112 137 113 $modal.find('.ceovw-modal-close').on('click', () => $modal.fadeOut(200)); 138 function closeOTPModal() { 139 $modal.fadeOut(200); 140 if (lastFocus && document.body.contains(lastFocus)) { lastFocus.focus(); } 141 } 142 143 $modal.find('.ceovw-modal-close').on('click', closeOTPModal); 144 $modal.on('click', function(e){ if (e.target === this) { closeOTPModal(); } }); 145 146 // Keyboard: Escape closes, Tab stays inside the dialog 147 $modal.on('keydown', function(e){ 148 if (e.key === 'Escape') { e.preventDefault(); closeOTPModal(); return; } 149 if (e.key !== 'Tab') return; 150 const $f = $modal.find('button:not(:disabled), input').filter(':visible'); 151 if (!$f.length) return; 152 const first = $f[0], last = $f[$f.length - 1]; 153 if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } 154 else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } 155 }); 156 157 // Spread several digits (paste or one-time-code autofill) across the boxes starting at index `from` 158 const fill = (digits, from) => { 159 digits.split('').slice(0, 6 - from).forEach((d, i) => { $otpInputs.eq(from + i).val(d); }); 160 const next = Math.min(from + digits.length, 5); 161 $otpInputs.eq(next).trigger('focus'); 162 }; 163 const maybeSubmit = () => { if (code().length === 6) $('#ceovw-btn-verify').trigger('click'); }; 114 164 115 165 $otpInputs.on('input', function() { 116 this.value = this.value.replace(/\D/g, ''); 117 if(this.value && $(this).next('.ceovw-otp-box').length) { 118 $(this).next('.ceovw-otp-box').focus(); 166 const digits = this.value.replace(/\D/g, ''); 167 const i = $otpInputs.index(this); 168 if (digits.length > 1) { this.value = ''; fill(digits, digits.length >= 6 ? 0 : i); } 169 else { 170 this.value = digits; 171 if (digits && i < 5) { $otpInputs.eq(i + 1).trigger('focus'); } 119 172 } 120 let fullCode = $otpInputs.map(function(){ return $(this).val(); }).get().join('');121 if(fullCode.length === 6) $('#ceovw-btn-verify').click();173 if (digits) otpMessage(''); 174 maybeSubmit(); 122 175 }); 123 176 124 177 $otpInputs.on('keydown', function(e) { 125 if(e.key === "Backspace" && !this.value && $(this).prev('.ceovw-otp-box').length) { 126 $(this).prev('.ceovw-otp-box').focus().val(''); 127 } 128 }); 178 const i = $otpInputs.index(this); 179 if (e.key === "Backspace" && !this.value && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).val('').trigger('focus'); } 180 else if (e.key === 'ArrowLeft' && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).trigger('focus'); } 181 else if (e.key === 'ArrowRight' && i < 5) { e.preventDefault(); $otpInputs.eq(i + 1).trigger('focus'); } 182 else if (e.key === 'Enter') { e.preventDefault(); $('#ceovw-btn-verify').trigger('click'); } 183 }); 184 185 $otpInputs.on('focus', function(){ this.select(); }); 129 186 130 187 $otpInputs.on('paste', function(e) { 131 188 e.preventDefault(); 132 let pasteData = (e.originalEvent || e).clipboardData.getData('text').replace(/\D/g, "").slice(0, 6); 133 $otpInputs.each(function(i) { 134 if(pasteData[i]) $(this).val(pasteData[i]); 135 }); 136 if(pasteData.length === 6) $('#ceovw-btn-verify').click(); 137 }); 138 139 // Verify OTP Action 189 const pasteData = ((e.originalEvent || e).clipboardData.getData('text') || '').replace(/\D/g, "").slice(0, 6); 190 if (!pasteData) return; 191 fill(pasteData, pasteData.length >= 6 ? 0 : $otpInputs.index(this)); 192 maybeSubmit(); 193 }); 194 195 // Verify OTP 140 196 $('#ceovw-btn-verify').on('click', function(){ 141 let code = $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 142 if(code.length !== 6) return; 197 const value = code(); 198 if (verifying) return; 199 if (value.length !== 6) { otpMessage(T.enterCode); $otpInputs.filter(function(){ return !this.value; }).first().trigger('focus'); return; } 143 200 144 201 let $btn = $(this); 202 verifying = true; 145 203 setBtnLoading($btn, true); 146 $('#ceovw-otp-error').text('');204 otpMessage(''); 147 205 148 206 $.post(CEOVW_APP.ajax_url, { 149 207 action: 'ceovw_verify_otp', 150 208 nonce: CEOVW_APP.nonce, 151 email: targetEmail, code: code, flow: currentFlow 152 }, function(res){ 153 if(res.success) { 154 $btn.find('.btn-text').text(CEOVW_APP.i18n.verified); 209 email: targetEmail, code: value, flow: currentFlow 210 }, function(res){ 211 if(res.success) { 212 $btn.find('.btn-text').text(T.verified); 213 otpMessage(T.verified, 'success'); 155 214 setTimeout(() => { 156 215 $modal.fadeOut(200); … … 159 218 } else if (currentFlow === 'forgot') { 160 219 resetToken = (res.data && res.data.reset_token) || ''; 161 $('.ceovw-view-pane').hide(); 162 $('#ceovw-view-newpass').fadeIn(300); 220 showView('newpass'); 163 221 $('#ceovw-verified-email').val(targetEmail); 164 222 setBtnLoading($btn, false); 165 $btn.find('.btn-text').text('Verify Code'); 223 $btn.find('.btn-text').text(T.verifyBtn); 224 verifying = false; 166 225 } 167 226 }, 1000); 168 227 } else { 228 verifying = false; 169 229 setBtnLoading($btn, false); 170 $('#ceovw-otp-error').text(res.data.message || CEOVW_APP.i18n.error);230 otpMessage((res.data && res.data.message) || T.error); 171 231 $otpInputs.val(''); 172 $otpInputs.first().focus(); 173 } 174 }).fail(() => { setBtnLoading($btn, false); $('#ceovw-otp-error').text(CEOVW_APP.i18n.error); }); 175 }); 176 177 // Timer Logic for Resend 232 $otpInputs.first().trigger('focus'); 233 } 234 }).fail(function() { 235 verifying = false; 236 setBtnLoading($btn, false); 237 otpMessage(T.error); 238 }); 239 }); 240 241 // Resend Timer 178 242 function startResendTimer(seconds) { 179 243 clearInterval(resendTimer); … … 181 245 let $txt = $('#ceovw-timer-text'); 182 246 $btn.prop('disabled', true); 183 247 $txt.text(`(${seconds}s)`); 248 184 249 resendTimer = setInterval(() => { 185 250 seconds--; … … 197 262 let $btn = $(this); 198 263 $btn.prop('disabled', true); 199 $('#ceovw-timer-text').text(' ...');200 264 $('#ceovw-timer-text').text('…'); 265 201 266 $.post(CEOVW_APP.ajax_url, { 202 267 action: currentFlow === 'register' ? 'ceovw_process_registration_req' : 'ceovw_process_forgot_req', … … 206 271 }, function(res){ 207 272 if(res.success) { 208 startResendTimer(60); 209 $('#ceovw-otp-error').text('New code sent!').css('color', 'green'); 210 setTimeout(()=> $('#ceovw-otp-error').text('').css('color', '#dc2626'), 3000); 211 } else { 212 alert(res.data.message); 213 $btn.prop('disabled', false); 214 } 215 }); 216 }); 217 218 // Set New Password Action 273 startResendTimer(resendDelay(res)); 274 otpMessage(T.codeSent, 'success'); 275 $otpInputs.val(''); $otpInputs.first().trigger('focus'); 276 } else { 277 otpMessage((res.data && res.data.message) || T.error); 278 if (res.data && parseInt(res.data.resend_in, 10) > 0) { startResendTimer(parseInt(res.data.resend_in, 10)); } 279 else { $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); } 280 } 281 }).fail(() => { otpMessage(T.error); $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); }); 282 }); 283 284 // Set New Password 219 285 $('#ceovw-form-newpass').on('submit', function(e){ 220 286 e.preventDefault(); … … 222 288 let pass = $form.find('input[name="new_password"]').val(); 223 289 let confirm = $form.find('input[name="new_password_confirm"]').val(); 224 225 if(pass !== confirm) { alert("Passwords do not match."); return; } 226 290 const $empty = missing($form); 291 292 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 293 if(pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="new_password_confirm"]').trigger('focus'); return; } 294 formMsg($form, ''); 295 227 296 let $btn = $('#ceovw-btn-setpass'); 228 297 setBtnLoading($btn, true); … … 238 307 setBtnLoading($btn, false); 239 308 $form[0].reset(); 240 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);241 $ ('.ceovw-view-pane').hide();242 $('#ceovw-form-login input[name="username"]').val(targetEmail);243 $('#ceovw-view-login').fadeIn(300);309 const $login = showView('login', false); 310 $login.find('input[name="username"]').val(targetEmail); 311 formMsg($login.find('form'), (res.data && res.data.message) || T.updated, 'success'); 312 setTimeout(() => $login.find('input[name="password"]').trigger('focus'), 320); 244 313 } else { 245 314 setBtnLoading($btn, false); 246 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);247 315 if (res.data && res.data.code === 'reset_expired') { 248 316 // Authorization expired or already used: restart the forgot password flow 249 317 resetToken = ''; 250 318 $form[0].reset(); 251 $('.ceovw-view-pane').hide(); 252 $('#ceovw-view-forgot').fadeIn(300); 319 const $forgot = showView('forgot'); 320 formMsg($forgot.find('form'), (res.data && res.data.message) || T.error); 321 } else { 322 formMsg($form, (res.data && res.data.message) || T.error); 253 323 } 254 324 } 255 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 256 }); 257 325 }).fail(() => { setBtnLoading($btn, false); formMsg($form, T.error); }); 326 }); 258 327 })(jQuery); -
naimur-email-otp-for-woocommerce/tags/2.1.2/includes/class-ajax.php
r3780003 r3780739 22 22 return ($ip === '::1' || $ip === '127.0.0.1') ? '127.0.0.1 (Localhost)' : $ip; 23 23 } 24 25 /** Coarse device label for activity logs (no full user agent is stored). */ 26 public static function device_label() { return self::get_user_device(); } 24 27 25 28 private static function get_user_device() { … … 78 81 $is_resend = ! empty( $_POST['is_resend'] ); 79 82 83 if ( ! CEOVW_Options::get( 'registration_enabled' ) ) { wp_send_json_error(['message' => __( 'Registration is not available.', 'naimur-email-otp-for-woocommerce' )]); } 80 84 if ( ! is_email( $email ) ) { wp_send_json_error(['message' => 'Invalid email address.']); } 81 85 if ( email_exists( $email ) ) { wp_send_json_error(['message' => 'An account is already registered with your email address.']); } 82 86 if ( CEOVW_Security::is_rate_limited( 'send' ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } 83 87 if ( ! CEOVW_Security::can_request_otp( $email ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } 88 $wait = CEOVW_Security::resend_wait( $email, 'register' ); 89 /* translators: %d: seconds */ 90 if ( $wait > 0 ) { wp_send_json_error(['message' => sprintf( __( 'Please wait %d seconds before requesting a new code.', 'naimur-email-otp-for-woocommerce' ), $wait ), 'resend_in' => $wait]); } 84 91 85 92 if ( $is_resend && $password === '' ) { … … 101 108 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Success', 'message' => 'Registration OTP sent via email.']); 102 109 CEOVW_Security::record_otp_request($email); 103 wp_send_json_success(['message' => 'OTP sent successfully.' ]);110 wp_send_json_success(['message' => 'OTP sent successfully.', 'resend_in' => CEOVW_Options::resend_seconds( 'register' )]); 104 111 } else { 105 CEOVW_Security::clear_ otp( $email);112 CEOVW_Security::clear_flow( $email, 'register' ); 106 113 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Failed', 'message' => 'Server failed to send email.']); 107 114 wp_send_json_error(['message' => 'Failed to send email. Check SMTP settings.']); … … 117 124 $generic = 'If an account exists for this email, a verification code has been sent.'; 118 125 126 if ( ! CEOVW_Options::get( 'forgot_enabled' ) ) { wp_send_json_error(['message' => __( 'Password reset by code is not available. Please use the standard lost password link.', 'naimur-email-otp-for-woocommerce' )]); } 119 127 if ( ! is_email( $email ) ) { wp_send_json_error(['message' => 'Please enter a valid email address.']); } 120 128 if ( CEOVW_Security::is_rate_limited( 'send' ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } … … 131 139 } 132 140 141 // Server-side resend cooldown. The response stays identical, so the cooldown cannot reveal whether an account exists. 142 if ( CEOVW_Security::resend_wait( $email, 'forgot' ) > 0 ) { 143 self::clean_buffer(); 144 wp_send_json_success(['message' => $generic]); 145 } 146 133 147 $otp = CEOVW_Security::generate_and_save_otp( $email, 'forgot' ); 134 $sent = $otp !== false && CEOVW_Emails::send_otp_email( $email, $otp, 'forgot' );148 $sent = $otp !== false && CEOVW_Emails::send_otp_email( $email, $otp, 'forgot', $user->display_name ); 135 149 136 150 self::clean_buffer(); … … 139 153 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Success', 'message' => 'Password reset OTP sent.']); 140 154 } else { 141 CEOVW_Security::clear_ otp( $email);155 CEOVW_Security::clear_flow( $email, 'forgot' ); 142 156 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Failed', 'message' => 'Server failed to send email.']); 143 157 } … … 154 168 $code = sanitize_text_field( $_POST['code'] ?? '' ); 155 169 $flow = sanitize_text_field( $_POST['flow'] ?? '' ); 170 171 // This endpoint serves the My Account flows only; checkout has its own session-bound endpoint. 172 if ( ! in_array( $flow, ['register', 'forgot'], true ) ) { wp_send_json_error(['message' => 'Invalid code. Please try again.']); } 173 if ( ( 'register' === $flow && ! CEOVW_Options::get( 'registration_enabled' ) ) || ( 'forgot' === $flow && ! CEOVW_Options::get( 'forgot_enabled' ) ) ) { 174 wp_send_json_error(['message' => __( 'This verification is not available.', 'naimur-email-otp-for-woocommerce' )]); 175 } 156 176 157 177 $row = CEOVW_Security::verify_otp_code( $email, $code, $flow ); … … 175 195 CEOVW_Users::auto_login( $user_id ); 176 196 CEOVW_Security::clear_otp( $email ); 177 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user_id, 'email' => $email, 'ip' => $real_ip, 'device' => $device ]);197 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user_id, 'email' => $email, 'ip' => $real_ip, 'device' => $device, 'event' => 'registration_verified']); 178 198 wp_send_json_success(['message' => 'Account created and verified.']); 179 199 } else { … … 188 208 189 209 $user = get_user_by( 'email', $email ); 190 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user ? $user->ID : 0, 'email' => $email, 'ip' => $real_ip, 'device' => $device ]);210 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user ? $user->ID : 0, 'email' => $email, 'ip' => $real_ip, 'device' => $device, 'event' => 'reset_verified']); 191 211 // The raw token is only returned to this browser flow; the database stores its SHA-256 hash. 192 212 wp_send_json_success(['message' => 'Code verified.', 'reset_token' => $token]); … … 199 219 200 220 $expired = ['message' => 'Your password reset session has expired. Please request a new code.', 'code' => 'reset_expired']; 221 if ( ! CEOVW_Options::get( 'forgot_enabled' ) ) { wp_send_json_error( $expired ); } 201 222 202 223 if ( CEOVW_Security::is_rate_limited( 'reset' ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } -
naimur-email-otp-for-woocommerce/tags/2.1.2/includes/class-emails.php
r3587442 r3780739 4 4 class CEOVW_Emails { 5 5 6 /** Internal flow name -> template type. */ 7 public static function type_for_flow( $flow ) { 8 $map = array( 'register' => 'registration', 'forgot' => 'forgot', 'checkout' => 'checkout' ); 9 return isset( $map[ $flow ] ) ? $map[ $flow ] : 'registration'; 10 } 11 12 /** 13 * Sends an OTP email using the stored (sanitized) template for the flow. 14 * Templates are stored as HTML in the database; no template file is ever built from a setting value. 15 * Uses wp_mail(), so SMTP plugins handle delivery. 16 */ 6 17 public static function send_otp_email( $email, $otp, $flow, $name = '' ) { 7 $subject = get_option('naimur-email-otp-for-woocommerce_email_subject', 'Your Verification Code'); 8 $template_id = get_option('naimur-email-otp-for-woocommerce_email_template', '1'); 9 10 $message = ''; 11 if ( $template_id === 'custom' ) { 12 $message = str_replace( '[naimur_email_otp_code]', $otp, get_option('naimur-email-otp-for-woocommerce_custom_email_html', 'Your code is [naimur_email_otp_code]') ); 13 } else { 14 // Include your default template files here 15 ob_start(); 16 include NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH . "templates/email-template-$template_id.php"; 17 $message = ob_get_clean(); 18 $message = str_replace( '[naimur_email_otp_code]', $otp, $message ); 18 $type = self::type_for_flow( $flow ); 19 $out = CEOVW_Templates::render( $type, array( 20 'customer_name' => $name, 21 'customer_email' => $email, 22 'otp_code' => $otp, 23 'expiry_minutes' => (int) ceil( CEOVW_Options::ttl( $flow ) / 60 ), 24 ) ); 25 return wp_mail( $email, $out['subject'], $out['html'], self::headers() ); 26 } 27 28 /** Test email from the admin (sample code, never a real OTP). */ 29 public static function send_test( $type, $to, $override = null ) { 30 $out = CEOVW_Templates::render( $type, CEOVW_Templates::sample_vars( $type ), $override ); 31 $ok = wp_mail( $to, '[' . __( 'Test', 'naimur-email-otp-for-woocommerce' ) . '] ' . $out['subject'], $out['html'], self::headers() ); 32 if ( $ok ) { update_option( 'ceovw_test_email_sent_at', time(), false ); } 33 return $ok; 34 } 35 36 /** Headers for this plugin's emails only (no global From filter, so other plugins' emails are unaffected). */ 37 public static function headers() { 38 $h = array( 'Content-Type: text/html; charset=UTF-8' ); 39 $from = CEOVW_Options::get( 'sender_email' ); 40 if ( $from ) { 41 $name = str_replace( array( "\r", "\n", '"', '<', '>' ), '', (string) CEOVW_Options::get( 'sender_name' ) ); 42 $h[] = 'From: ' . ( '' !== $name ? '"' . $name . '" ' : '' ) . '<' . $from . '>'; 19 43 } 20 21 return wp_mail( $email, $subject, $message, ['Content-Type: text/html; charset=UTF-8'] ); 44 return $h; 22 45 } 23 46 } -
naimur-email-otp-for-woocommerce/tags/2.1.2/includes/class-logs.php
r3780003 r3780739 7 7 const EMAIL_T_TTL = 7; // days 8 8 9 public static function init(){ add_action('admin_menu', [__CLASS__, 'menu']); } 10 11 public static function menu(){ 12 add_submenu_page('woocommerce', esc_html__('Email/Activity Logs', 'naimur-email-otp-for-woocommerce'), esc_html__('Email Verification Logs', 'naimur-email-otp-for-woocommerce'), 'manage_woocommerce', 'naimur-email-otp-for-woocommerce-logs', [__CLASS__, 'render']); 13 } 14 9 // Logs are shown on the "Activity & Diagnostics" admin page (includes/admin/views/diagnostics.php). 10 15 11 public static function get_email_logs(){ $logs = get_transient(self::EMAIL_T_KEY); return is_array($logs)?$logs:[]; } 16 12 … … 41 37 update_option(self::ACTIVITY_KEY, $logs, false); 42 38 } 43 44 public static function render(){45 if (isset($_POST['ceovw_clear_logs']) && check_admin_referer('ceovw_logs')){46 if ($_POST['ceovw_clear_logs'] === 'email') { delete_transient(self::EMAIL_T_KEY); }47 if ($_POST['ceovw_clear_logs'] === 'activity') { update_option(self::ACTIVITY_KEY, [] ); }48 echo '<div class="updated"><p>'.esc_html__('Logs cleared.', 'naimur-email-otp-for-woocommerce').'</p></div>';49 }50 51 $email_logs = self::get_email_logs();52 $activity = self::get_activity_logs();53 ?>54 55 <div class="wrap">56 <h1><?php esc_html_e('Email & Activity Logs', 'naimur-email-otp-for-woocommerce'); ?></h1>57 58 <h2><?php esc_html_e('Last Sent OTP Emails', 'naimur-email-otp-for-woocommerce'); ?></h2>59 <form method="post">60 <?php wp_nonce_field('ceovw_logs'); ?>61 <input type="hidden" name="ceovw_clear_logs" value="email" />62 <button class="button"><?php esc_html_e('Clear Email Logs','naimur-email-otp-for-woocommerce'); ?></button>63 </form>64 <table class="widefat striped">65 <thead>66 <tr>67 <th><?php esc_html_e('Time','naimur-email-otp-for-woocommerce'); ?></th>68 <th><?php esc_html_e('Email','naimur-email-otp-for-woocommerce'); ?></th>69 <th><?php esc_html_e('Code','naimur-email-otp-for-woocommerce'); ?></th>70 <th><?php esc_html_e('Status','naimur-email-otp-for-woocommerce'); ?></th>71 <th><?php esc_html_e('Message','naimur-email-otp-for-woocommerce'); ?></th>72 </tr>73 </thead>74 <tbody>75 <?php if (empty($email_logs)): ?>76 <tr><td colspan="5"><?php esc_html_e('No logs yet.','naimur-email-otp-for-woocommerce'); ?></td></tr>77 <?php else: foreach (array_reverse($email_logs) as $row): ?>78 <tr>79 <td><?php echo esc_html( date_i18n( get_option('date_format').' '.get_option('time_format'), $row['time'] ?? time() ) ); ?></td>80 <td><?php echo esc_html(ceovw_mask_email($row['email'] ?? '')); ?></td>81 <td><code>••••••</code></td>82 <td><?php echo esc_html($row['status'] ?? ''); ?></td>83 <td><?php echo esc_html($row['message'] ?? ''); ?></td>84 </tr>85 <?php endforeach; endif; ?>86 </tbody>87 </table>88 89 <h2 style="margin-top:28px;"><?php esc_html_e('Activity (Who verified when)','naimur-email-otp-for-woocommerce'); ?></h2>90 <form method="post">91 <?php wp_nonce_field('ceovw_logs'); ?>92 <input type="hidden" name="ceovw_clear_logs" value="activity" />93 <button class="button"><?php esc_html_e('Clear Activity Logs','naimur-email-otp-for-woocommerce'); ?></button>94 </form>95 <table class="widefat striped">96 <thead>97 <tr>98 <th><?php esc_html_e('Time','naimur-email-otp-for-woocommerce'); ?></th>99 <th><?php esc_html_e('User ID','naimur-email-otp-for-woocommerce'); ?></th>100 <th><?php esc_html_e('Email','naimur-email-otp-for-woocommerce'); ?></th>101 <th><?php esc_html_e('Device','naimur-email-otp-for-woocommerce'); ?></th>102 <th><?php esc_html_e('IP','naimur-email-otp-for-woocommerce'); ?></th>103 </tr>104 </thead>105 <tbody>106 <?php if (empty($activity)): ?>107 <tr><td colspan="5"><?php esc_html_e('No activity yet.','naimur-email-otp-for-woocommerce'); ?></td></tr>108 <?php else: foreach (array_reverse($activity) as $row): ?>109 <tr>110 <td><?php echo esc_html( date_i18n( get_option('date_format').' '.get_option('time_format'), $row['time'] ?? time() ) ); ?></td>111 <td><?php echo esc_html($row['user_id'] ?? ''); ?></td>112 <td><?php echo esc_html(ceovw_mask_email($row['email'] ?? '')); ?></td>113 <td><strong><?php echo esc_html($row['device'] ?? '-'); ?></strong></td>114 <td><?php echo esc_html($row['ip'] ?? ''); ?></td>115 </tr>116 <?php endforeach; endif; ?>117 </tbody>118 </table>119 </div>120 <?php121 }122 39 } 123 CEOVW_Logs::init(); -
naimur-email-otp-for-woocommerce/tags/2.1.2/includes/class-security.php
r3780003 r3780739 4 4 class CEOVW_Security { 5 5 6 const MAX_ATTEMPTS = 5; 6 const MAX_ATTEMPTS = 5; // default; per-flow value comes from CEOVW_Options (range 3-10) 7 7 8 8 private static function table() { … … 18 18 * @return string|false The plain OTP (for the email only), or false on failure. 19 19 */ 20 public static function generate_and_save_otp( $email, $flow, $password = '', $name = '', $temp_data = null ) {20 public static function generate_and_save_otp( $email, $flow, $password = '', $name = '', $temp_data = null, $context_hash = null ) { 21 21 global $wpdb; 22 22 $table = self::table(); … … 32 32 } 33 33 34 $wpdb->delete( $table, ['email' => $email] ); 34 // Replace only this email's pending row for the same flow (and, for checkout, the same session), 35 // so a new code for one flow can never cancel another flow's authorization. 36 if ( null !== $context_hash ) { 37 $wpdb->delete( $table, ['email' => $email, 'flow' => $flow, 'context_hash' => $context_hash] ); 38 } else { 39 $wpdb->delete( $table, ['email' => $email, 'flow' => $flow] ); 40 } 35 41 36 42 $inserted = $wpdb->insert( $table, [ … … 40 46 'payload' => $payload, 41 47 'attempts' => 0, 42 'token_hash' => null, 43 'expires_at' => time() + ceovw_otp_ttl(), 44 'created_at' => current_time( 'mysql' ) 48 'token_hash' => null, 49 'expires_at' => time() + ceovw_otp_ttl( $flow ), 50 'sent_at' => time(), 51 'context_hash' => $context_hash, 52 'created_at' => current_time( 'mysql' ) 45 53 ]); 46 54 … … 56 64 * @return object|WP_Error The consumed row (with payload) on success. 57 65 */ 58 public static function verify_otp_code( $email, $code, $flow ) {66 public static function verify_otp_code( $email, $code, $flow, $context_hash = null ) { 59 67 global $wpdb; 60 68 $table = self::table(); 61 69 $now = time(); 62 63 if ( ! in_array( $flow, ['register', 'forgot'], true ) || ! preg_match( '/^\d{6}$/', $code ) ) { 70 $max = class_exists( 'CEOVW_Options' ) ? CEOVW_Options::max_attempts( $flow ) : self::MAX_ATTEMPTS; 71 72 if ( ! in_array( $flow, ['register', 'forgot', 'checkout'], true ) || ! is_string( $code ) || ! preg_match( '/^\d{6}$/', $code ) ) { 64 73 return new WP_Error( 'invalid', 'Invalid code. Please try again.' ); 65 74 } 66 67 $row = $wpdb->get_row( $wpdb->prepare( 68 "SELECT * FROM $table WHERE email = %s AND flow = %s AND token_hash IS NULL AND otp <> '' ORDER BY id DESC LIMIT 1", 69 $email, $flow 70 ) ); 75 // Checkout codes are bound to the WooCommerce session that requested them. 76 if ( 'checkout' === $flow && ( ! is_string( $context_hash ) || '' === $context_hash ) ) { 77 return new WP_Error( 'invalid', 'Invalid code. Please try again.' ); 78 } 79 80 $row = 'checkout' === $flow 81 ? $wpdb->get_row( $wpdb->prepare( 82 "SELECT * FROM $table WHERE email = %s AND flow = %s AND context_hash = %s AND token_hash IS NULL AND otp <> '' ORDER BY id DESC LIMIT 1", 83 $email, $flow, $context_hash ) ) 84 : $wpdb->get_row( $wpdb->prepare( 85 "SELECT * FROM $table WHERE email = %s AND flow = %s AND token_hash IS NULL AND otp <> '' ORDER BY id DESC LIMIT 1", 86 $email, $flow ) ); 71 87 72 88 if ( ! $row ) { … … 82 98 $reserved = $wpdb->query( $wpdb->prepare( 83 99 "UPDATE $table SET attempts = attempts + 1 WHERE id = %d AND token_hash IS NULL AND attempts < %d AND expires_at > %d", 84 $row->id, self::MAX_ATTEMPTS, $now100 $row->id, $max, $now 85 101 ) ); 86 102 … … 88 104 // so a parallel request that already issued a reset authorization is never undone. 89 105 if ( ! $reserved ) { 90 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, self::MAX_ATTEMPTS) );106 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, $max ) ); 91 107 return new WP_Error( 'locked', 'Too many incorrect attempts. Please request a new code.' ); 92 108 } … … 96 112 if ( ! hash_equals( (string) $row->otp, $expected ) ) { 97 113 // Invalidate the OTP once the attempt limit is reached. 98 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, self::MAX_ATTEMPTS) );114 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, $max ) ); 99 115 return new WP_Error( 'invalid', 'Invalid code. Please try again.' ); 100 116 } … … 162 178 } 163 179 180 /** 181 * Seconds until another code may be emailed for this email/flow (server-side resend cooldown). 0 = allowed now. 182 * Only pending codes count; an issued authorization (token_hash set) does not block a new request. 183 */ 184 public static function resend_wait( $email, $flow, $context_hash = null ) { 185 global $wpdb; 186 $table = self::table(); 187 $sent = null !== $context_hash 188 ? (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX(sent_at) FROM $table WHERE email = %s AND flow = %s AND context_hash = %s AND token_hash IS NULL", $email, $flow, $context_hash ) ) 189 : (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX(sent_at) FROM $table WHERE email = %s AND flow = %s AND token_hash IS NULL", $email, $flow ) ); 190 $cool = class_exists( 'CEOVW_Options' ) ? CEOVW_Options::resend_seconds( $flow ) : 60; 191 return $sent ? max( 0, $sent + $cool - time() ) : 0; 192 } 193 194 /** 195 * Checkout: converts a verified checkout OTP into a single-use, short-lived authorization bound to the 196 * same email and WooCommerce session. Separate from password reset (flow = 'checkout'). 197 * Returns the raw token (kept only in the server-side WooCommerce session, never sent to the browser). 198 */ 199 public static function issue_checkout_auth( $row, $context_hash ) { 200 global $wpdb; 201 $table = self::table(); 202 $token = bin2hex( random_bytes( 32 ) ); 203 $ttl = max( 60, (int) apply_filters( 'ceovw_checkout_auth_ttl', 30 * MINUTE_IN_SECONDS ) ); 204 $updated = $wpdb->query( $wpdb->prepare( 205 "UPDATE $table SET otp = '', attempts = 0, token_hash = %s, expires_at = %d 206 WHERE id = %d AND flow = 'checkout' AND context_hash = %s AND otp = %s AND token_hash IS NULL AND expires_at > %d", 207 hash( 'sha256', $token ), time() + $ttl, $row->id, $context_hash, $row->otp, time() 208 ) ); 209 return $updated ? $token : false; 210 } 211 212 /** Non-consuming check: valid checkout authorization for this exact email + session. */ 213 public static function find_checkout_auth( $email, $context_hash, $token ) { 214 global $wpdb; 215 if ( ! is_string( $token ) || ! preg_match( '/^[a-f0-9]{64}$/', $token ) || ! $context_hash ) { return null; } 216 $table = self::table(); 217 return $wpdb->get_row( $wpdb->prepare( 218 "SELECT id, email, token_hash FROM $table WHERE token_hash = %s AND flow = 'checkout' AND email = %s AND context_hash = %s AND expires_at > %d LIMIT 1", 219 hash( 'sha256', $token ), $email, $context_hash, time() 220 ) ); 221 } 222 223 /** Consumes a checkout authorization exactly once (atomic DELETE). */ 224 public static function consume_checkout_auth( $email, $context_hash, $token ) { 225 global $wpdb; 226 if ( ! is_string( $token ) || ! preg_match( '/^[a-f0-9]{64}$/', $token ) || ! $context_hash ) { return false; } 227 $table = self::table(); 228 return (bool) $wpdb->query( $wpdb->prepare( 229 "DELETE FROM $table WHERE token_hash = %s AND flow = 'checkout' AND email = %s AND context_hash = %s AND expires_at > %d", 230 hash( 'sha256', $token ), $email, $context_hash, time() 231 ) ); 232 } 233 164 234 public static function can_request_otp( $email ) { 165 235 global $wpdb; … … 168 238 169 239 $count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $table WHERE email = %s AND action_type = 'otp_request' AND created_at > %s", $email, $twenty_four_hours_ago) ); 170 return $count < 5; 240 $limit = class_exists( 'CEOVW_Options' ) ? (int) CEOVW_Options::get( 'rl_email_per_day' ) : 5; 241 return $count < $limit; 171 242 } 172 243 … … 193 264 $table = $wpdb->prefix . 'ceovw_logs'; 194 265 266 $o = class_exists( 'CEOVW_Options' ); 195 267 $defaults = [ 196 'send' => ['limit' => 10, 'window' => HOUR_IN_SECONDS],197 'verify' => ['limit' => 30, 'window' => 15 * MINUTE_IN_SECONDS],198 'reset' => ['limit' => 10, 'window' => 15 * MINUTE_IN_SECONDS],268 'send' => ['limit' => $o ? (int) CEOVW_Options::get( 'rl_send_per_hour' ) : 10, 'window' => HOUR_IN_SECONDS], 269 'verify' => ['limit' => $o ? (int) CEOVW_Options::get( 'rl_verify_per_15min' ) : 30, 'window' => 15 * MINUTE_IN_SECONDS], 270 'reset' => ['limit' => $o ? (int) CEOVW_Options::get( 'rl_reset_per_15min' ) : 10, 'window' => 15 * MINUTE_IN_SECONDS], 199 271 ]; 200 272 if ( ! isset( $defaults[ $action ] ) ) { return false; } … … 242 314 } 243 315 316 /** Removes this email's rows for one flow only. */ 317 public static function clear_flow( $email, $flow ) { 318 global $wpdb; 319 $wpdb->delete( self::table(), ['email' => $email, 'flow' => $flow] ); 320 } 321 244 322 public static function clear_otp( $email ) { 245 323 global $wpdb; -
naimur-email-otp-for-woocommerce/tags/2.1.2/includes/class-settings.php
r3780003 r3780739 2 2 if (!defined('ABSPATH')) { exit; } 3 3 4 /** 5 * Settings now live in CEOVW_Options and the "Naimur OTP" admin pages (includes/admin). 6 * This class only keeps the legacy [naimur_email_otp_code] shortcode registered so content 7 * written for versions before 2.1.2 does not show a raw shortcode. 8 */ 4 9 class CEOVW_Settings { 5 10 public static function init(){ 6 add_action('admin_menu', [__CLASS__, 'menu']);7 add_action('admin_init', [__CLASS__, 'register']);8 11 add_shortcode('naimur_email_otp_code', [__CLASS__, 'shortcode_code']); 9 add_action('wp_ajax_ceovw_preview_template', [__CLASS__, 'preview_template']);10 }11 12 public static function menu(){13 // Main Settings Page14 add_submenu_page('woocommerce', esc_html__('Email Verification', 'naimur-email-otp-for-woocommerce'), esc_html__('Email Verification', 'naimur-email-otp-for-woocommerce'), 'manage_woocommerce', 'naimur-email-otp-for-woocommerce-settings', [__CLASS__, 'render']);15 16 // Help, Docs & Support Page17 add_submenu_page('woocommerce', esc_html__('OTP Help & Docs', 'naimur-email-otp-for-woocommerce'), esc_html__('OTP Help & Docs', 'naimur-email-otp-for-woocommerce'), 'manage_woocommerce', 'naimur-email-otp-for-woocommerce-help', [__CLASS__, 'render_help_page']);18 }19 20 public static function register(){21 $sanitize_text = 'sanitize_text_field';22 23 // Email Template Settings24 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_email_subject', ['sanitize_callback' => $sanitize_text]);25 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_email_template', ['sanitize_callback' => $sanitize_text]);26 27 // Custom Email HTML with capability check (allows <style>, <html> tags for admins)28 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_custom_email_html', [29 'sanitize_callback' => function($html) {30 return current_user_can('unfiltered_html') ? $html : wp_kses_post($html);31 }32 ]);33 34 // Design Settings35 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_brand_color', ['sanitize_callback' => 'sanitize_hex_color']);36 37 // OTP Logic Settings38 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_code_length', ['sanitize_callback' => 'absint']);39 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_resend_cooldown', ['sanitize_callback' => 'absint']);40 41 // Override Settings42 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_override_enabled', ['sanitize_callback' => 'rest_sanitize_boolean']);43 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_myaccount_page_id', ['sanitize_callback' => 'absint']);44 45 // Captcha Settings46 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_captcha_type', ['sanitize_callback' => $sanitize_text]);47 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_recaptcha_site_key', ['sanitize_callback' => $sanitize_text]);48 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_recaptcha_secret_key', ['sanitize_callback' => $sanitize_text]);49 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_turnstile_site_key', ['sanitize_callback' => $sanitize_text]);50 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_turnstile_secret_key', ['sanitize_callback' => $sanitize_text]);51 52 // Form Header Settings53 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_login_header_html', ['sanitize_callback' => 'wp_kses_post']);54 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_register_header_html', ['sanitize_callback' => 'wp_kses_post']);55 }56 57 public static function render(){58 if ( ! current_user_can( 'manage_woocommerce' ) ) { return; }59 ?>60 <div class="wrap">61 <h1><?php /* translators: %s: plugin version */ echo esc_html( sprintf( __('Customer Email OTP Verification for WooCommerce (v%s)', 'naimur-email-otp-for-woocommerce'), NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION ) ); ?></h1>62 <form method="post" action="options.php">63 <?php settings_fields('naimur-email-otp-for-woocommerce'); ?>64 <table class="form-table" role="presentation">65 <!-- Email Template Section -->66 <tr>67 <th><label for="naimur-email-otp-for-woocommerce_email_subject"><?php esc_html_e('Email Subject', 'naimur-email-otp-for-woocommerce'); ?></label></th>68 <td>69 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_email_subject" id="naimur-email-otp-for-woocommerce_email_subject" value="<?php echo esc_attr(get_option('naimur-email-otp-for-woocommerce_email_subject', 'Your Verification Code')); ?>" />70 </td>71 </tr>72 <tr>73 <th><label for="naimur-email-otp-for-woocommerce_email_template"><?php esc_html_e('Select Email Template', 'naimur-email-otp-for-woocommerce'); ?></label></th>74 <td>75 <select name="naimur-email-otp-for-woocommerce_email_template" id="naimur-email-otp-for-woocommerce_email_template">76 <option value="1" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template', '1'), '1'); ?>><?php esc_html_e('Template 1 (Simple)', 'naimur-email-otp-for-woocommerce'); ?></option>77 <option value="2" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template'), '2'); ?>><?php esc_html_e('Template 2 (Modern)', 'naimur-email-otp-for-woocommerce'); ?></option>78 <option value="3" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template'), '3'); ?>><?php esc_html_e('Template 3 (Classic)', 'naimur-email-otp-for-woocommerce'); ?></option>79 <option value="custom" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template'), 'custom'); ?>><?php esc_html_e('Custom HTML', 'naimur-email-otp-for-woocommerce'); ?></option>80 </select>81 </td>82 </tr>83 84 <!-- Live Gutenberg-like HTML Editor -->85 <tr id="ceovw_custom_html_row" style="display: none;">86 <th><label for="naimur-email-otp-for-woocommerce_custom_email_html"><?php esc_html_e('Write Custom HTML/CSS', 'naimur-email-otp-for-woocommerce'); ?></label></th>87 <td>88 <textarea name="naimur-email-otp-for-woocommerce_custom_email_html" id="naimur-email-otp-for-woocommerce_custom_email_html" rows="12" class="large-text code" placeholder="<html><body><h1>Your Code: [naimur_email_otp_code]</h1></body></html>"><?php echo esc_textarea(get_option('naimur-email-otp-for-woocommerce_custom_email_html', '')); ?></textarea>89 <p class="description"><?php esc_html_e('Use the shortcode [naimur_email_otp_code] where you want the 6-digit OTP to appear. Changes will preview live below.', 'naimur-email-otp-for-woocommerce'); ?></p>90 </td>91 </tr>92 93 <tr>94 <th><?php esc_html_e('Live Visual Preview', 'naimur-email-otp-for-woocommerce'); ?></th>95 <td>96 <div id="ceovw_template_preview_container" style="border: 1px solid #dcdcdc; border-radius: 8px; padding: 15px; background: #f9f9f9; max-width: 600px; min-height: 200px; box-shadow: inset 0 0 10px rgba(0,0,0,0.02);">97 <!-- Template Preview will be loaded here dynamically -->98 </div>99 </td>100 </tr>101 102 <!-- Design Section -->103 <tr>104 <th><label for="naimur-email-otp-for-woocommerce_brand_color"><?php esc_html_e('Brand color (HEX)', 'naimur-email-otp-for-woocommerce'); ?></label></th>105 <td><input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_brand_color" id="naimur-email-otp-for-woocommerce_brand_color" value="<?php echo esc_attr(get_option('naimur-email-otp-for-woocommerce_brand_color', '#3b82f6')); ?>" placeholder="#3b82f6" /></td>106 </tr>107 108 <!-- Override UI Section -->109 <tr>110 <th><?php esc_html_e('Modern Login/Register UI', 'naimur-email-otp-for-woocommerce'); ?></th>111 <td>112 <label><input type="checkbox" name="naimur-email-otp-for-woocommerce_override_enabled" value="1" <?php checked(get_option('naimur-email-otp-for-woocommerce_override_enabled', 0), 1); ?> /> <?php esc_html_e('Enable custom modern design', 'naimur-email-otp-for-woocommerce'); ?></label><br/>113 <div style="margin-top: 10px;">114 <label for="naimur-email-otp-for-woocommerce_myaccount_page_id" style="display:block; margin-bottom:5px; font-weight:500;"><?php esc_html_e('Select My Account page', 'naimur-email-otp-for-woocommerce'); ?></label>115 <?php wp_dropdown_pages(['name'=>'naimur-email-otp-for-woocommerce_myaccount_page_id', 'show_option_none'=>'— Select —', 'option_none_value'=>'0', 'selected'=>(int)get_option('naimur-email-otp-for-woocommerce_myaccount_page_id', 0)]); ?>116 </div>117 </td>118 </tr>119 120 <!-- Captcha Section -->121 <tr>122 <th><label for="naimur-email-otp-for-woocommerce_captcha_type"><?php esc_html_e('Captcha Provider', 'naimur-email-otp-for-woocommerce'); ?></label></th>123 <td>124 <select name="naimur-email-otp-for-woocommerce_captcha_type" id="naimur-email-otp-for-woocommerce_captcha_type">125 <option value="none" <?php selected(get_option('naimur-email-otp-for-woocommerce_captcha_type'), 'none'); ?>><?php esc_html_e('None', 'naimur-email-otp-for-woocommerce'); ?></option>126 <option value="recaptcha" <?php selected(get_option('naimur-email-otp-for-woocommerce_captcha_type'), 'recaptcha'); ?>><?php esc_html_e('Google reCAPTCHA v2', 'naimur-email-otp-for-woocommerce'); ?></option>127 <option value="turnstile" <?php selected(get_option('naimur-email-otp-for-woocommerce_captcha_type'), 'turnstile'); ?>><?php esc_html_e('Cloudflare Turnstile', 'naimur-email-otp-for-woocommerce'); ?></option>128 </select>129 </td>130 </tr>131 <tr>132 <th><?php esc_html_e('Google reCAPTCHA Keys', 'naimur-email-otp-for-woocommerce'); ?></th>133 <td>134 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_recaptcha_site_key" placeholder="Site Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key','') ); ?>" style="margin-bottom: 5px;" /><br/>135 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_recaptcha_secret_key" placeholder="Secret Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_recaptcha_secret_key','') ); ?>" />136 </td>137 </tr>138 <tr>139 <th><?php esc_html_e('Cloudflare Turnstile Keys', 'naimur-email-otp-for-woocommerce'); ?></th>140 <td>141 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_turnstile_site_key" placeholder="Site Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_turnstile_site_key','') ); ?>" style="margin-bottom: 5px;" /><br/>142 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_turnstile_secret_key" placeholder="Secret Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_turnstile_secret_key','') ); ?>" />143 </td>144 </tr>145 146 <!-- Form Texts -->147 <tr>148 <th><label for="naimur-email-otp-for-woocommerce_login_header_html"><?php esc_html_e( 'Login header HTML', 'naimur-email-otp-for-woocommerce' ); ?></label></th>149 <td><?php wp_editor( get_option( 'naimur-email-otp-for-woocommerce_login_header_html', '<p class="ceovw-muted">Please enter your details</p><h1>Welcome back</h1>' ), 'naimur-email-otp-for-woocommerce_login_header_html', [ 'textarea_rows' => 5 ] ); ?></td>150 </tr>151 <tr>152 <th><label for="naimur-email-otp-for-woocommerce_register_header_html"><?php esc_html_e( 'Register header HTML', 'naimur-email-otp-for-woocommerce' ); ?></label></th>153 <td><?php wp_editor( get_option( 'naimur-email-otp-for-woocommerce_register_header_html', '<h1>Create your account</h1>' ), 'naimur-email-otp-for-woocommerce_register_header_html', [ 'textarea_rows' => 5 ] ); ?></td>154 </tr>155 </table>156 <?php submit_button(); ?>157 </form>158 </div>159 160 <script>161 jQuery(document).ready(function($) {162 var templateSelect = $('#naimur-email-otp-for-woocommerce_email_template');163 var customRow = $('#ceovw_custom_html_row');164 var customTextarea = $('#naimur-email-otp-for-woocommerce_custom_email_html');165 var previewContainer = $('#ceovw_template_preview_container');166 167 function renderIframe(htmlContent) {168 var encodedStr = htmlContent.replace(/"/g, '"').replace(/'/g, ''');169 previewContainer.html('<iframe srcdoc="' + encodedStr + '" style="width:100%; height:450px; border:1px solid #eaeaea; background:#ffffff; border-radius: 4px;"></iframe>');170 }171 172 function updatePreview() {173 var selected_template = templateSelect.val();174 175 if (selected_template === 'custom') {176 // Show Custom HTML Textarea177 customRow.show();178 // Live Gutenberg-style rendering179 var rawHtml = customTextarea.val();180 var liveHtml = rawHtml.replace(/\[naimur_email_otp_code\]/g, '<strong style="color:var(--ceovw-brand, #3b82f6); font-size:24px;">123456</strong>');181 renderIframe(liveHtml);182 } else {183 // Hide Custom HTML Textarea & Load Pre-defined Template via AJAX184 customRow.hide();185 previewContainer.html('<p style="color:#666; font-style:italic;">Loading template preview...</p>');186 187 $.post(ajaxurl, {188 action: 'ceovw_preview_template',189 template_id: selected_template,190 _ajax_nonce: '<?php echo wp_create_nonce('ceovw_preview_nonce'); ?>'191 }, function(response) {192 renderIframe(response);193 });194 }195 }196 197 // Initialize198 updatePreview();199 200 // Trigger on Dropdown Change201 templateSelect.on('change', updatePreview);202 203 // Trigger Live Preview on Typing (The Gutenberg effect!)204 customTextarea.on('input', updatePreview);205 });206 </script>207 <?php }208 209 public static function render_help_page(){210 if ( ! current_user_can( 'manage_woocommerce' ) ) { return; }211 ?>212 <div class="wrap">213 <h1><?php esc_html_e('Help, Documentation & Support', 'naimur-email-otp-for-woocommerce'); ?></h1>214 <p><?php esc_html_e('Thank you for using Naimur Email OTP Verification for WooCommerce. Below you will find useful information to manage and troubleshoot the plugin.', 'naimur-email-otp-for-woocommerce'); ?></p>215 216 <div class="card" style="max-width: 800px; margin-top: 20px; padding: 20px;">217 <h2 style="margin-top: 0;"><?php esc_html_e('How to Enable the Modern Login/Register UI?', 'naimur-email-otp-for-woocommerce'); ?></h2>218 <p><?php esc_html_e('You do not need to use any shortcodes manually. The plugin comes with an auto-execution system.', 'naimur-email-otp-for-woocommerce'); ?></p>219 <ol style="font-size: 14px; line-height: 1.6;">220 <li><?php esc_html_e('Go to the plugin Settings tab.', 'naimur-email-otp-for-woocommerce'); ?></li>221 <li><?php esc_html_e('Check the box "Enable custom modern design".', 'naimur-email-otp-for-woocommerce'); ?></li>222 <li><?php esc_html_e('From the dropdown, select your WooCommerce "My Account" page.', 'naimur-email-otp-for-woocommerce'); ?></li>223 <li><?php esc_html_e('Save changes.', 'naimur-email-otp-for-woocommerce'); ?></li>224 </ol>225 <p style="color: #646970;"><em><?php esc_html_e('That is it! The plugin will automatically override the default boring WooCommerce form with our modern, responsive, and secure UI design.', 'naimur-email-otp-for-woocommerce'); ?></em></p>226 </div>227 228 <div class="card" style="max-width: 800px; margin-top: 20px; padding: 20px;">229 <h2 style="margin-top: 0;"><?php esc_html_e('Need Support or Found a Bug?', 'naimur-email-otp-for-woocommerce'); ?></h2>230 <p><?php esc_html_e('If you are facing any issues, conflicts, or have questions about how to set up the plugin, please visit our official support forum on WordPress.org. Explain your issue in detail, and we will try to resolve it as soon as possible.', 'naimur-email-otp-for-woocommerce'); ?></p>231 <a href="https://wordpress.org/support/plugin/naimur-email-otp-for-woocommerce/" target="_blank" class="button button-primary"><?php esc_html_e('Get Support on WordPress.org', 'naimur-email-otp-for-woocommerce'); ?></a>232 </div>233 234 <div class="card" style="max-width: 800px; margin-top: 20px; padding: 20px;">235 <h2 style="margin-top: 0;"><?php esc_html_e('Author Personal Consultation', 'naimur-email-otp-for-woocommerce'); ?></h2>236 <p><?php esc_html_e('Need a custom feature, specialized WordPress & WooCommerce development, or one-on-one consultation? You can reach out to the author (Naimur Rahman Nahid) directly through the personal contact form.', 'naimur-email-otp-for-woocommerce'); ?></p>237 <a href="https://naimurrahmannahid.com/contact/" target="_blank" class="button"><?php esc_html_e('Contact for Consultation', 'naimur-email-otp-for-woocommerce'); ?></a>238 </div>239 </div>240 <?php241 12 } 242 13 243 14 public static function shortcode_code($atts, $content = ''){ return $content; } 244 245 // AJAX Handler for Pre-defined Template Preview246 public static function preview_template(){247 check_ajax_referer('ceovw_preview_nonce');248 if (!current_user_can('manage_woocommerce')) wp_die('-');249 250 // Only the bundled templates can be previewed. The ID must exactly match the allowlist251 // before it is used in a file path; anything else (missing, arrays, path characters) is rejected.252 $allowed_templates = ['1', '2', '3'];253 $template = ( isset($_POST['template_id']) && is_string($_POST['template_id']) ) ? wp_unslash($_POST['template_id']) : '';254 if ( ! in_array( $template, $allowed_templates, true ) ) {255 wp_die( esc_html__( 'Invalid template.', 'naimur-email-otp-for-woocommerce' ), '', array( 'response' => 400 ) );256 }257 258 $code = '<strong style="color:#3b82f6; font-size:24px;">123456</strong>';259 $file = NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH . 'templates/email-template-' . $template . '.php';260 if (file_exists($file)) {261 // Read file and replace shortcode for preview262 $content = file_get_contents($file);263 echo str_replace('[naimur_email_otp_code]', $code, $content);264 } else {265 echo '<p>Template file not found.</p>';266 }267 wp_die();268 }269 15 } 270 16 CEOVW_Settings::init(); -
naimur-email-otp-for-woocommerce/tags/2.1.2/includes/helpers.php
r3780003 r3780739 23 23 // token_hash: SHA-256 of the single-use password reset token 24 24 // expires_at: unix timestamp (UTC) after which the row is invalid 25 // sent_at: unix timestamp of the last email for this row (server-side resend cooldown, 2.1.2) 26 // context_hash: HMAC of the WooCommerce session a checkout verification is bound to (2.1.2) 25 27 $sql2 = "CREATE TABLE {$wpdb->prefix}ceovw_otps ( 26 28 id mediumint(9) NOT NULL AUTO_INCREMENT, … … 32 34 token_hash char(64) DEFAULT NULL, 33 35 expires_at bigint(20) unsigned NOT NULL DEFAULT 0, 36 sent_at bigint(20) unsigned NOT NULL DEFAULT 0, 37 context_hash char(64) DEFAULT NULL, 34 38 created_at datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, 35 39 PRIMARY KEY (id), 36 40 KEY email (email), 37 41 KEY token_hash (token_hash), 38 KEY expires_at (expires_at) 42 KEY expires_at (expires_at), 43 KEY context_hash (context_hash) 39 44 ) $charset_collate;"; 40 45 … … 45 50 46 51 /** 47 * Confirms the OTP table has the columns required by the 2.1.1authorization logic.52 * Confirms the OTP table has the columns required by the current authorization logic. 48 53 */ 49 54 function ceovw_schema_is_valid() { … … 54 59 if ( empty( $columns ) ) { return false; } 55 60 56 foreach ( ['attempts', 'token_hash', 'expires_at', 'otp' ] as $column ) {61 foreach ( ['attempts', 'token_hash', 'expires_at', 'otp', 'sent_at', 'context_hash'] as $column ) { 57 62 if ( ! isset( $columns[ $column ] ) ) { return false; } 58 63 } … … 84 89 85 90 /** 86 * 2.1.0 -> 2.1.1 migration. Saves the new DB version only when every required step succeeds.91 * Database/settings migration (2.1.0 / 2.1.1 -> current). Saves the new DB version only when every required step succeeds. 87 92 */ 88 93 function ceovw_run_upgrade() { … … 122 127 // 7. Ensure the cleanup event exists. 123 128 ceovw_schedule_cleanup(); 129 130 // 7b. 2.1.2: carry the pre-2.1.2 email template settings (custom HTML / subject) into the new template store. 131 // Deferred to init when needed: the default templates are translated, and translations must not load earlier. 132 if ( class_exists( 'CEOVW_Templates' ) ) { 133 if ( did_action( 'init' ) ) { CEOVW_Templates::migrate_legacy(); } 134 else { add_action( 'init', array( 'CEOVW_Templates', 'migrate_legacy' ), 1 ); } 135 } 124 136 125 137 // 8. Mark the migration as complete. … … 230 242 $cutoff = wp_date( 'Y-m-d H:i:s', time() - 2 * DAY_IN_SECONDS ); 231 243 $wpdb->query( $wpdb->prepare( "DELETE FROM $logs WHERE created_at < %s", $cutoff ) ); 244 245 // Activity log retention (General settings). 246 if ( class_exists( 'CEOVW_Options' ) ) { 247 $keep_after = time() - DAY_IN_SECONDS * (int) CEOVW_Options::get( 'log_retention_days' ); 248 $activity = get_option( 'ceovw_activity_logs', array() ); 249 if ( is_array( $activity ) && $activity ) { 250 $kept = array_values( array_filter( $activity, function ( $e ) use ( $keep_after ) { return is_array( $e ) && (int) ( $e['time'] ?? 0 ) >= $keep_after; } ) ); 251 if ( count( $kept ) !== count( $activity ) ) { update_option( 'ceovw_activity_logs', $kept, false ); } 252 } 253 } 254 update_option( 'ceovw_last_cleanup', time(), false ); 232 255 } 233 256 … … 235 258 * Seconds an emailed OTP (and its temporary registration data) stays valid. 236 259 */ 237 function ceovw_otp_ttl( ) {238 return max( 60, (int) apply_filters( 'ceovw_otp_expiry', 300) );260 function ceovw_otp_ttl( $flow = 'register' ) { 261 return class_exists( 'CEOVW_Options' ) ? CEOVW_Options::ttl( $flow ) : max( 60, (int) apply_filters( 'ceovw_otp_expiry', 300, $flow ) ); 239 262 } 240 263 -
naimur-email-otp-for-woocommerce/tags/2.1.2/languages/naimur-email-otp-for-woocommerce.pot
r3587442 r3780739 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Naimur Email OTP Verification for WooCommerce 1.2.0\n"5 "Project-Id-Version: Naimur Email OTP Verification for WooCommerce 2.1.2\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/naimur-email-otp-for-woocommerce\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026- 06-22T09:54:30+06:00\n"12 "POT-Creation-Date: 2026-11-03T00:02:16+06:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.10.0\n"14 "X-Generator: GNU xgettext\n" 15 15 "X-Domain: naimur-email-otp-for-woocommerce\n" 16 16 … … 20 20 21 21 #. Description of the plugin 22 msgid "Verify WooCommerce customer emails using a 6-digit OTP before account creation. Modern UI with Google reCAPTCHA & Cloudflare Turnstile protection."22 msgid "Verify WooCommerce customer emails with a 6-digit OTP for registration, password reset and (optionally) checkout, with editable email templates and a modern admin." 23 23 msgstr "" 24 24 … … 27 27 msgstr "" 28 28 29 #: naimur-email-otp-for-woocommerce.php:68 30 #: includes/class-settings.php:13 31 #: includes/class-settings.php:13 32 msgid "Email Verification" 33 msgstr "" 34 35 #: naimur-email-otp-for-woocommerce.php:64 29 #: includes/admin/class-admin.php:27 30 msgid "Overview" 31 msgstr "" 32 33 #: includes/admin/class-admin.php:27 34 msgid "See what is protected, what still needs setting up, and what happened recently." 35 msgstr "" 36 37 #: includes/admin/class-admin.php:28 38 msgid "Choose where customers must confirm their email address with a 6-digit code." 39 msgstr "" 40 41 #: includes/admin/class-admin.php:28 includes/admin/class-admin.php:168 42 #: includes/admin/class-admin.php:169 includes/admin/class-admin.php:170 43 #: includes/admin/class-admin.php:171 includes/admin/class-admin.php:172 44 #: includes/admin/class-admin.php:173 includes/admin/class-admin.php:174 45 #: includes/admin/class-admin.php:175 includes/admin/views/general.php:10 46 #: includes/admin/views/help.php:10 includes/admin/views/security.php:21 47 msgid "Verification" 48 msgstr "" 49 50 #: includes/admin/class-admin.php:29 51 msgid "Change the wording and design of the emails that carry the verification codes." 52 msgstr "" 53 54 #: includes/admin/class-admin.php:29 includes/admin/class-admin.php:176 55 #: includes/admin/views/help.php:65 56 msgid "Email Templates" 57 msgstr "" 58 59 #: includes/admin/class-admin.php:30 60 msgid "Limits that stop abuse, such as too many codes or too many guesses." 61 msgstr "" 62 63 #: includes/admin/class-admin.php:30 includes/admin/class-admin.php:177 64 #: includes/admin/class-admin.php:178 includes/admin/views/help.php:75 65 msgid "Security" 66 msgstr "" 67 68 #: includes/admin/class-admin.php:31 includes/admin/class-admin.php:179 69 #: includes/admin/class-admin.php:180 includes/admin/class-admin.php:181 70 #: includes/admin/views/help.php:21 71 msgid "Activity & Diagnostics" 72 msgstr "" 73 74 #: includes/admin/class-admin.php:31 75 msgid "Send a test email, review recent emails and check that everything is working." 76 msgstr "" 77 78 #: includes/admin/class-admin.php:32 includes/admin/class-admin.php:182 79 #: includes/admin/class-admin.php:183 includes/admin/class-admin.php:184 80 #: includes/admin/class-admin.php:185 includes/admin/views/help.php:19 81 msgid "General Settings" 82 msgstr "" 83 84 #: includes/admin/class-admin.php:32 85 msgid "Sender details, branding and what happens to your data when the plugin is deleted." 86 msgstr "" 87 88 #: includes/admin/class-admin.php:33 89 msgid "Help & Documentation" 90 msgstr "" 91 92 #: includes/admin/class-admin.php:33 93 msgid "Step-by-step guides and answers to common questions." 94 msgstr "" 95 96 #: includes/admin/class-admin.php:66 includes/admin/class-admin.php:215 97 msgid "Naimur OTP" 98 msgstr "" 99 100 #: includes/admin/class-admin.php:123 101 msgid "Settings saved." 102 msgstr "" 103 104 #: includes/admin/class-admin.php:124 105 msgid "All changes saved" 106 msgstr "" 107 108 #: includes/admin/class-admin.php:125 109 msgid "Saving…" 110 msgstr "" 111 112 #: includes/admin/class-admin.php:126 113 msgid "Something went wrong. Please try again." 114 msgstr "" 115 116 #: includes/admin/class-admin.php:127 117 msgid "Saved. Some values were adjusted to the allowed range." 118 msgstr "" 119 120 #: includes/admin/class-admin.php:128 121 msgid "Please correct the highlighted fields." 122 msgstr "" 123 124 #. translators: 1: minimum, 2: maximum 125 #: includes/admin/class-admin.php:130 126 #, php-format 127 msgid "Enter a whole number from %1$d to %2$d." 128 msgstr "" 129 130 #: includes/admin/class-admin.php:131 131 msgid "Enter a valid email address, for example name@yourstore.com." 132 msgstr "" 133 134 #: includes/admin/class-admin.php:132 135 msgid "Enter a full web address starting with https://" 136 msgstr "" 137 138 #: includes/admin/class-admin.php:133 139 msgid "Confirm" 140 msgstr "" 141 142 #: includes/admin/class-admin.php:134 includes/class-checkout.php:256 143 msgid "Cancel" 144 msgstr "" 145 146 #: includes/admin/class-admin.php:135 147 msgid "Placeholder inserted." 148 msgstr "" 149 150 #: includes/admin/class-admin.php:136 151 msgid "Test email sent." 152 msgstr "" 153 154 #: includes/admin/class-admin.php:137 155 msgid "Choose logo" 156 msgstr "" 157 158 #: includes/admin/class-admin.php:138 159 msgid "Use this image" 160 msgstr "" 161 162 #: includes/admin/class-admin.php:139 includes/admin/views/general.php:30 163 msgid "No logo" 164 msgstr "" 165 166 #: includes/admin/class-admin.php:140 includes/admin/views/help.php:156 167 msgid "No help topics match your search." 168 msgstr "" 169 170 #: includes/admin/class-admin.php:141 171 msgid "No matching settings." 172 msgstr "" 173 174 #: includes/admin/class-admin.php:142 175 msgid "Unsaved changes" 176 msgstr "" 177 178 #: includes/admin/class-admin.php:143 179 msgid "You have unsaved changes. Leave this page?" 180 msgstr "" 181 182 #: includes/admin/class-admin.php:144 183 msgid "Changes discarded." 184 msgstr "" 185 186 #: includes/admin/class-admin.php:145 187 msgid "Customized template" 188 msgstr "" 189 190 #: includes/admin/class-admin.php:146 191 msgid "Built-in default template" 192 msgstr "" 193 194 #: includes/admin/class-admin.php:147 includes/admin/views/templates.php:19 195 msgid "Customized" 196 msgstr "" 197 198 #: includes/admin/class-admin.php:148 includes/admin/views/templates.php:19 199 msgid "Default" 200 msgstr "" 201 202 #: includes/admin/class-admin.php:149 203 msgid "You have unsaved changes. Discard them and switch template?" 204 msgstr "" 205 206 #: includes/admin/class-admin.php:150 207 msgid "Template saved." 208 msgstr "" 209 210 #: includes/admin/class-admin.php:151 211 msgid "Template restored to the default." 212 msgstr "" 213 214 #: includes/admin/class-admin.php:152 215 msgid "Just now" 216 msgstr "" 217 218 #: includes/admin/class-admin.php:153 219 msgid "Log cleared." 220 msgstr "" 221 222 #: includes/admin/class-admin.php:154 includes/admin/views/dashboard.php:10 223 #: includes/admin/views/templates.php:13 224 #: includes/admin/views/verification.php:10 225 #: includes/admin/views/verification.php:64 226 msgid "On" 227 msgstr "" 228 229 #: includes/admin/class-admin.php:155 includes/admin/views/dashboard.php:11 230 #: includes/admin/views/templates.php:13 231 #: includes/admin/views/verification.php:11 232 #: includes/admin/views/verification.php:64 233 msgid "Off" 234 msgstr "" 235 236 #: includes/admin/class-admin.php:156 includes/admin/views/verification.php:129 237 msgid "Guests and logged-in customers" 238 msgstr "" 239 240 #: includes/admin/class-admin.php:157 includes/admin/views/verification.php:129 241 msgid "Guest customers only" 242 msgstr "" 243 244 #. translators: %d: number of matching help topics 245 #: includes/admin/class-admin.php:159 246 #, php-format 247 msgid "%d matching topics" 248 msgstr "" 249 250 #: includes/admin/class-admin.php:168 251 msgid "Customer login and registration forms" 252 msgstr "" 253 254 #: includes/admin/class-admin.php:169 includes/admin/class-admin.php:231 255 #: includes/class-templates.php:14 256 msgid "Registration OTP" 257 msgstr "" 258 259 #: includes/admin/class-admin.php:170 includes/admin/class-admin.php:232 260 #: includes/class-templates.php:15 261 msgid "Password Reset OTP" 262 msgstr "" 263 264 #: includes/admin/class-admin.php:171 includes/admin/class-admin.php:233 265 #: includes/class-templates.php:16 266 msgid "Checkout OTP" 267 msgstr "" 268 269 #: includes/admin/class-admin.php:172 includes/admin/views/verification.php:33 270 msgid "Code Expiration Time" 271 msgstr "" 272 273 #: includes/admin/class-admin.php:173 includes/admin/views/verification.php:34 274 msgid "Wait Before Resending" 275 msgstr "" 276 277 #: includes/admin/class-admin.php:174 includes/admin/views/verification.php:35 278 msgid "Wrong Code Attempts Allowed" 279 msgstr "" 280 281 #: includes/admin/class-admin.php:175 includes/admin/views/verification.php:112 282 msgid "Also verify logged-in customers" 283 msgstr "" 284 285 #: includes/admin/class-admin.php:176 286 msgid "Email templates" 287 msgstr "" 288 289 #: includes/admin/class-admin.php:177 includes/admin/views/security.php:7 290 msgid "Rate limits" 291 msgstr "" 292 293 #: includes/admin/class-admin.php:178 294 msgid "Visitor IP address and proxies" 295 msgstr "" 296 297 #: includes/admin/class-admin.php:179 includes/admin/views/dashboard.php:51 298 #: includes/admin/views/diagnostics.php:31 includes/admin/views/help.php:86 299 msgid "Send a test email" 300 msgstr "" 301 302 #: includes/admin/class-admin.php:180 includes/admin/views/diagnostics.php:48 303 msgid "Email log" 304 msgstr "" 305 306 #: includes/admin/class-admin.php:181 includes/admin/views/diagnostics.php:99 307 msgid "Keep activity history for" 308 msgstr "" 309 310 #: includes/admin/class-admin.php:182 311 msgid "Sender name and email" 312 msgstr "" 313 314 #: includes/admin/class-admin.php:183 includes/admin/views/general.php:28 315 msgid "Store logo for emails" 316 msgstr "" 317 318 #: includes/admin/class-admin.php:184 includes/admin/views/general.php:42 319 msgid "Brand colour" 320 msgstr "" 321 322 #: includes/admin/class-admin.php:185 323 msgid "Delete data when the plugin is deleted" 324 msgstr "" 325 326 #: includes/admin/class-admin.php:198 327 msgid "You do not have permission to access this page." 328 msgstr "" 329 330 #: includes/admin/class-admin.php:203 331 msgid "Naimur Email OTP" 332 msgstr "" 333 334 #. translators: %s: plugin version 335 #: includes/admin/class-admin.php:203 336 #, php-format 337 msgid "Version %s" 338 msgstr "" 339 340 #: includes/admin/class-admin.php:204 341 msgid "Find a setting" 342 msgstr "" 343 344 #: includes/admin/class-admin.php:205 345 msgid "Find a setting…" 346 msgstr "" 347 348 #: includes/admin/class-admin.php:205 349 msgid "Type to search, then use the arrow keys to choose a result." 350 msgstr "" 351 352 #: includes/admin/class-admin.php:207 353 msgid "Naimur OTP sections" 354 msgstr "" 355 356 #: includes/admin/class-admin.php:214 357 msgid "Breadcrumb" 358 msgstr "" 359 360 #. translators: 1: minimum, 2: maximum 361 #: includes/admin/class-admin.php:276 362 #, php-format 363 msgid "Allowed range: %1$d to %2$d." 364 msgstr "" 365 366 #: includes/admin/class-admin.php:300 367 msgid "Learn more" 368 msgstr "" 369 370 #: includes/admin/class-admin.php:305 includes/admin/views/templates.php:67 371 msgid "Discard changes" 372 msgstr "" 373 374 #: includes/admin/class-admin.php:305 375 msgid "Save changes" 376 msgstr "" 377 378 #: includes/admin/class-admin.php:384 379 msgid "Never" 380 msgstr "" 381 382 #: includes/admin/class-admin.php:386 383 #, php-format 384 msgid "%s ago" 385 msgstr "" 386 387 #. translators: %s: human time difference 388 #: includes/admin/class-admin.php:386 389 #, php-format 390 msgid "in %s" 391 msgstr "" 392 393 #: includes/admin/class-admin.php:392 394 msgid "Your session expired. Please reload the page." 395 msgstr "" 396 397 #: includes/admin/class-admin.php:393 includes/admin/class-admin.php:408 398 msgid "You do not have permission to do this." 399 msgstr "" 400 401 #: includes/admin/class-admin.php:407 402 msgid "Unknown settings section." 403 msgstr "" 404 405 #: includes/admin/class-admin.php:415 406 msgid "Please enter a valid sender email address." 407 msgstr "" 408 409 #: includes/admin/class-admin.php:450 naimur-email-otp-for-woocommerce.php:116 410 msgid "Please enter a valid email address." 411 msgstr "" 412 413 #: includes/admin/class-admin.php:458 414 msgid "WordPress could not send the email. Check your mail or SMTP configuration." 415 msgstr "" 416 417 #. translators: %s: recipient email address 418 #: includes/admin/class-admin.php:459 419 #, php-format 420 msgid "Test email sent to %s. Check the inbox (and the spam folder)." 421 msgstr "" 422 423 #: includes/admin/class-admin.php:467 424 msgid "Unknown log." 425 msgstr "" 426 427 #: includes/admin/class-admin.php:468 428 msgid "Logs cleared." 429 msgstr "" 430 431 #: includes/admin/class-admin.php:473 432 msgid "The database upgrade has not completed yet." 433 msgstr "" 434 435 #: includes/admin/class-admin.php:476 436 msgid "Cleanup finished." 437 msgstr "" 438 439 #: includes/admin/views/dashboard.php:12 includes/admin/views/templates.php:13 440 #: includes/admin/views/verification.php:12 441 msgid "Needs setup" 442 msgstr "" 443 444 #: includes/admin/views/dashboard.php:17 445 msgid "Registration Verification" 446 msgstr "" 447 448 #: includes/admin/views/dashboard.php:19 449 msgid "New accounts are created without an email code." 450 msgstr "" 451 452 #: includes/admin/views/dashboard.php:19 453 msgid "New customers confirm their email before their account is created." 454 msgstr "" 455 456 #: includes/admin/views/dashboard.php:19 457 msgid "Turned on, but the customer login and registration forms are off, so no code is asked for yet." 458 msgstr "" 459 460 #: includes/admin/views/dashboard.php:21 461 msgid "Registration settings" 462 msgstr "" 463 464 #: includes/admin/views/dashboard.php:25 465 msgid "Password Reset Verification" 466 msgstr "" 467 468 #: includes/admin/views/dashboard.php:27 469 msgid "Customers reset their password with an emailed code." 470 msgstr "" 471 472 #: includes/admin/views/dashboard.php:27 473 msgid "The standard WooCommerce reset link is used." 474 msgstr "" 475 476 #: includes/admin/views/dashboard.php:27 477 msgid "Turned on, but the customer login and registration forms are off, so the standard WooCommerce reset link is used." 478 msgstr "" 479 480 #: includes/admin/views/dashboard.php:29 481 msgid "Password reset settings" 482 msgstr "" 483 484 #: includes/admin/views/dashboard.php:33 485 msgid "Checkout Verification" 486 msgstr "" 487 488 #: includes/admin/views/dashboard.php:35 489 msgid "Optional. Orders are placed without an email code." 490 msgstr "" 491 492 #: includes/admin/views/dashboard.php:35 493 msgid "Orders are created only after the billing email is confirmed." 494 msgstr "" 495 496 #: includes/admin/views/dashboard.php:37 497 msgid "Checkout settings" 498 msgstr "" 499 500 #: includes/admin/views/dashboard.php:41 501 msgid "Email Delivery" 502 msgstr "" 503 504 #: includes/admin/views/dashboard.php:43 505 msgid "Working" 506 msgstr "" 507 508 #: includes/admin/views/dashboard.php:44 509 msgid "Not tested yet" 510 msgstr "" 511 512 #: includes/admin/views/dashboard.php:45 513 msgid "Problem found" 514 msgstr "" 515 516 #: includes/admin/views/dashboard.php:47 517 msgid "The most recent email could not be sent. Check your mail or SMTP setup." 518 msgstr "" 519 520 #. translators: %s: relative time, e.g. "5 mins ago" 521 #: includes/admin/views/dashboard.php:48 522 #, php-format 523 msgid "Last test email sent %s." 524 msgstr "" 525 526 #: includes/admin/views/dashboard.php:49 527 msgid "Emails are being handed to your mail system." 528 msgstr "" 529 530 #: includes/admin/views/dashboard.php:49 531 msgid "Send a test email to make sure codes reach your customers." 532 msgstr "" 533 534 #: includes/admin/views/dashboard.php:55 535 msgid "Security Health" 536 msgstr "" 537 538 #: includes/admin/views/dashboard.php:56 539 #: includes/admin/views/diagnostics.php:82 540 msgid "Healthy" 541 msgstr "" 542 543 #: includes/admin/views/dashboard.php:56 544 msgid "Needs attention" 545 msgstr "" 546 547 #: includes/admin/views/dashboard.php:57 548 msgid "A database update is still running. Verification is paused until it finishes." 549 msgstr "" 550 551 #: includes/admin/views/dashboard.php:58 552 msgid "Rate limits are active and expired codes are removed automatically." 553 msgstr "" 554 555 #: includes/admin/views/dashboard.php:58 556 msgid "The automatic cleanup of expired codes is not running on time." 557 msgstr "" 558 559 #: includes/admin/views/dashboard.php:60 includes/admin/views/help.php:69 560 msgid "Security settings" 561 msgstr "" 562 563 #: includes/admin/views/dashboard.php:60 564 msgid "View diagnostics" 565 msgstr "" 566 567 #: includes/admin/views/dashboard.php:65 568 msgid "Registration and password reset codes appear inside these forms on your My Account page." 569 msgstr "" 570 571 #: includes/admin/views/dashboard.php:65 572 msgid "Turn on the customer login and registration forms" 573 msgstr "" 574 575 #: includes/admin/views/dashboard.php:66 576 msgid "Emails from your own domain are less likely to land in spam." 577 msgstr "" 578 579 #: includes/admin/views/dashboard.php:66 580 msgid "Set the sender name and email address" 581 msgstr "" 582 583 #: includes/admin/views/dashboard.php:67 584 msgid "Add your store logo to the emails" 585 msgstr "" 586 587 #: includes/admin/views/dashboard.php:67 588 msgid "Customers trust emails that look like your store." 589 msgstr "" 590 591 #: includes/admin/views/dashboard.php:68 592 msgid "Confirms that your site can deliver the codes." 593 msgstr "" 594 595 #: includes/admin/views/dashboard.php:68 596 msgid "Send a test email and check that it arrives" 597 msgstr "" 598 599 #: includes/admin/views/dashboard.php:69 600 msgid "Place a test order yourself first." 601 msgstr "" 602 603 #: includes/admin/views/dashboard.php:69 604 msgid "Turn on checkout verification" 605 msgstr "" 606 607 #: includes/admin/views/dashboard.php:79 608 msgid "Welcome to Naimur Email OTP" 609 msgstr "" 610 611 #: includes/admin/views/dashboard.php:80 612 msgid "Stop fake accounts and protect your customers by asking for a one-time code sent by email when they register, reset a password or check out." 613 msgstr "" 614 615 #: includes/admin/views/dashboard.php:85 616 msgid "Everything looks good" 617 msgstr "" 618 619 #. translators: %d: number of items that need attention 620 #: includes/admin/views/dashboard.php:88 621 #, php-format 622 msgid "%d item needs attention" 623 msgid_plural "%d items need attention" 624 msgstr[0] "" 625 msgstr[1] "" 626 627 #. translators: %s: plugin version 628 #: includes/admin/views/dashboard.php:91 629 #, php-format 630 msgid "Plugin version %s" 631 msgstr "" 632 633 #: includes/admin/views/dashboard.php:95 includes/admin/views/dashboard.php:147 634 #: includes/admin/views/dashboard.php:149 635 #: includes/admin/views/diagnostics.php:53 636 #: includes/admin/views/diagnostics.php:55 637 msgid "Status" 638 msgstr "" 639 640 #. translators: 1: completed steps, 2: total steps 641 #: includes/admin/views/dashboard.php:109 642 #, php-format 643 msgid "%1$d of %2$d done" 644 msgstr "" 645 646 #: includes/admin/views/dashboard.php:109 647 msgid "Setup checklist" 648 msgstr "" 649 650 #: includes/admin/views/dashboard.php:110 651 msgid "Setup progress" 652 msgstr "" 653 654 #: includes/admin/views/dashboard.php:117 655 msgid "Optional" 656 msgstr "" 657 658 #: includes/admin/views/dashboard.php:120 659 msgid "Done" 660 msgstr "" 661 662 #: includes/admin/views/dashboard.php:120 663 msgid "Not done yet" 664 msgstr "" 665 666 #: includes/admin/views/dashboard.php:124 667 msgid "Sender and logo settings can be changed by a site administrator." 668 msgstr "" 669 670 #: includes/admin/views/dashboard.php:127 671 msgid "Quick actions" 672 msgstr "" 673 674 #: includes/admin/views/dashboard.php:129 675 msgid "Configure Checkout OTP" 676 msgstr "" 677 678 #: includes/admin/views/dashboard.php:129 679 msgid "Turn it on or off and choose who is affected." 680 msgstr "" 681 682 #: includes/admin/views/dashboard.php:131 683 msgid "Change the subject, text and design." 684 msgstr "" 685 686 #: includes/admin/views/dashboard.php:131 687 msgid "Edit Email Template" 688 msgstr "" 689 690 #: includes/admin/views/dashboard.php:133 691 msgid "Check that codes reach the inbox." 692 msgstr "" 693 694 #: includes/admin/views/dashboard.php:133 695 msgid "Send a Test Email" 696 msgstr "" 697 698 #: includes/admin/views/dashboard.php:135 699 msgid "Limits for codes and wrong guesses." 700 msgstr "" 701 702 #: includes/admin/views/dashboard.php:135 703 msgid "Open Security Settings" 704 msgstr "" 705 706 #: includes/admin/views/dashboard.php:137 707 msgid "Step-by-step guides and answers." 708 msgstr "" 709 710 #: includes/admin/views/dashboard.php:137 711 msgid "View Documentation" 712 msgstr "" 713 714 #: includes/admin/views/dashboard.php:143 715 msgid "Recent emails" 716 msgstr "" 717 718 #: includes/admin/views/dashboard.php:143 719 msgid "View all" 720 msgstr "" 721 722 #: includes/admin/views/dashboard.php:145 723 msgid "No verification emails yet. They appear here once customers start receiving codes." 724 msgstr "" 725 726 #: includes/admin/views/dashboard.php:147 727 #: includes/admin/views/dashboard.php:149 728 #: includes/admin/views/diagnostics.php:35 729 #: includes/admin/views/diagnostics.php:53 730 #: includes/admin/views/diagnostics.php:55 731 #: includes/admin/views/diagnostics.php:66 732 #: includes/admin/views/diagnostics.php:68 733 msgid "Email" 734 msgstr "" 735 736 #: includes/admin/views/dashboard.php:147 737 #: includes/admin/views/dashboard.php:149 738 #: includes/admin/views/diagnostics.php:66 739 #: includes/admin/views/diagnostics.php:68 740 msgid "Event" 741 msgstr "" 742 743 #: includes/admin/views/dashboard.php:147 744 #: includes/admin/views/dashboard.php:149 745 #: includes/admin/views/diagnostics.php:53 746 #: includes/admin/views/diagnostics.php:55 747 #: includes/admin/views/diagnostics.php:66 748 #: includes/admin/views/diagnostics.php:68 749 msgid "Time" 750 msgstr "" 751 752 #: includes/admin/views/diagnostics.php:13 753 msgid "WooCommerce is not active. This plugin needs WooCommerce." 754 msgstr "" 755 756 #: includes/admin/views/diagnostics.php:14 757 msgid "The database update has not finished. Verification is paused until it does; reload this page to try again." 758 msgstr "" 759 760 #: includes/admin/views/diagnostics.php:15 761 msgid "No SMTP plugin found. Emails are sent with the basic server mail function, which is often marked as spam. An SMTP plugin is recommended." 762 msgstr "" 763 764 #: includes/admin/views/diagnostics.php:16 765 msgid "WP-Cron is turned off (DISABLE_WP_CRON). Make sure a server cron job runs wp-cron.php, or expired codes are only removed when you click \"Run cleanup now\"." 766 msgstr "" 767 768 #: includes/admin/views/diagnostics.php:17 769 msgid "Checkout verification is on, but the checkout page type was not recognised." 770 msgstr "" 771 772 #: includes/admin/views/diagnostics.php:20 773 msgid "Checkout email verified" 774 msgstr "" 775 776 #: includes/admin/views/diagnostics.php:21 777 msgid "Order placed with verified email" 778 msgstr "" 779 780 #: includes/admin/views/diagnostics.php:22 781 msgid "Registered with verified email" 782 msgstr "" 783 784 #: includes/admin/views/diagnostics.php:23 785 msgid "Password reset code verified" 786 msgstr "" 787 788 #: includes/admin/views/diagnostics.php:27 789 msgid "Things to check" 790 msgstr "" 791 792 #: includes/admin/views/diagnostics.php:31 793 msgid "Emails not arriving?" 794 msgstr "" 795 796 #: includes/admin/views/diagnostics.php:32 797 msgid "Sends one of your saved emails with sample data (code 123456) so you can check that it arrives and looks right." 798 msgstr "" 799 800 #: includes/admin/views/diagnostics.php:39 801 msgid "Send to" 802 msgstr "" 803 804 #: includes/admin/views/diagnostics.php:42 805 msgid "Send test email" 806 msgstr "" 807 808 #: includes/admin/views/diagnostics.php:44 809 msgid "If it does not arrive within a few minutes, check the spam folder and your SMTP plugin's log." 810 msgstr "" 811 812 #: includes/admin/views/diagnostics.php:48 813 #: includes/admin/views/diagnostics.php:62 814 msgid "Clear log" 815 msgstr "" 816 817 #: includes/admin/views/diagnostics.php:48 818 msgid "Clear the email log? This cannot be undone." 819 msgstr "" 820 821 #: includes/admin/views/diagnostics.php:49 822 msgid "\"Success\" means WordPress handed the email to your mail system. It does not guarantee it reached the inbox. The log keeps the last 100 emails for 7 days." 823 msgstr "" 824 825 #: includes/admin/views/diagnostics.php:51 826 msgid "No emails logged yet." 827 msgstr "" 828 829 #: includes/admin/views/diagnostics.php:53 830 #: includes/admin/views/diagnostics.php:55 831 msgid "Message" 832 msgstr "" 833 834 #: includes/admin/views/diagnostics.php:62 835 msgid "Clear the activity log? This cannot be undone." 836 msgstr "" 837 838 #: includes/admin/views/diagnostics.php:62 839 msgid "Verification activity" 840 msgstr "" 841 842 #: includes/admin/views/diagnostics.php:64 843 msgid "No verification activity yet." 844 msgstr "" 845 846 #: includes/admin/views/diagnostics.php:66 847 #: includes/admin/views/diagnostics.php:68 848 msgid "Device" 849 msgstr "" 850 851 #: includes/admin/views/diagnostics.php:66 852 #: includes/admin/views/diagnostics.php:68 853 msgid "User" 854 msgstr "" 855 856 #: includes/admin/views/diagnostics.php:68 857 msgid "Email verified" 858 msgstr "" 859 860 #: includes/admin/views/diagnostics.php:68 861 msgid "Guest" 862 msgstr "" 863 864 #: includes/admin/views/diagnostics.php:76 865 msgid "System information" 866 msgstr "" 867 868 #: includes/admin/views/diagnostics.php:78 869 msgid "Plugin" 870 msgstr "" 871 872 #: includes/admin/views/diagnostics.php:80 873 msgid "Not active" 874 msgstr "" 875 876 #: includes/admin/views/diagnostics.php:82 877 #: includes/admin/views/verification.php:162 878 msgid "Database" 879 msgstr "" 880 881 #: includes/admin/views/diagnostics.php:82 882 msgid "Needs update" 883 msgstr "" 884 885 #: includes/admin/views/diagnostics.php:83 886 msgid "Codes waiting to be used" 887 msgstr "" 888 889 #: includes/admin/views/diagnostics.php:84 890 #: includes/admin/views/verification.php:47 891 #: includes/admin/views/verification.php:152 892 msgid "Checkout block" 893 msgstr "" 894 895 #: includes/admin/views/diagnostics.php:84 896 msgid "Checkout page" 897 msgstr "" 898 899 #: includes/admin/views/diagnostics.php:84 900 #: includes/admin/views/verification.php:154 901 msgid "Classic checkout" 902 msgstr "" 903 904 #: includes/admin/views/diagnostics.php:84 905 msgid "Not recognised" 906 msgstr "" 907 908 #: includes/admin/views/diagnostics.php:84 909 msgid "Not set" 910 msgstr "" 911 912 #: includes/admin/views/diagnostics.php:85 913 msgid "High-performance order storage" 914 msgstr "" 915 916 #: includes/admin/views/diagnostics.php:85 917 #: includes/admin/views/verification.php:160 918 msgid "Order storage" 919 msgstr "" 920 921 #: includes/admin/views/diagnostics.php:85 922 msgid "WordPress posts" 923 msgstr "" 924 925 #: includes/admin/views/diagnostics.php:86 926 msgid "Mail" 927 msgstr "" 928 929 #: includes/admin/views/diagnostics.php:86 930 msgid "PHP mail (no SMTP plugin found)" 931 msgstr "" 932 933 #: includes/admin/views/diagnostics.php:88 934 msgid "Include these versions when you ask for support. Never share codes, passwords or customer data." 935 msgstr "" 936 937 #: includes/admin/views/diagnostics.php:91 938 msgid "Maintenance" 939 msgstr "" 940 941 #: includes/admin/views/diagnostics.php:93 942 msgid "Next cleanup" 943 msgstr "" 944 945 #: includes/admin/views/diagnostics.php:93 946 msgid "Not scheduled" 947 msgstr "" 948 949 #: includes/admin/views/diagnostics.php:94 950 msgid "Last cleanup" 951 msgstr "" 952 953 #: includes/admin/views/diagnostics.php:96 954 msgid "Expired codes and old history are removed automatically every hour. You can also run it now." 955 msgstr "" 956 957 #: includes/admin/views/diagnostics.php:97 958 msgid "Run cleanup now" 959 msgstr "" 960 961 #: includes/admin/views/diagnostics.php:99 962 msgid "Older verification activity is removed by the cleanup." 963 msgstr "" 964 965 #: includes/admin/views/diagnostics.php:99 966 msgid "days" 967 msgstr "" 968 969 #. translators: %s: link to the Verification page 970 #: includes/admin/views/general.php:9 971 #, php-format 972 msgid "Looking for the customer login and registration form settings? They are now on the %s page, next to the codes they control." 973 msgstr "" 974 975 #: includes/admin/views/general.php:14 976 msgid "Sender" 977 msgstr "" 978 979 #: includes/admin/views/general.php:15 980 msgid "Who the verification emails come from. Only emails sent by this plugin are changed. Leave empty to use your WordPress defaults." 981 msgstr "" 982 983 #: includes/admin/views/general.php:18 984 msgid "Sender name" 985 msgstr "" 986 987 #: includes/admin/views/general.php:18 988 msgid "Usually your store name." 989 msgstr "" 990 991 #: includes/admin/views/general.php:19 992 msgid "Sender email" 993 msgstr "" 994 995 #: includes/admin/views/general.php:19 996 msgid "Use an address on your own domain so emails are not marked as spam. Also shown to customers as the support address." 997 msgstr "" 998 999 #: includes/admin/views/general.php:25 1000 msgid "Branding" 1001 msgstr "" 1002 1003 #: includes/admin/views/general.php:34 1004 msgid "Choose from Media Library" 1005 msgstr "" 1006 1007 #: includes/admin/views/general.php:35 1008 msgid "Remove" 1009 msgstr "" 1010 1011 #: includes/admin/views/general.php:39 1012 msgid "When empty, the WooCommerce email header image is used." 1013 msgstr "" 1014 1015 #: includes/admin/views/general.php:39 1016 msgid "When empty, your store name is shown as text." 1017 msgstr "" 1018 1019 #: includes/admin/views/general.php:44 1020 msgid "Used for buttons and highlights in the emails, the checkout popup and the customer forms." 1021 msgstr "" 1022 1023 #: includes/admin/views/general.php:50 1024 msgid "When the plugin is deleted" 1025 msgstr "" 1026 1027 #: includes/admin/views/general.php:51 1028 msgid "Delete all plugin data when the plugin is deleted" 1029 msgstr "" 1030 1031 #: includes/admin/views/general.php:51 1032 msgid "When off, your settings and email templates are kept so you can reinstall later. Pending codes, reset authorizations and logs are always removed." 1033 msgstr "" 1034 1035 #: includes/admin/views/help.php:13 1036 msgid "Getting started" 1037 msgstr "" 1038 1039 #: includes/admin/views/help.php:15 1040 msgid "Follow these steps once. The Overview page shows a checklist that ticks itself off as you go." 1041 msgstr "" 1042 1043 #. translators: %s: link to the Verification page 1044 #: includes/admin/views/help.php:17 1045 #, php-format 1046 msgid "1. Open %s and turn on the customer login and registration forms (Step 1). Choose your My Account page." 1047 msgstr "" 1048 1049 #. translators: %s: link to General Settings 1050 #: includes/admin/views/help.php:19 1051 #, php-format 1052 msgid "2. In %s, set your sender name, sender email and store logo so emails look like they come from your store." 1053 msgstr "" 1054 1055 #. translators: %s: link to Activity & Diagnostics 1056 #: includes/admin/views/help.php:21 1057 #, php-format 1058 msgid "3. Send a test email from %s and make sure it arrives." 1059 msgstr "" 1060 1061 #: includes/admin/views/help.php:22 1062 msgid "4. Optional: turn on Checkout OTP after you have placed a test order yourself." 1063 msgstr "" 1064 1065 #: includes/admin/views/help.php:26 1066 msgid "Registration OTP setup" 1067 msgstr "" 1068 1069 #: includes/admin/views/help.php:28 1070 msgid "When a customer signs up on your My Account page, they receive a 6-digit code by email and must enter it before the account is created. Email addresses that are not confirmed never become accounts." 1071 msgstr "" 1072 1073 #: includes/admin/views/help.php:29 1074 msgid "It needs the customer login and registration forms (Step 1 on the Verification page). If those forms are off, the Registration tab shows \"Needs setup\"." 1075 msgstr "" 1076 1077 #: includes/admin/views/help.php:30 1078 msgid "Code Expiration Time, Wait Before Resending and Wrong Code Attempts Allowed can be set separately for registration." 1079 msgstr "" 1080 1081 #. translators: %s: link to the Registration tab 1082 #. translators: %s: link to the Password Reset tab 1083 #. translators: %s: link to the Checkout tab 1084 #. translators: %s: link to the test email section 1085 #: includes/admin/views/help.php:32 includes/admin/views/help.php:43 1086 #: includes/admin/views/help.php:54 includes/admin/views/help.php:86 1087 #, php-format 1088 msgid "Go to %s." 1089 msgstr "" 1090 1091 #: includes/admin/views/help.php:32 1092 msgid "Registration OTP settings" 1093 msgstr "" 1094 1095 #: includes/admin/views/help.php:36 1096 msgid "Password reset setup" 1097 msgstr "" 1098 1099 #: includes/admin/views/help.php:38 1100 msgid "The customer clicks \"Forgot password?\", enters their email, receives a code and, after entering it, chooses a new password with the \"Update Password\" button." 1101 msgstr "" 1102 1103 #: includes/admin/views/help.php:39 1104 msgid "The reset permission works once and expires after 5 minutes. The customer then signs in with the new password; they are never signed in automatically." 1105 msgstr "" 1106 1107 #: includes/admin/views/help.php:40 1108 msgid "The form shows the same message whether or not an account exists, so it cannot be used to find out who is registered." 1109 msgstr "" 1110 1111 #: includes/admin/views/help.php:41 1112 msgid "If you turn this off, the standard WooCommerce \"Lost your password?\" link is used instead." 1113 msgstr "" 1114 1115 #: includes/admin/views/help.php:43 1116 msgid "Password Reset OTP settings" 1117 msgstr "" 1118 1119 #: includes/admin/views/help.php:47 1120 msgid "Checkout OTP setup" 1121 msgstr "" 1122 1123 #: includes/admin/views/help.php:49 1124 msgid "Off by default. When on, clicking Place order opens a popup asking for the code sent to the billing email. The order is created only after the code is verified, so no payment is taken before that." 1125 msgstr "" 1126 1127 #: includes/admin/views/help.php:50 1128 msgid "Guests are always asked. Logged-in customers are asked too unless you turn off \"Also verify logged-in customers\"." 1129 msgstr "" 1130 1131 #: includes/admin/views/help.php:51 1132 msgid "A verification is valid once, for the same browser session and the exact same email. Changing the email needs a new code. If a payment fails, the customer can retry payment for the same order without a new code." 1133 msgstr "" 1134 1135 #: includes/admin/views/help.php:52 1136 msgid "Before turning it on: place a test order with each payment method you use and check that the popup appears and the order completes." 1137 msgstr "" 1138 1139 #: includes/admin/views/help.php:54 1140 msgid "Checkout OTP settings" 1141 msgstr "" 1142 1143 #: includes/admin/views/help.php:58 1144 msgid "Email template customization" 1145 msgstr "" 1146 1147 #: includes/admin/views/help.php:60 1148 msgid "Each email (Registration, Password Reset, Checkout) has its own subject, preheader and design. Choose one with the tabs at the top of the Email Templates page. The preview on the right updates as you type; use Desktop and Mobile to check both sizes." 1149 msgstr "" 1150 1151 #: includes/admin/views/help.php:61 1152 msgid "Click a placeholder to insert it where your cursor is. {{otp_code}} must appear in every email. Others: {{site_name}}, {{site_url}}, {{customer_name}}, {{customer_email}}, {{expiry_minutes}}, {{support_email}}, {{site_logo}}, {{brand_color}} and {{preheader}}." 1153 msgstr "" 1154 1155 #: includes/admin/views/help.php:62 1156 msgid "For safety, scripts, forms, iframes, event handlers and javascript: links are removed when you save, and PHP is never run. Placeholder values are escaped." 1157 msgstr "" 1158 1159 #: includes/admin/views/help.php:63 1160 msgid "\"Send test\" emails what is in the editor now. \"Discard changes\" returns to the last saved version, and \"Reset to default\" restores the built-in design." 1161 msgstr "" 1162 1163 #. translators: %s: link to Email Templates 1164 #: includes/admin/views/help.php:65 1165 #, php-format 1166 msgid "Go to %s. Only site administrators can edit email HTML." 1167 msgstr "" 1168 1169 #: includes/admin/views/help.php:71 1170 msgid "Rate limits count codes sent per visitor and per email address, and code checks per visitor. They reset automatically after their time window, so a blocked visitor can try again later." 1171 msgstr "" 1172 1173 #: includes/admin/views/help.php:72 1174 msgid "The visitor address comes from REMOTE_ADDR. Forwarded headers are ignored because they can be forged. Behind Cloudflare or another proxy, a developer can supply the real address with the ceovw_client_ip filter after checking the request came from the proxy." 1175 msgstr "" 1176 1177 #: includes/admin/views/help.php:73 1178 msgid "CAPTCHA is not available in this version. Earlier versions showed CAPTCHA widgets without checking them on the server, so they were removed rather than give a false sense of protection." 1179 msgstr "" 1180 1181 #. translators: %s: link to Security 1182 #: includes/admin/views/help.php:75 1183 #, php-format 1184 msgid "Go to %s. Only site administrators can change these limits." 1185 msgstr "" 1186 1187 #: includes/admin/views/help.php:79 1188 msgid "SMTP troubleshooting: emails are not arriving" 1189 msgstr "" 1190 1191 #: includes/admin/views/help.php:81 1192 msgid "1. Install and set up an SMTP plugin, for example WP Mail SMTP or FluentSMTP. Many hosts block or spam-filter the basic PHP mail function." 1193 msgstr "" 1194 1195 #: includes/admin/views/help.php:82 1196 msgid "2. Use a sender email address on your own domain (General Settings)." 1197 msgstr "" 1198 1199 #: includes/admin/views/help.php:83 1200 msgid "3. Send a test email and check the inbox and the spam folder." 1201 msgstr "" 1202 1203 #: includes/admin/views/help.php:84 1204 msgid "4. Look at the email log. \"Failed\" means WordPress could not hand the email over; check your SMTP plugin's settings and log. \"Success\" means it was handed over, not that it reached the inbox." 1205 msgstr "" 1206 1207 #: includes/admin/views/help.php:90 1208 msgid "WooCommerce compatibility" 1209 msgstr "" 1210 1211 #: includes/admin/views/help.php:92 1212 msgid "Checkout block and classic checkout ([woocommerce_checkout] shortcode): supported." 1213 msgstr "" 1214 1215 #: includes/admin/views/help.php:93 1216 msgid "High-performance order storage (HPOS) and WordPress posts storage: supported." 1217 msgstr "" 1218 1219 #: includes/admin/views/help.php:94 1220 msgid "Payment methods: tested with Cash on delivery and Direct bank transfer. Other gateways have not been tested, so place a test order with yours before turning Checkout OTP on." 1221 msgstr "" 1222 1223 #: includes/admin/views/help.php:95 1224 msgid "Express payment buttons outside the checkout form (for example Apple Pay or Google Pay on the cart page) cannot show the popup. While Checkout OTP is on, those orders are refused and the customer completes the purchase through the checkout form." 1225 msgstr "" 1226 1227 #: includes/admin/views/help.php:96 1228 msgid "Custom checkout pages that use neither the block nor the shortcode are still protected on the server, but the popup may not appear. Activity & Diagnostics shows which checkout type your store uses." 1229 msgstr "" 1230 1231 #: includes/admin/views/help.php:100 1232 msgid "Frequently asked questions" 1233 msgstr "" 1234 1235 #: includes/admin/views/help.php:102 1236 msgid "Does Checkout OTP turn on by itself after updating?" 1237 msgstr "" 1238 1239 #: includes/admin/views/help.php:102 1240 msgid "No. It stays off until you turn it on." 1241 msgstr "" 1242 1243 #: includes/admin/views/help.php:103 1244 msgid "Can a customer reuse a code?" 1245 msgstr "" 1246 1247 #: includes/admin/views/help.php:103 1248 msgid "No. Every code works once, only for the purpose it was sent for, and only until it expires." 1249 msgstr "" 1250 1251 #: includes/admin/views/help.php:104 1252 msgid "Shop managers can turn verification on or off and send test emails. Email HTML, security limits and general settings need a site administrator." 1253 msgstr "" 1254 1255 #: includes/admin/views/help.php:104 1256 msgid "Who can change these settings?" 1257 msgstr "" 1258 1259 #: includes/admin/views/help.php:105 1260 msgid "Pending codes, reset permissions and logs are always removed. Settings and templates are kept unless \"Delete all plugin data\" is on in General Settings." 1261 msgstr "" 1262 1263 #: includes/admin/views/help.php:105 1264 msgid "What happens when I delete the plugin?" 1265 msgstr "" 1266 1267 #: includes/admin/views/help.php:106 1268 msgid "Are codes or passwords stored in the logs?" 1269 msgstr "" 1270 1271 #: includes/admin/views/help.php:106 1272 msgid "No. Codes are stored only as hashes, logs never contain codes, passwords or tokens, and email addresses are masked." 1273 msgstr "" 1274 1275 #: includes/admin/views/help.php:110 1276 msgid "Common errors and solutions" 1277 msgstr "" 1278 1279 #: includes/admin/views/help.php:112 1280 msgid "\"Too many requests. Please try again later.\" or \"Please wait … seconds before requesting a new code.\"" 1281 msgstr "" 1282 1283 #: includes/admin/views/help.php:112 1284 msgid "A rate limit or the resend wait was reached. The customer can try again after the time shown. If many real customers see this, raise the limits on the Security page." 1285 msgstr "" 1286 1287 #: includes/admin/views/help.php:113 1288 msgid "\"That code is not correct.\" or \"This code has expired.\"" 1289 msgstr "" 1290 1291 #: includes/admin/views/help.php:113 1292 msgid "The code was mistyped, already used, replaced by a newer one, or older than the Code Expiration Time. Ask the customer to request a new code and use the newest email." 1293 msgstr "" 1294 1295 #: includes/admin/views/help.php:114 1296 msgid "\"Please verify your email address to place your order.\"" 1297 msgstr "" 1298 1299 #: includes/admin/views/help.php:114 1300 msgid "The order came from a place that cannot show the popup, such as an express payment button. The customer should use the checkout form." 1301 msgstr "" 1302 1303 #: includes/admin/views/help.php:115 1304 msgid "Registration shows no code step" 1305 msgstr "" 1306 1307 #: includes/admin/views/help.php:115 1308 msgid "The customer login and registration forms are off, or the wrong My Account page is selected. Check Step 1 on the Verification page." 1309 msgstr "" 1310 1311 #: includes/admin/views/help.php:116 1312 msgid "\"This service is temporarily unavailable.\" or \"Email verification is temporarily unavailable…\"" 1313 msgstr "" 1314 1315 #: includes/admin/views/help.php:116 1316 msgid "Usually the plugin's database update has not finished, so verification is paused for safety. Open any admin page to let it finish; if the message continues, check Activity & Diagnostics and ask for support." 1317 msgstr "" 1318 1319 #: includes/admin/views/help.php:120 1320 msgid "For developers" 1321 msgstr "" 1322 1323 #: includes/admin/views/help.php:122 1324 msgid "ceovw_client_ip (filter): return the visitor IP address used for rate limits." 1325 msgstr "" 1326 1327 #: includes/admin/views/help.php:123 1328 msgid "ceovw_otp_expiry (filter): code lifetime in seconds, receives the flow name. Minimum 60." 1329 msgstr "" 1330 1331 #: includes/admin/views/help.php:124 1332 msgid "ceovw_checkout_auth_ttl (filter): how long a verified checkout email stays valid before the order is placed, in seconds. Default 1800." 1333 msgstr "" 1334 1335 #: includes/admin/views/help.php:125 1336 msgid "ceovw_admin_capability (filter): capability needed for these pages. Default manage_woocommerce." 1337 msgstr "" 1338 1339 #: includes/admin/views/help.php:126 1340 msgid "ceovw_settings_capability (filter): capability needed for email HTML, security limits and general settings. Default manage_options." 1341 msgstr "" 1342 1343 #: includes/admin/views/help.php:127 1344 msgid "Codes and reset permissions are stored as hashes in the wp_ceovw_otps table; rate-limit history is in wp_ceovw_logs. A scheduled event removes expired records every hour." 1345 msgstr "" 1346 1347 #: includes/admin/views/help.php:134 1348 msgid "Topics" 1349 msgstr "" 1350 1351 #: includes/admin/views/help.php:143 1352 msgid "Search the documentation" 1353 msgstr "" 1354 1355 #: includes/admin/views/help.php:144 1356 msgid "For example: checkout, SMTP, placeholders" 1357 msgstr "" 1358 1359 #: includes/admin/views/help.php:159 1360 msgid "Still need help?" 1361 msgstr "" 1362 1363 #. translators: %s: link to the WordPress.org support forum 1364 #: includes/admin/views/help.php:163 1365 #, php-format 1366 msgid "Ask in the %s. Please include the versions shown under System information in Activity & Diagnostics, but never post codes, passwords or personal data." 1367 msgstr "" 1368 1369 #: includes/admin/views/help.php:164 1370 msgid "(opens in a new tab)" 1371 msgstr "" 1372 1373 #: includes/admin/views/help.php:164 1374 msgid "WordPress.org support forum" 1375 msgstr "" 1376 1377 #: includes/admin/views/security.php:8 1378 msgid "These limits apply to registration, password reset and checkout codes. They stop one visitor from flooding inboxes or guessing codes. The defaults suit most stores; values outside the safe range are adjusted automatically." 1379 msgstr "" 1380 1381 #: includes/admin/views/security.php:11 1382 msgid "Codes per visitor, per hour" 1383 msgstr "" 1384 1385 #: includes/admin/views/security.php:11 1386 msgid "How many codes one visitor (IP address) can request in an hour." 1387 msgstr "" 1388 1389 #: includes/admin/views/security.php:11 1390 msgid "per hour" 1391 msgstr "" 1392 1393 #: includes/admin/views/security.php:12 1394 msgid "Codes per email address, per day" 1395 msgstr "" 1396 1397 #: includes/admin/views/security.php:12 1398 msgid "Protects one email address from receiving too many codes." 1399 msgstr "" 1400 1401 #: includes/admin/views/security.php:12 1402 msgid "per day" 1403 msgstr "" 1404 1405 #: includes/admin/views/security.php:13 1406 msgid "Code checks per visitor, per 15 minutes" 1407 msgstr "" 1408 1409 #: includes/admin/views/security.php:13 1410 msgid "How many times one visitor can try codes. Slows down guessing." 1411 msgstr "" 1412 1413 #: includes/admin/views/security.php:13 includes/admin/views/security.php:14 1414 msgid "per 15 min" 1415 msgstr "" 1416 1417 #: includes/admin/views/security.php:14 1418 msgid "Applies to the last step of a password reset." 1419 msgstr "" 1420 1421 #: includes/admin/views/security.php:14 1422 msgid "Password changes per visitor, per 15 minutes" 1423 msgstr "" 1424 1425 #. translators: %s: link to the Verification page 1426 #: includes/admin/views/security.php:20 1427 #, php-format 1428 msgid "Code expiration time, resend wait and wrong attempts are set for each verification type on the %s page." 1429 msgstr "" 1430 1431 #: includes/admin/views/security.php:28 1432 msgid "Visitor IP address" 1433 msgstr "" 1434 1435 #: includes/admin/views/security.php:29 1436 msgid "Rate limits use the address your web server reports for each connection (REMOTE_ADDR). Headers such as X-Forwarded-For are ignored because visitors can forge them." 1437 msgstr "" 1438 1439 #: includes/admin/views/security.php:31 1440 msgid "Your address right now" 1441 msgstr "" 1442 1443 #: includes/admin/views/security.php:33 1444 msgid "Custom address resolver" 1445 msgstr "" 1446 1447 #: includes/admin/views/security.php:34 1448 msgid "Active (ceovw_client_ip filter)" 1449 msgstr "" 1450 1451 #: includes/admin/views/security.php:34 1452 msgid "None" 1453 msgstr "" 1454 1455 #: includes/admin/views/security.php:36 1456 msgid "Using Cloudflare or another proxy? Every visitor may then share the proxy's address. A developer can pass the real address with the ceovw_client_ip filter after checking the request really came from the proxy." 1457 msgstr "" 1458 1459 #: includes/admin/views/security.php:39 1460 msgid "CAPTCHA" 1461 msgstr "" 1462 1463 #: includes/admin/views/security.php:39 1464 msgid "Unavailable" 1465 msgstr "" 1466 1467 #: includes/admin/views/security.php:40 1468 msgid "CAPTCHA is not part of this version. Earlier versions showed reCAPTCHA or Turnstile widgets without verifying them on the server, so they gave no real protection and have been removed. Rate limits, per-email limits and attempt limits protect the forms instead." 1469 msgstr "" 1470 1471 #: includes/admin/views/security.php:42 1472 msgid "A CAPTCHA provider was selected in an earlier version. Its saved keys are kept but not used." 1473 msgstr "" 1474 1475 #: includes/admin/views/security.php:48 1476 msgid "Always-on protections" 1477 msgstr "" 1478 1479 #: includes/admin/views/security.php:49 1480 msgid "These cannot be turned off." 1481 msgstr "" 1482 1483 #: includes/admin/views/security.php:51 1484 msgid "Codes are random 6-digit numbers from a cryptographically secure generator" 1485 msgstr "" 1486 1487 #: includes/admin/views/security.php:52 1488 msgid "Codes are stored only as a keyed hash, never in plain text" 1489 msgstr "" 1490 1491 #: includes/admin/views/security.php:53 1492 msgid "Each code works once, and wrong attempts are counted safely" 1493 msgstr "" 1494 1495 #: includes/admin/views/security.php:54 1496 msgid "A registration code cannot reset a password or verify a checkout" 1497 msgstr "" 1498 1499 #: includes/admin/views/security.php:55 1500 msgid "A verified password reset is valid once, for 5 minutes, and never signs the customer in" 1501 msgstr "" 1502 1503 #: includes/admin/views/security.php:56 1504 msgid "Password reset shows the same message whether or not the account exists" 1505 msgstr "" 1506 1507 #: includes/admin/views/security.php:57 1508 msgid "Checkout verification is tied to the browser session and the exact email" 1509 msgstr "" 1510 1511 #: includes/admin/views/security.php:58 1512 msgid "Logs never contain codes, passwords or tokens, and emails are masked" 1513 msgstr "" 1514 1515 #: includes/admin/views/security.php:59 1516 msgid "Expired records are removed by a scheduled cleanup" 1517 msgstr "" 1518 1519 #: includes/admin/views/security.php:60 1520 msgid "Verification is blocked while a database update is pending" 1521 msgstr "" 1522 1523 #: includes/admin/views/templates.php:16 1524 msgid "Email template" 1525 msgstr "" 1526 1527 #. translators: 1: verification name, 2: On/Off, 3: link to the settings 1528 #: includes/admin/views/templates.php:34 1529 #, php-format 1530 msgid "Sent for %1$s (currently %2$s). %3$s" 1531 msgstr "" 1532 1533 #: includes/admin/views/templates.php:36 1534 msgid "Change settings" 1535 msgstr "" 1536 1537 #: includes/admin/views/templates.php:42 1538 msgid "Email subject" 1539 msgstr "" 1540 1541 #: includes/admin/views/templates.php:46 1542 msgid "Preheader" 1543 msgstr "" 1544 1545 #: includes/admin/views/templates.php:48 1546 msgid "The short preview line many inboxes show after the subject." 1547 msgstr "" 1548 1549 #: includes/admin/views/templates.php:51 1550 msgid "Email design (HTML)" 1551 msgstr "" 1552 1553 #: includes/admin/views/templates.php:53 1554 msgid "For safety, scripts, forms, iframes, event handlers and unsafe links are removed when you save. PHP is never run." 1555 msgstr "" 1556 1557 #: includes/admin/views/templates.php:56 1558 msgid "(click to insert at the cursor)" 1559 msgstr "" 1560 1561 #: includes/admin/views/templates.php:56 1562 msgid "Available placeholders" 1563 msgstr "" 1564 1565 #. translators: 1: placeholder, 2: description 1566 #: includes/admin/views/templates.php:59 1567 #, php-format 1568 msgid "Insert %1$s: %2$s" 1569 msgstr "" 1570 1571 #: includes/admin/views/templates.php:65 1572 msgid "Replace this template with the built-in default? Your changes will be lost." 1573 msgstr "" 1574 1575 #: includes/admin/views/templates.php:65 1576 msgid "Reset to default" 1577 msgstr "" 1578 1579 #: includes/admin/views/templates.php:68 1580 msgid "Save template" 1581 msgstr "" 1582 1583 #: includes/admin/views/templates.php:74 1584 msgid "Live preview" 1585 msgstr "" 1586 1587 #: includes/admin/views/templates.php:75 1588 msgid "Preview width" 1589 msgstr "" 1590 1591 #: includes/admin/views/templates.php:76 1592 msgid "Desktop" 1593 msgstr "" 1594 1595 #: includes/admin/views/templates.php:77 1596 msgid "Mobile" 1597 msgstr "" 1598 1599 #: includes/admin/views/templates.php:80 1600 msgid "Subject:" 1601 msgstr "" 1602 1603 #: includes/admin/views/templates.php:81 1604 msgid "Email preview" 1605 msgstr "" 1606 1607 #: includes/admin/views/templates.php:82 1608 msgid "The preview uses sample data (Jane Doe, code 123456) and runs with scripts disabled." 1609 msgstr "" 1610 1611 #: includes/admin/views/templates.php:84 1612 msgid "Send a test of this email" 1613 msgstr "" 1614 1615 #: includes/admin/views/templates.php:87 1616 msgid "Send test" 1617 msgstr "" 1618 1619 #: includes/admin/views/templates.php:89 1620 msgid "Sends what is in the editor now, with sample data. It does not create a real code." 1621 msgstr "" 1622 1623 #: includes/admin/views/templates.php:91 1624 msgid "Template guide" 1625 msgstr "" 1626 1627 #: includes/admin/views/verification.php:18 1628 msgid "Ask new customers for a code when they create an account" 1629 msgstr "" 1630 1631 #: includes/admin/views/verification.php:19 1632 msgid "The account is created only after the customer enters the 6-digit code sent to their email." 1633 msgstr "" 1634 1635 #: includes/admin/views/verification.php:20 1636 msgid "Stops fake sign-ups and typos in email addresses. Customers fill in the registration form, receive a code by email and enter it to finish creating their account." 1637 msgstr "" 1638 1639 #: includes/admin/views/verification.php:25 1640 msgid "Let customers reset their password with an emailed code" 1641 msgstr "" 1642 1643 #: includes/admin/views/verification.php:26 1644 msgid "When off, the standard WooCommerce \"Lost your password?\" link is used instead." 1645 msgstr "" 1646 1647 #: includes/admin/views/verification.php:27 1648 msgid "Customers enter their email, receive a code and then choose a new password. They are not signed in automatically afterwards, and the form never reveals whether an email is registered." 1649 msgstr "" 1650 1651 #: includes/admin/views/verification.php:33 1652 msgid "How long the verification code remains valid. 5 minutes is recommended." 1653 msgstr "" 1654 1655 #: includes/admin/views/verification.php:33 1656 msgid "minutes" 1657 msgstr "" 1658 1659 #: includes/admin/views/verification.php:34 1660 msgid "How long customers wait before they can ask for a new code." 1661 msgstr "" 1662 1663 #: includes/admin/views/verification.php:34 1664 msgid "seconds" 1665 msgstr "" 1666 1667 #: includes/admin/views/verification.php:35 1668 msgid "After this many wrong entries the code stops working and a new one is needed." 1669 msgstr "" 1670 1671 #: includes/admin/views/verification.php:35 1672 msgid "tries" 1673 msgstr "" 1674 1675 #: includes/admin/views/verification.php:39 1676 msgid "Related:" 1677 msgstr "" 1678 1679 #: includes/admin/views/verification.php:41 1680 msgid "Edit this email" 1681 msgstr "" 1682 1683 #: includes/admin/views/verification.php:42 1684 msgid "Security limits" 1685 msgstr "" 1686 1687 #: includes/admin/views/verification.php:44 1688 msgid "How it works" 1689 msgstr "" 1690 1691 #: includes/admin/views/verification.php:48 1692 msgid "Classic checkout (shortcode)" 1693 msgstr "" 1694 1695 #: includes/admin/views/verification.php:49 1696 msgid "Custom checkout page (not recognised)" 1697 msgstr "" 1698 1699 #: includes/admin/views/verification.php:50 1700 msgid "No checkout page set" 1701 msgstr "" 1702 1703 #: includes/admin/views/verification.php:53 1704 msgid "Verification type" 1705 msgstr "" 1706 1707 #: includes/admin/views/verification.php:63 1708 msgid "Step 1: Customer login and registration forms" 1709 msgstr "" 1710 1711 #: includes/admin/views/verification.php:66 1712 msgid "Registration and password reset codes are asked for inside the plugin's own sign-in, sign-up and password reset forms. They replace the standard forms on your My Account page for visitors who are not signed in. This setting is shared by the Registration and Password Reset tabs." 1713 msgstr "" 1714 1715 #: includes/admin/views/verification.php:67 1716 msgid "Required for registration and password reset codes. Checkout verification works without it." 1717 msgstr "" 1718 1719 #: includes/admin/views/verification.php:67 1720 msgid "Use the Naimur OTP login, registration and password reset forms" 1721 msgstr "" 1722 1723 #: includes/admin/views/verification.php:69 1724 msgid "My Account page" 1725 msgstr "" 1726 1727 #: includes/admin/views/verification.php:74 1728 msgid "— Select a page —" 1729 msgstr "" 1730 1731 #: includes/admin/views/verification.php:79 1732 msgid "Usually the same page as WooCommerce → Settings → Advanced → My account page." 1733 msgstr "" 1734 1735 #: includes/admin/views/verification.php:93 1736 msgid "Go to Step 1" 1737 msgstr "" 1738 1739 #: includes/admin/views/verification.php:93 1740 msgid "This needs the customer login and registration forms. Turn them on in Step 1 above." 1741 msgstr "" 1742 1743 #: includes/admin/views/verification.php:109 1744 msgid "Customers confirm their billing email with a 6-digit code before the order is created. The check runs on your server, so no order is created and no payment is taken until the code is verified." 1745 msgstr "" 1746 1747 #: includes/admin/views/verification.php:111 1748 msgid "Off by default. Place a test order with your own payment method before turning this on for customers." 1749 msgstr "" 1750 1751 #: includes/admin/views/verification.php:111 1752 msgid "Require email verification at checkout" 1753 msgstr "" 1754 1755 #: includes/admin/views/verification.php:112 1756 msgid "When off, only guest checkouts are asked for a code." 1757 msgstr "" 1758 1759 #: includes/admin/views/verification.php:120 1760 msgid "How checkout verification works" 1761 msgstr "" 1762 1763 #: includes/admin/views/verification.php:122 1764 msgid "Customer places order" 1765 msgstr "" 1766 1767 #: includes/admin/views/verification.php:122 1768 msgid "They fill in checkout and click Place order." 1769 msgstr "" 1770 1771 #: includes/admin/views/verification.php:123 1772 msgid "A popup opens and a code goes to the billing email." 1773 msgstr "" 1774 1775 #: includes/admin/views/verification.php:123 1776 msgid "Email code sent" 1777 msgstr "" 1778 1779 #: includes/admin/views/verification.php:124 1780 msgid "Customer verifies" 1781 msgstr "" 1782 1783 #: includes/admin/views/verification.php:124 1784 msgid "They type or paste the 6-digit code." 1785 msgstr "" 1786 1787 #: includes/admin/views/verification.php:125 1788 msgid "Checkout continues" 1789 msgstr "" 1790 1791 #: includes/admin/views/verification.php:125 1792 msgid "The order is placed automatically." 1793 msgstr "" 1794 1795 #: includes/admin/views/verification.php:128 1796 msgid "Which customers are affected?" 1797 msgstr "" 1798 1799 #: includes/admin/views/verification.php:129 1800 msgid "A verified email stays verified for that browser session, so customers are not asked twice." 1801 msgstr "" 1802 1803 #: includes/admin/views/verification.php:130 1804 msgid "When does the code popup appear?" 1805 msgstr "" 1806 1807 #: includes/admin/views/verification.php:131 1808 msgid "Right after the customer clicks Place order, before the order is created. If they change the billing email, a new code is needed." 1809 msgstr "" 1810 1811 #: includes/admin/views/verification.php:132 1812 msgid "How long does the code last?" 1813 msgstr "" 1814 1815 #. translators: %s: number of minutes 1816 #: includes/admin/views/verification.php:133 1817 #, php-format 1818 msgid "%s minutes, set by Code Expiration Time above." 1819 msgstr "" 1820 1821 #: includes/admin/views/verification.php:134 1822 msgid "What if the customer cannot verify?" 1823 msgstr "" 1824 1825 #. translators: %s: number of seconds 1826 #: includes/admin/views/verification.php:135 1827 #, php-format 1828 msgid "The order is not created and no payment is taken. They can ask for a new code after %s seconds, correct their email address, or close the popup and keep their cart." 1829 msgstr "" 1830 1831 #: includes/admin/views/verification.php:136 1832 msgid "Which email is sent?" 1833 msgstr "" 1834 1835 #: includes/admin/views/verification.php:139 1836 msgid "Checkout OTP email template" 1837 msgstr "" 1838 1839 #. translators: %s: link to the email template 1840 #: includes/admin/views/verification.php:139 1841 #, php-format 1842 msgid "The %s." 1843 msgstr "" 1844 1845 #: includes/admin/views/verification.php:141 1846 msgid "The Checkout OTP email template." 1847 msgstr "" 1848 1849 #: includes/admin/views/verification.php:148 1850 msgid "Compatibility" 1851 msgstr "" 1852 1853 #: includes/admin/views/verification.php:150 1854 msgid "Your checkout page" 1855 msgstr "" 1856 1857 #: includes/admin/views/verification.php:153 1858 #: includes/admin/views/verification.php:155 1859 msgid "Supported" 1860 msgstr "" 1861 1862 #: includes/admin/views/verification.php:153 1863 msgid "The code popup opens when Place order is clicked." 1864 msgstr "" 1865 1866 #: includes/admin/views/verification.php:155 1867 msgid "Pages using the [woocommerce_checkout] shortcode." 1868 msgstr "" 1869 1870 #: includes/admin/views/verification.php:156 1871 msgid "Payment methods" 1872 msgstr "" 1873 1874 #: includes/admin/views/verification.php:157 1875 msgid "Test yours first" 1876 msgstr "" 1877 1878 #: includes/admin/views/verification.php:157 1879 msgid "Tested with Cash on delivery and Direct bank transfer. Other payment gateways have not been tested; place a test order with yours before turning this on." 1880 msgstr "" 1881 1882 #: includes/admin/views/verification.php:158 1883 msgid "Express payment buttons" 1884 msgstr "" 1885 1886 #: includes/admin/views/verification.php:159 1887 msgid "Buttons such as Apple Pay or Google Pay outside the checkout form cannot show the popup. While verification is on, those orders are refused with \"Please verify your email address to place your order.\" and the customer uses the checkout form instead." 1888 msgstr "" 1889 1890 #: includes/admin/views/verification.php:159 1891 msgid "Not supported" 1892 msgstr "" 1893 1894 #: includes/admin/views/verification.php:161 1895 msgid "High-performance order storage (supported)" 1896 msgstr "" 1897 1898 #: includes/admin/views/verification.php:161 1899 msgid "WordPress posts storage (supported)" 1900 msgstr "" 1901 1902 #: includes/admin/views/verification.php:163 1903 msgid "Ready" 1904 msgstr "" 1905 1906 #: includes/admin/views/verification.php:163 1907 msgid "Update pending: checkout is blocked until it completes" 1908 msgstr "" 1909 1910 #: includes/admin/views/verification.php:166 1911 msgid "Your checkout page uses neither the Checkout block nor the [woocommerce_checkout] shortcode. Verification is still enforced on the server, but the code popup may not appear. Test before enabling." 1912 msgstr "" 1913 1914 #: includes/class-ajax.php:83 1915 msgid "Registration is not available." 1916 msgstr "" 1917 1918 #. translators: %d: seconds 1919 #: includes/class-ajax.php:90 1920 #, php-format 1921 msgid "Please wait %d seconds before requesting a new code." 1922 msgstr "" 1923 1924 #: includes/class-ajax.php:126 1925 msgid "Password reset by code is not available. Please use the standard lost password link." 1926 msgstr "" 1927 1928 #: includes/class-ajax.php:174 1929 msgid "This verification is not available." 1930 msgstr "" 1931 1932 #: includes/class-checkout.php:77 1933 msgid "Please verify your email address to place your order." 1934 msgstr "" 1935 1936 #: includes/class-checkout.php:86 1937 msgid "Email verification is temporarily unavailable, so orders cannot be placed right now. Please try again shortly." 1938 msgstr "" 1939 1940 #: includes/class-checkout.php:151 includes/class-checkout.php:165 1941 msgid "Your session has expired. Please refresh the page and try again." 1942 msgstr "" 1943 1944 #: includes/class-checkout.php:153 1945 msgid "Email verification is not required." 1946 msgstr "" 1947 1948 #: includes/class-checkout.php:157 1949 msgid "Please enter a valid billing email address first." 1950 msgstr "" 1951 1952 #: includes/class-checkout.php:163 1953 msgid "Too many requests. Please try again later." 1954 msgstr "" 1955 1956 #: includes/class-checkout.php:169 1957 msgid "A code was already sent. Please check your inbox." 1958 msgstr "" 1959 1960 #: includes/class-checkout.php:171 1961 msgid "Too many codes were requested for this email. Please try again later." 1962 msgstr "" 1963 1964 #: includes/class-checkout.php:179 1965 msgid "We could not send the verification email. Please check the address and try again." 1966 msgstr "" 1967 1968 #: includes/class-checkout.php:192 1969 msgid "Too many attempts. Please try again later." 1970 msgstr "" 1971 1972 #: includes/class-checkout.php:199 includes/class-checkout.php:254 1973 msgid "This code has expired. Please request a new one." 1974 msgstr "" 1975 1976 #: includes/class-checkout.php:200 1977 msgid "Too many incorrect attempts. Please request a new code." 1978 msgstr "" 1979 1980 #: includes/class-checkout.php:201 includes/class-checkout.php:207 1981 msgid "That code is not correct. Please try again." 1982 msgstr "" 1983 1984 #: includes/class-checkout.php:239 1985 msgid "Verify your email" 1986 msgstr "" 1987 1988 #. translators: %s: masked email address 1989 #: includes/class-checkout.php:241 1990 #, php-format 1991 msgid "We sent a 6-digit code to %s. Enter it below to place your order." 1992 msgstr "" 1993 1994 #: includes/class-checkout.php:242 1995 msgid "Sending your code…" 1996 msgstr "" 1997 1998 #. translators: %d: position of the digit (1-6) 1999 #. translators: %d: digit position 2000 #: includes/class-checkout.php:244 templates/modal.php:14 2001 #, php-format 2002 msgid "Digit %d of 6" 2003 msgstr "" 2004 2005 #: includes/class-checkout.php:245 2006 msgid "Verify and place order" 2007 msgstr "" 2008 2009 #: includes/class-checkout.php:246 2010 msgid "Verifying…" 2011 msgstr "" 2012 2013 #: includes/class-checkout.php:247 2014 msgid "Email verified. Placing your order…" 2015 msgstr "" 2016 2017 #: includes/class-checkout.php:248 2018 msgid "Resend code" 2019 msgstr "" 2020 2021 #. translators: %d: seconds 2022 #: includes/class-checkout.php:250 2023 #, php-format 2024 msgid "Resend code in %ds" 2025 msgstr "" 2026 2027 #: includes/class-checkout.php:251 naimur-email-otp-for-woocommerce.php:119 2028 msgid "A new code has been sent." 2029 msgstr "" 2030 2031 #. translators: %s: time like 4:59 2032 #: includes/class-checkout.php:253 2033 #, php-format 2034 msgid "Code expires in %s" 2035 msgstr "" 2036 2037 #: includes/class-checkout.php:255 2038 msgid "Change email" 2039 msgstr "" 2040 2041 #: includes/class-checkout.php:257 templates/modal.php:6 2042 msgid "Close" 2043 msgstr "" 2044 2045 #: includes/class-checkout.php:258 naimur-email-otp-for-woocommerce.php:118 2046 msgid "Please enter all 6 digits." 2047 msgstr "" 2048 2049 #: includes/class-checkout.php:259 2050 msgid "Something went wrong. Please check your connection and try again." 2051 msgstr "" 2052 2053 #: includes/class-checkout.php:260 2054 msgid "Try again" 2055 msgstr "" 2056 2057 #: includes/class-emails.php:31 2058 msgid "Test" 2059 msgstr "" 2060 2061 #: includes/class-templates.php:23 2062 msgid "Your store name" 2063 msgstr "" 2064 2065 #: includes/class-templates.php:24 2066 msgid "Your store address (URL)" 2067 msgstr "" 2068 2069 #: includes/class-templates.php:25 2070 msgid "Customer name (or \"there\" when unknown)" 2071 msgstr "" 2072 2073 #: includes/class-templates.php:26 2074 msgid "Customer email address" 2075 msgstr "" 2076 2077 #: includes/class-templates.php:27 2078 msgid "The 6-digit code (required)" 2079 msgstr "" 2080 2081 #: includes/class-templates.php:28 2082 msgid "Minutes until the code expires" 2083 msgstr "" 2084 2085 #: includes/class-templates.php:29 2086 msgid "Support email address" 2087 msgstr "" 2088 2089 #: includes/class-templates.php:30 2090 msgid "Store logo image (or the store name when no logo is set)" 2091 msgstr "" 2092 2093 #: includes/class-templates.php:31 2094 msgid "Brand accent colour (hex)" 2095 msgstr "" 2096 2097 #: includes/class-templates.php:32 2098 msgid "Preheader text (inbox preview line)" 2099 msgstr "" 2100 2101 #: includes/class-templates.php:39 2102 msgid "Your {{site_name}} verification code" 2103 msgstr "" 2104 2105 #: includes/class-templates.php:40 2106 msgid "Use this code to confirm your email address." 2107 msgstr "" 2108 2109 #: includes/class-templates.php:41 2110 msgid "Confirm your email address" 2111 msgstr "" 2112 2113 #: includes/class-templates.php:42 2114 msgid "Hi {{customer_name}}, use the code below to finish creating your {{site_name}} account." 2115 msgstr "" 2116 2117 #: includes/class-templates.php:45 2118 msgid "Your {{site_name}} password reset code" 2119 msgstr "" 2120 2121 #: includes/class-templates.php:46 2122 msgid "Use this code to reset your password." 2123 msgstr "" 2124 2125 #: includes/class-templates.php:47 2126 msgid "Reset your password" 2127 msgstr "" 2128 2129 #: includes/class-templates.php:48 2130 msgid "We received a request to reset the password for {{customer_email}}. Enter the code below to continue." 2131 msgstr "" 2132 2133 #: includes/class-templates.php:51 2134 msgid "Your {{site_name}} checkout verification code" 2135 msgstr "" 2136 2137 #: includes/class-templates.php:52 2138 msgid "Use this code to confirm your email and place your order." 2139 msgstr "" 2140 2141 #: includes/class-templates.php:53 2142 msgid "Verify your email to place your order" 2143 msgstr "" 2144 2145 #: includes/class-templates.php:54 2146 msgid "Hi {{customer_name}}, enter the code below on the checkout page to confirm {{customer_email}} and complete your order." 2147 msgstr "" 2148 2149 #: includes/class-templates.php:63 2150 msgid "This code expires in {{expiry_minutes}} minutes and can be used once." 2151 msgstr "" 2152 2153 #: includes/class-templates.php:64 2154 msgid "If you did not request this code, you can ignore this email. Never share this code with anyone. We will never ask you for it." 2155 msgstr "" 2156 2157 #: includes/class-templates.php:65 2158 msgid "Need help? Contact us at {{support_email}}." 2159 msgstr "" 2160 2161 #: includes/class-templates.php:159 2162 msgid "The email subject is required." 2163 msgstr "" 2164 2165 #: includes/class-templates.php:160 2166 msgid "The email subject must be 200 characters or fewer." 2167 msgstr "" 2168 2169 #: includes/class-templates.php:161 2170 msgid "The preheader must be 255 characters or fewer." 2171 msgstr "" 2172 2173 #: includes/class-templates.php:162 2174 msgid "The HTML is too large (maximum 100 KB)." 2175 msgstr "" 2176 2177 #: includes/class-templates.php:165 2178 msgid "The HTML must contain the {{otp_code}} placeholder, otherwise customers will not receive their code." 2179 msgstr "" 2180 2181 #: includes/class-templates.php:168 2182 msgid "Some HTML was removed for security: scripts, forms, event handlers, unsafe links or unsupported tags are not allowed in emails." 2183 msgstr "" 2184 2185 #: includes/class-templates.php:228 2186 msgid "there" 2187 msgstr "" 2188 2189 #: includes/class-users.php:12 2190 msgid "An account is already registered with your email address." 2191 msgstr "" 2192 2193 #: naimur-email-otp-for-woocommerce.php:110 2194 msgid "Processing..." 2195 msgstr "" 2196 2197 #: naimur-email-otp-for-woocommerce.php:111 2198 msgid "Verified! Redirecting..." 2199 msgstr "" 2200 2201 #: naimur-email-otp-for-woocommerce.php:112 2202 msgid "An error occurred. Please try again." 2203 msgstr "" 2204 2205 #: naimur-email-otp-for-woocommerce.php:113 2206 msgid "Resend in" 2207 msgstr "" 2208 2209 #: naimur-email-otp-for-woocommerce.php:115 2210 msgid "Please fill in all fields." 2211 msgstr "" 2212 2213 #: naimur-email-otp-for-woocommerce.php:117 2214 msgid "The passwords do not match." 2215 msgstr "" 2216 2217 #: naimur-email-otp-for-woocommerce.php:120 templates/modal.php:19 2218 msgid "Verify Code" 2219 msgstr "" 2220 2221 #: naimur-email-otp-for-woocommerce.php:121 2222 msgid "Your password has been updated. Please sign in." 2223 msgstr "" 2224 2225 #: naimur-email-otp-for-woocommerce.php:134 2226 msgid "Settings" 2227 msgstr "" 2228 2229 #: templates/modal.php:9 2230 msgid "Check your email" 2231 msgstr "" 2232 2233 #: templates/modal.php:10 36 2234 msgid "Enter the 6-digit code sent to" 37 2235 msgstr "" 38 2236 39 #: naimur-email-otp-for-woocommerce.php:65 40 #: templates/modal.php:16 41 msgid "Resend code" 42 msgstr "" 43 44 #: naimur-email-otp-for-woocommerce.php:66 45 #: templates/modal.php:15 46 msgid "Verify & Create Account" 47 msgstr "" 48 49 #: naimur-email-otp-for-woocommerce.php:67 50 msgid "Invalid or expired code. Try again." 51 msgstr "" 52 53 #: naimur-email-otp-for-woocommerce.php:78 54 msgid "Settings" 55 msgstr "" 56 57 #: naimur-email-otp-for-woocommerce.php:79 58 msgid "Logs" 59 msgstr "" 60 61 #: includes/class-ajax.php:19 62 msgid "Invalid form data." 63 msgstr "" 64 65 #: includes/class-ajax.php:22 66 msgid "Email already registered. Please log in." 67 msgstr "" 68 69 #: includes/class-ajax.php:28 70 msgid "Security verification failed. Please try again." 71 msgstr "" 72 73 #: includes/class-ajax.php:41 74 msgid "Failed to send email. Contact support." 75 msgstr "" 76 77 #: includes/class-ajax.php:44 78 msgid "Code sent. Check your inbox." 79 msgstr "" 80 81 #: includes/class-ajax.php:55 82 msgid "No pending verification. Please resend code." 83 msgstr "" 84 85 #: includes/class-ajax.php:63 86 msgid "Too many attempts. Start over." 87 msgstr "" 88 89 #: includes/class-ajax.php:67 90 msgid "Invalid or expired code." 91 msgstr "" 92 93 #: includes/class-security.php:35 94 msgid "Please complete the security check." 95 msgstr "" 96 97 #: includes/class-security.php:43 98 msgid "Security verification failed." 99 msgstr "" 100 101 #: includes/class-settings.php:44 102 msgid "Customer Email OTP Verification for WooCommerce (v1.2.0)" 103 msgstr "" 104 105 #: includes/class-settings.php:49 106 msgid "Brand color (HEX)" 107 msgstr "" 108 109 #: includes/class-settings.php:53 110 msgid "Modern Login/Register UI" 111 msgstr "" 112 113 #: includes/class-settings.php:55 114 msgid "Enable custom modern design" 115 msgstr "" 116 117 #: includes/class-settings.php:56 118 msgid "Select My Account page" 119 msgstr "" 120 121 #: includes/class-settings.php:61 122 msgid "Captcha Provider" 123 msgstr "" 124 125 #: includes/class-settings.php:64 126 msgid "None" 127 msgstr "" 128 129 #: includes/class-settings.php:65 130 msgid "Google reCAPTCHA v2" 131 msgstr "" 132 133 #: includes/class-settings.php:66 134 msgid "Cloudflare Turnstile" 135 msgstr "" 136 137 #: includes/class-settings.php:71 138 msgid "Google reCAPTCHA Keys" 139 msgstr "" 140 141 #: includes/class-settings.php:78 142 msgid "Cloudflare Turnstile Keys" 143 msgstr "" 144 145 #: includes/class-settings.php:85 146 msgid "Login header HTML" 147 msgstr "" 148 149 #: includes/class-settings.php:89 150 msgid "Register header HTML" 151 msgstr "" 152 153 #: includes/class-settings.php:95 154 msgid "View Logs" 155 msgstr "" 156 157 #: includes/class-users.php:10 158 msgid "Authenticated" 159 msgstr "" 160 161 #: templates/modal.php:4 162 msgid "Close" 163 msgstr "" 164 165 #: templates/modal.php:6 166 msgid "Verify your email" 167 msgstr "" 168 169 #: templates/woocommerce/myaccount/form-login.php:13 170 #: templates/woocommerce/myaccount/form-login.php:55 171 msgid "Email address" 172 msgstr "" 173 174 #: templates/woocommerce/myaccount/form-login.php:17 175 #: templates/woocommerce/myaccount/form-login.php:59 2237 #: templates/modal.php:12 2238 msgid "Verification code" 2239 msgstr "" 2240 2241 #: templates/modal.php:23 2242 msgid "Didn't get it?" 2243 msgstr "" 2244 2245 #: templates/modal.php:24 2246 msgid "Resend Code" 2247 msgstr "" 2248 2249 #: templates/modal.php:27 2250 msgid "Tip: you can paste the whole code into the first box. Check your spam folder if it has not arrived." 2251 msgstr "" 2252 2253 #: templates/woocommerce/myaccount/form-login.php:14 2254 msgid "Welcome Back" 2255 msgstr "" 2256 2257 #: templates/woocommerce/myaccount/form-login.php:15 2258 msgid "Sign in to your account" 2259 msgstr "" 2260 2261 #: templates/woocommerce/myaccount/form-login.php:20 2262 #: templates/woocommerce/myaccount/form-login.php:58 2263 msgid "Email Address" 2264 msgstr "" 2265 2266 #: templates/woocommerce/myaccount/form-login.php:24 2267 #: templates/woocommerce/myaccount/form-login.php:63 176 2268 msgid "Password" 177 2269 msgstr "" 178 2270 2271 #: templates/woocommerce/myaccount/form-login.php:29 2272 msgid "Remember me" 2273 msgstr "" 2274 179 2275 #: templates/woocommerce/myaccount/form-login.php:30 180 msgid "Remember me"181 msgstr ""182 183 #: templates/woocommerce/myaccount/form-login.php:31184 2276 msgid "Forgot password?" 185 2277 msgstr "" 186 2278 187 #: templates/woocommerce/myaccount/form-login.php:36 2279 #: templates/woocommerce/myaccount/form-login.php:34 2280 msgid "Sign In" 2281 msgstr "" 2282 2283 #: templates/woocommerce/myaccount/form-login.php:40 2284 msgid "Don't have an account?" 2285 msgstr "" 2286 2287 #: templates/woocommerce/myaccount/form-login.php:40 2288 msgid "Sign up" 2289 msgstr "" 2290 2291 #: templates/woocommerce/myaccount/form-login.php:49 2292 msgid "Create Account" 2293 msgstr "" 2294 2295 #: templates/woocommerce/myaccount/form-login.php:50 2296 msgid "We'll email you a 6-digit code to confirm your address." 2297 msgstr "" 2298 2299 #: templates/woocommerce/myaccount/form-login.php:54 2300 msgid "Full Name" 2301 msgstr "" 2302 2303 #: templates/woocommerce/myaccount/form-login.php:67 2304 msgid "Confirm Password" 2305 msgstr "" 2306 2307 #: templates/woocommerce/myaccount/form-login.php:73 2308 msgid "Sign Up & Verify" 2309 msgstr "" 2310 2311 #: templates/woocommerce/myaccount/form-login.php:78 2312 msgid "Already have an account?" 2313 msgstr "" 2314 2315 #: templates/woocommerce/myaccount/form-login.php:78 188 2316 msgid "Sign in" 189 2317 msgstr "" 190 2318 191 #: templates/woocommerce/myaccount/form-login.php:39 192 msgid "Don't have an account?" 193 msgstr "" 194 195 #: templates/woocommerce/myaccount/form-login.php:39 196 msgid "Sign up" 197 msgstr "" 198 199 #: templates/woocommerce/myaccount/form-login.php:51 200 msgid "Full Name" 201 msgstr "" 202 203 #: templates/woocommerce/myaccount/form-login.php:63 204 msgid "Confirm Password" 205 msgstr "" 206 207 #: templates/woocommerce/myaccount/form-login.php:77 208 msgid "Create Account" 209 msgstr "" 210 211 #: templates/woocommerce/myaccount/form-login.php:81 212 msgid "Back to sign in" 213 msgstr "" 2319 #: templates/woocommerce/myaccount/form-login.php:87 2320 msgid "Reset Password" 2321 msgstr "" 2322 2323 #: templates/woocommerce/myaccount/form-login.php:88 2324 msgid "Enter your email and we'll send you a 6-digit code." 2325 msgstr "" 2326 2327 #: templates/woocommerce/myaccount/form-login.php:92 2328 msgid "Registered Email" 2329 msgstr "" 2330 2331 #: templates/woocommerce/myaccount/form-login.php:97 2332 msgid "Send Verification Code" 2333 msgstr "" 2334 2335 #: templates/woocommerce/myaccount/form-login.php:102 2336 msgid "Back to Sign in" 2337 msgstr "" 2338 2339 #: templates/woocommerce/myaccount/form-login.php:109 2340 msgid "Set New Password" 2341 msgstr "" 2342 2343 #: templates/woocommerce/myaccount/form-login.php:110 2344 msgid "Choose a new password for your account." 2345 msgstr "" 2346 2347 #: templates/woocommerce/myaccount/form-login.php:115 2348 msgid "New Password" 2349 msgstr "" 2350 2351 #: templates/woocommerce/myaccount/form-login.php:119 2352 msgid "Confirm New Password" 2353 msgstr "" 2354 2355 #: templates/woocommerce/myaccount/form-login.php:124 2356 msgid "Update Password" 2357 msgstr "" -
naimur-email-otp-for-woocommerce/tags/2.1.2/naimur-email-otp-for-woocommerce.php
r3780003 r3780739 3 3 * Plugin Name: Naimur Email OTP Verification for WooCommerce 4 4 * Plugin URI: https://naimurrahmannahid.com/naimur-otp-plugin/ 5 * Description: Verify WooCommerce customer emails with a 6-digit OTP for registration and password reset, with a modern, speed-optimized My Account UI.6 * Version: 2.1. 15 * Description: Verify WooCommerce customer emails with a 6-digit OTP for registration, password reset and (optionally) checkout, with editable email templates and a modern admin. 6 * Version: 2.1.2 7 7 * Requires at least: 6.0 8 8 * Tested up to: 7.1 9 9 * Requires PHP: 7.4 10 10 * Requires Plugins: woocommerce 11 * WC tested up to: 1 0.811 * WC tested up to: 11.2 12 12 * Author: Naimur Rahman Nahid 13 13 * Author URI: https://naimurrahmannahid.com/ … … 17 17 if ( ! defined( 'ABSPATH' ) ) { exit; } 18 18 19 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION', '2.1. 1');20 define('CEOVW_DB_VERSION', '2.1. 1');19 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION', '2.1.2'); 20 define('CEOVW_DB_VERSION', '2.1.2'); 21 21 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH', plugin_dir_path(__FILE__)); 22 22 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL', plugin_dir_url(__FILE__)); 23 23 24 24 // Load all files exactly matching your folder structure 25 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-options.php'; 26 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-templates.php'; 25 27 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/helpers.php'; 26 28 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-ajax.php'; … … 31 33 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-settings.php'; 32 34 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-users.php'; 35 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-checkout.php'; 36 37 CEOVW_Checkout::init(); 38 if ( is_admin() ) { 39 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/admin/class-admin.php'; 40 CEOVW_Admin::init(); 41 } 42 43 // Declare compatibility with WooCommerce features this plugin was tested with 44 add_action( 'before_woocommerce_init', function() { 45 if ( class_exists( '\\Automattic\\WooCommerce\\Utilities\\FeaturesUtil' ) ) { 46 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); 47 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true ); 48 } 49 }); 33 50 34 51 // Activation Hooks for DB & Cron … … 69 86 // Enqueue Highly Optimized Assets 70 87 add_action('wp_enqueue_scripts', function() { 71 $override_enabled = (bool) get_option('naimur-email-otp-for-woocommerce_override_enabled', false);72 $my_page_id = (int) get_option('naimur-email-otp-for-woocommerce_myaccount_page_id', 0);88 $override_enabled = (bool) CEOVW_Options::get('modern_ui'); 89 $my_page_id = (int) CEOVW_Options::get('myaccount_page_id'); 73 90 74 91 $should_enqueue = false; … … 80 97 wp_enqueue_style('ceovw-modal-css', NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL.'assets/css/modal.css', [], NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION); 81 98 wp_enqueue_style('ceovw-corporate-ui', NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL.'assets/css/override-myaccount.css', [], NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION); 82 83 // Captcha Integration 84 $captcha_type = get_option('naimur-email-otp-for-woocommerce_captcha_type', 'none'); 85 if ( $captcha_type === 'recaptcha' && get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key') ) { 86 wp_enqueue_script('google-recaptcha','https://www.google.com/recaptcha/api.js',[],NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION,true); 87 wp_enqueue_script('ceovw-boot-app', NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL.'assets/js/boot.js', ['jquery'], NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION, true); 88 } elseif ( $captcha_type === 'turnstile' && get_option('naimur-email-otp-for-woocommerce_turnstile_site_key') ) { 89 wp_enqueue_script('cloudflare-turnstile','https://challenges.cloudflare.com/turnstile/v0/api.js',[],NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION,true); 90 } 99 100 // CAPTCHA is not loaded: earlier versions displayed it without server-side verification (see Security & Rate Limits) 91 101 92 102 // Main App Logic … … 102 112 'error' => esc_html__('An error occurred. Please try again.', 'naimur-email-otp-for-woocommerce'), 103 113 'resend_in' => esc_html__('Resend in', 'naimur-email-otp-for-woocommerce'), 114 // Shown with .text() in JavaScript, so plain (unescaped) strings are safe here. 115 'required' => __('Please fill in all fields.', 'naimur-email-otp-for-woocommerce'), 116 'badEmail' => __('Please enter a valid email address.', 'naimur-email-otp-for-woocommerce'), 117 'mismatch' => __('The passwords do not match.', 'naimur-email-otp-for-woocommerce'), 118 'enterCode' => __('Please enter all 6 digits.', 'naimur-email-otp-for-woocommerce'), 119 'codeSent' => __('A new code has been sent.', 'naimur-email-otp-for-woocommerce'), 120 'verifyBtn' => __('Verify Code', 'naimur-email-otp-for-woocommerce'), 121 'updated' => __('Your password has been updated. Please sign in.', 'naimur-email-otp-for-woocommerce'), 104 122 ], 105 123 'settings' => [ 106 'captcha' => sanitize_text_field($captcha_type), 107 'site_key' => $captcha_type === 'recaptcha' ? get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key') : get_option('naimur-email-otp-for-woocommerce_turnstile_site_key'), 124 'resend' => [ 125 'register' => CEOVW_Options::resend_seconds('registration'), 126 'forgot' => CEOVW_Options::resend_seconds('forgot'), 127 ], 108 128 ] 109 129 ]); … … 112 132 // Settings Links 113 133 add_filter('plugin_action_links_'.plugin_basename(__FILE__), function($links){ 114 $links[] = '<a href="'.esc_url(admin_url('admin.php?page= naimur-email-otp-for-woocommerce-settings')).'">'.esc_html__('Settings', 'naimur-email-otp-for-woocommerce').'</a>';134 $links[] = '<a href="'.esc_url(admin_url('admin.php?page=ceovw-dashboard')).'">'.esc_html__('Settings', 'naimur-email-otp-for-woocommerce').'</a>'; 115 135 return $links; 116 136 }); -
naimur-email-otp-for-woocommerce/tags/2.1.2/readme.txt
r3780003 r3780739 2 2 Contributors: naimurrahmannahid 3 3 Donate link: https://buymeacoffee.com/naimurrahmannahid 4 Tags: woocommerce, otp, email verification, c ustomer4 Tags: woocommerce, otp, email verification, checkout, customer 5 5 Requires at least: 6.0 6 6 Tested up to: 7.1 7 7 Requires PHP: 7.4 8 Stable tag: 2.1. 18 Stable tag: 2.1.2 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Verify WooCommerce customer emails with a 6-digit OTP during registration and secure password recovery.12 Verify WooCommerce customer emails with a 6-digit code at registration, password reset and (optionally) checkout. 13 13 14 14 == Description == 15 15 16 **Naimur Email OTP Verification for WooCommerce** confirms that a customer really owns their email address before an account is created, and uses the same one-time code (OTP) to protect password recovery.16 **Naimur Email OTP Verification for WooCommerce** confirms that a customer really owns their email address. A one-time code (OTP) is emailed and must be entered before the action completes. 17 17 18 18 The OTP is used for: … … 20 20 * New customer registration 21 21 * Password reset (Forgot Password) 22 23 The plugin does not add OTP to login or checkout. 22 * Checkout (optional, off by default): the order is placed only after the billing email is verified 23 24 The plugin does not add OTP to login. 24 25 25 26 = Key features = 26 27 27 28 * 6-digit email OTP for WooCommerce customer registration 28 * OTP-based Forgot Password flow with a single-use, short-lived reset authorization 29 * Modern, responsive My Account sign-in, registration and password recovery forms 30 * OTP entry with auto-advance, paste support and a resend button with countdown 31 * Three bundled email templates plus your own custom HTML template, with a live preview in the admin 32 * Email and activity logs in the admin (OTP codes are never shown; email addresses are masked) 33 * Server-side protection: OTP attempt limits, code expiry and rate limits for sending, verification and password reset 34 * Translation-ready, with developer filters such as `ceovw_otp_expiry`, `ceovw_rate_limit` and `ceovw_client_ip` 29 * OTP-based Forgot Password flow with a single-use, short-lived reset authorization (no automatic login after a reset) 30 * Optional checkout email verification for the Checkout block and the classic checkout, for guests and (optionally) logged-in customers. Verification is enforced on the server before the order is created, so no payment is taken from an unverified checkout 31 * Accessible verification popup at checkout: 6-digit input with paste support, countdown, resend with cooldown, change email, mobile bottom-sheet layout 32 * Three responsive email templates (registration, password reset, checkout) that use your store logo, name and brand colour 33 * HTML template editor with syntax highlighting, placeholders, a sandboxed live preview (desktop and mobile) and test emails 34 * "Naimur OTP" admin area: overview with setup checklist, one Verification page with Registration, Password Reset and Checkout tabs, email templates, security limits, branding, activity and diagnostics, searchable help and a settings finder 35 * Server-side protection: hashed codes, attempt limits, expiry, single use, per-IP and per-email rate limits, purpose and session binding 36 * Compatible with WooCommerce High-Performance Order Storage (HPOS) and the Cart and Checkout blocks 37 * Translation-ready, with developer filters such as `ceovw_otp_expiry`, `ceovw_checkout_auth_ttl`, `ceovw_rate_limit` and `ceovw_client_ip` 35 38 36 39 = How it works = 37 40 38 1. The customer fills in the registration form and receives a 6-digit code by email.39 2. The account is created only after the correct code is entered.40 3. For a forgotten password, the customer requests a code, verifies it, and then sets a new password. After the reset they sign in with the new password; they are not logged in automatically.41 1. Registration: the customer fills in the registration form and receives a 6-digit code by email. The account is created only after the correct code is entered. 42 2. Password reset: the customer requests a code, verifies it and sets a new password, then signs in with it. 43 3. Checkout (when enabled): after clicking Place order, a popup asks for the code sent to the billing email. Once verified, the order is placed automatically. Changing the email requires a new code. 41 44 42 45 = Documentation = 43 46 44 Full documentation for setup, usage and customization: https://naimurrahmannahid.com/naimur-otp-plugin/47 Built-in documentation is available under **Naimur OTP → Help & Documentation**. More guides: https://naimurrahmannahid.com/naimur-otp-plugin/ 45 48 46 49 Video tutorial: … … 48 51 [youtube https://youtu.be/AehMWNLxkVk] 49 52 50 == External services ==51 52 This plugin can optionally display a Google reCAPTCHA or Cloudflare Turnstile widget on the forms of its My Account UI. When enabled, the provider's script is loaded in the visitor's browser and the visitor's browser communicates directly with the provider. In version 2.1.1 the plugin's server does not send the CAPTCHA response to the provider for verification.53 54 **Google reCAPTCHA:**55 When reCAPTCHA is enabled in the plugin settings and a site key is configured, the visitor's browser loads the reCAPTCHA script from Google, and Google may receive:56 * The IP address of the user57 * Browser and usage information collected by the reCAPTCHA widget58 59 Service Provider:60 * Google reCAPTCHA – Provided by Google LLC61 * Terms of Service: https://www.google.com/recaptcha/terms/62 * Privacy Policy: https://policies.google.com/privacy/63 64 **Cloudflare Turnstile:**65 When Turnstile is enabled and a site key is configured, the visitor's browser loads the Turnstile script from Cloudflare. Similar to reCAPTCHA, IP and browser telemetry may be processed by Cloudflare.66 67 Service provider:68 * Cloudflare Turnstile – provided by Cloudflare, Inc.69 * Terms of Service: https://www.cloudflare.com/website-terms/70 * Privacy Policy: https://www.cloudflare.com/privacypolicy/71 72 53 == Source code == 73 54 … … 78 59 * `assets/src/js/main.js` 79 60 80 This file contains the original source code that can be viewed, studied and modified by developers.61 All other JavaScript and CSS files (`assets/js/checkout-otp.js`, `assets/admin/admin.js` and the stylesheets) are shipped unminified. 81 62 82 63 == Installation == 83 64 84 65 1. In your WordPress admin, go to **Plugins → Add New**, search for "Naimur Email OTP Verification for WooCommerce", then install and activate it. WooCommerce must be installed and active. 85 2. Go to **WooCommerce → Email Verification**. 86 3. Set the email subject and choose an email template (or write your own custom HTML using the `[naimur_email_otp_code]` placeholder). The live preview shows the result. 87 4. To use the modern forms, tick **Enable custom modern design** and select your WooCommerce My Account page, then save. 66 2. Open **Naimur OTP → Verification**. In Step 1, turn on the customer login and registration forms and select your My Account page. 67 3. Open **Naimur OTP → General Settings** and set your sender name, sender email and logo. 68 4. Review the emails under **Naimur OTP → Email Templates** and send yourself a test email. 69 5. Optional: turn on **Naimur OTP → Verification → Checkout OTP** after placing a test order yourself. 88 70 5. Make sure your site can send email. Using an SMTP plugin is recommended for reliable delivery. 89 71 … … 91 73 92 74 = Where is the OTP used? = 93 For new customer registration and for password reset (Forgot Password). The plugin does not add OTP to login or checkout. 94 95 = Can I customize the verification email? = 96 Yes. You can set the email subject, choose one of three bundled templates, or write your own HTML template. Use the `[naimur_email_otp_code]` placeholder where the code should appear. The settings page shows a live preview. 75 For new customer registration, for password reset and, if you enable it, at checkout. The plugin does not add OTP to login. 76 77 = Does checkout verification work with the Checkout block? = 78 Yes. It supports the Checkout block (Store API) and the classic `[woocommerce_checkout]` shortcode. The check runs on the server when the order is placed, so it cannot be skipped by disabling JavaScript or calling the API directly. 79 80 = Can a customer be charged before verifying? = 81 No. The order is not created until the billing email is verified, so payment is never requested for an unverified checkout. If a payment fails, the customer can retry payment for the same order and the same email without a new code. 82 83 = What about express payment buttons (Apple Pay, Google Pay)? = 84 Express buttons outside the checkout form skip the verification popup. While checkout verification is required, such orders are refused with the message "Please verify your email address to place your order." and the customer can complete the purchase through the checkout form. Turn checkout verification off if you rely on express-only payments. 85 86 = Can I customize the verification emails? = 87 Yes. Each email has its own subject, preheader and HTML. Use placeholders such as `{{otp_code}}`, `{{customer_name}}`, `{{site_name}}`, `{{expiry_minutes}}` and `{{support_email}}`. Scripts, forms, iframes and unsafe links are removed when you save, and PHP is never executed. 97 88 98 89 = How long is a code valid? = 99 Codes expire after 5 minutes and allow up to 5 verification attempts. Developers can change the expiry with the `ceovw_otp_expiry` filter.90 5 minutes by default, with 5 attempts. You can set expiry (1–30 minutes), resend delay and attempts separately for registration, password reset and checkout. 100 91 101 92 = Is the customer logged in after resetting their password? = 102 No. Since version 2.1.1 the customer is returnedto the sign-in form and signs in with the new password.93 No. The customer returns to the sign-in form and signs in with the new password. 103 94 104 95 = Does this plugin work with SMTP plugins? = 105 Yes. Emails are sent with WordPress's `wp_mail()`, so SMTP plugins that hook into `wp_mail()` (for example WP Mail SMTP, FluentSMTP or Post SMTP) handle delivery. 106 107 = Do reCAPTCHA and Cloudflare Turnstile protect the forms? =108 No t in version 2.1.1. The widgets can be displayed on the plugin's forms, but the CAPTCHA response is not verified on the server, so they do not currently block automated requests. OTP requests, OTP verification and password resets are protected by server-side rate limits instead. Server-side CAPTCHA verification is planned for a future release.96 Yes. Emails are sent with WordPress's `wp_mail()`, so SMTP plugins that hook into `wp_mail()` (for example WP Mail SMTP, FluentSMTP or Post SMTP) handle delivery. The sender name and address you set apply only to this plugin's emails. 97 98 = Is there CAPTCHA? = 99 No. Earlier versions could display reCAPTCHA or Turnstile without verifying it on the server, which gave no real protection, so it was removed in 2.1.2. The forms are protected by server-side rate limits and attempt limits. 109 100 110 101 = Does it slow down the site? = 111 The plugin's styles and scripts load only on the WooCommerce My Account page for visitors who are not logged in.102 No noticeable impact. The My Account scripts load only on the My Account page for visitors who are not logged in, the checkout script loads only on the checkout page when checkout verification is enabled, and admin assets load only on the plugin's own admin pages. 112 103 113 104 = My site is behind a proxy or CDN. Which IP address is used? = 114 105 For rate limiting and activity logs the plugin uses `REMOTE_ADDR` and does not trust forwarded headers. If your server sits behind a trusted proxy, return the real client IP through the `ceovw_client_ip` filter. 115 106 107 = What happens to my data if I delete the plugin? = 108 Pending codes, reset authorizations and logs are always removed. Settings and email templates are kept unless you turn on "Delete all plugin data" under General Settings. 109 116 110 == Screenshots == 117 111 118 1. Customer registration form in the modern My Account design. 119 2. Email OTP verification during customer registration. 120 3. Forgot Password: request a verification code by email. 121 4. Set a new password after the code is verified. 122 5. After a password reset the customer returns to the sign-in form (no automatic login). 123 6. Email subject and template selection with live preview. 124 7. Custom HTML email template with live preview. 125 8. Email and activity logs with masked email addresses and hidden OTP codes. 112 1. Overview with the status of each verification, email delivery and security health, a setup checklist and quick actions. 113 2. Checkout verification popup on the Checkout block. 114 3. Checkout verification popup on a phone. 115 4. Email template editor with syntax highlighting and live preview. 116 5. Checkout OTP tab: settings, how checkout verification works and compatibility. 117 6. Verification page with Registration, Password Reset and Checkout tabs. 118 7. Security settings: rate limits and always-on protections. 119 8. Checkout verification email. 120 9. Customer registration form on the My Account page. 121 10. Email OTP verification during customer registration. 122 11. Set a new password after the code is verified. 123 12. Activity & Diagnostics: test email, email log with masked addresses and system information. 126 124 127 125 == Changelog == 126 127 = 2.1.2 = 128 Added: 129 * Optional checkout email verification (off by default) for the Checkout block and classic checkout, for guests and logged-in customers. Enforced on the server before the order is created; verification is single-use and bound to the browser session, the exact email and the checkout purpose. 130 * Accessible checkout verification popup with paste support, countdown, resend cooldown, change email and a mobile layout. 131 * The registration and password reset code popup now matches the checkout popup: dialog semantics, Escape to close, focus kept inside, paste and one-time-code autofill, arrow and Backspace keys, and inline messages instead of browser alerts. 132 * New "Naimur OTP" admin menu with Overview, Verification (Registration, Password Reset and Checkout tabs), Email Templates, Security, Activity & Diagnostics, General Settings and Help & Documentation. 133 * Overview page with real status for each verification, email delivery and security health, a setup checklist and quick actions. 134 * Plain-language settings with help text, unsaved-changes warning, Discard changes, inline validation, a settings finder and contextual links to the documentation. 135 * Three new responsive email templates (registration, password reset, checkout) using your store logo and brand colour. 136 * HTML email template editor with syntax highlighting, placeholders, sandboxed live preview (desktop and mobile), reset and test email. 137 * Per-flow settings for code expiry, resend delay and attempts; adjustable rate limits within safe ranges. 138 * Sender name and email for this plugin's emails only, store logo, uninstall data retention option and activity log retention. 139 * Server-side resend cooldown for all flows. 140 * Declared compatibility with WooCommerce HPOS and the Cart and Checkout blocks. 141 Changed: 142 * Settings moved from WooCommerce → Email Verification to the new Naimur OTP menu. Existing settings are kept; old admin links redirect. 143 * Email HTML, security limits and general settings now require a site administrator (manage_options). Shop managers can still turn verification on or off and send test emails. 144 * A custom HTML template and custom subject from earlier versions are migrated to the registration and password reset templates. The bundled templates 1–3 are replaced by the new design. 145 * Password reset button now reads "Update Password" (the customer is not logged in automatically). 146 * When password reset or registration OTP is turned off, the standard WooCommerce links are shown instead. 147 Removed: 148 * reCAPTCHA and Cloudflare Turnstile widgets. They were never verified on the server. Saved keys are kept but unused, and no third-party scripts are loaded. 149 Security: 150 * All 2.1.1 protections retained: hashed codes, single-use reset authorization, atomic attempt counting, expiry, rate limits, cron cleanup, no automatic login after reset. 151 * Email HTML is filtered with a strict allowlist; placeholder values are escaped. 152 * Verification fails closed while a database upgrade is pending. 128 153 129 154 = 2.1.1 = … … 170 195 == Upgrade Notice == 171 196 197 = 2.1.2 = 198 Adds optional checkout email verification (off by default), a new Naimur OTP admin menu and editable responsive email templates. Settings are migrated automatically. reCAPTCHA/Turnstile display was removed. Requires WooCommerce. 199 172 200 = 2.1.1 = 173 201 Security release. Hardens password reset and OTP verification. Recommended for all users. After a password reset, customers now sign in with their new password. Pending password reset sessions started before the update must be restarted. -
naimur-email-otp-for-woocommerce/tags/2.1.2/templates/modal.php
r3587442 r3780739 1 1 <?php defined('ABSPATH') || exit; ?> 2 2 3 <!-- OTP MODAL OVERLAY -->3 <!-- OTP MODAL OVERLAY (same visual language as the checkout popup) --> 4 4 <div id="ceovw-otp-modal" class="ceovw-modal-overlay" style="display:none;"> 5 <div class="ceovw-modal-content"> 6 <button class="ceovw-modal-close" aria-label="Close">×</button> 5 <div class="ceovw-modal-content" role="dialog" aria-modal="true" aria-labelledby="ceovw-otp-title" aria-describedby="ceovw-otp-desc"> 6 <button type="button" class="ceovw-modal-close" aria-label="<?php esc_attr_e('Close', 'naimur-email-otp-for-woocommerce'); ?>"><span aria-hidden="true">×</span></button> 7 <div class="ceovw-modal-icon" aria-hidden="true"><svg viewBox="0 0 48 48" width="34" height="34" fill="none"><rect x="6" y="11" width="36" height="26" rx="5" stroke="currentColor" stroke-width="2.5"/><path d="M8 15l16 11 16-11" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div> 7 8 <div class="ceovw-modal-header"> 8 <h3 ><?php esc_html_e('Verification Required', 'naimur-email-otp-for-woocommerce'); ?></h3>9 <p ><?php esc_html_e('Enter the 6-digit code sent to', 'naimur-email-otp-for-woocommerce'); ?> <strong id="ceovw-target-email"></strong></p>9 <h3 id="ceovw-otp-title"><?php esc_html_e('Check your email', 'naimur-email-otp-for-woocommerce'); ?></h3> 10 <p id="ceovw-otp-desc"><?php esc_html_e('Enter the 6-digit code sent to', 'naimur-email-otp-for-woocommerce'); ?> <strong id="ceovw-target-email"></strong></p> 10 11 </div> 11 <div class="ceovw-otp-inputs"> 12 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required autofocus> 13 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 14 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 15 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 16 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 17 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 12 <div class="ceovw-otp-inputs" role="group" aria-label="<?php esc_attr_e('Verification code', 'naimur-email-otp-for-woocommerce'); ?>"> 13 <?php for ( $ceovw_i = 1; $ceovw_i <= 6; $ceovw_i++ ) : ?> 14 <input type="text" inputmode="numeric" pattern="[0-9]*" class="ceovw-otp-box" autocomplete="<?php echo 1 === $ceovw_i ? 'one-time-code' : 'off'; ?>" aria-label="<?php echo esc_attr( sprintf( /* translators: %d: digit position */ __( 'Digit %d of 6', 'naimur-email-otp-for-woocommerce' ), $ceovw_i ) ); ?>"> 15 <?php endfor; ?> 18 16 </div> 19 <div id="ceovw-otp-error" class="ceovw-error-msg" ></div>20 <button class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-verify">17 <div id="ceovw-otp-error" class="ceovw-error-msg" role="alert" aria-live="assertive"></div> 18 <button type="button" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-verify"> 21 19 <span class="btn-text"><?php esc_html_e('Verify Code', 'naimur-email-otp-for-woocommerce'); ?></span> 22 <span class="btn-loader" ></span>20 <span class="btn-loader" aria-hidden="true"></span> 23 21 </button> 24 22 <div class="ceovw-resend-wrapper"> 25 <button id="ceovw-btn-resend" class="ceovw-link" disabled><?php esc_html_e('Resend Code', 'naimur-email-otp-for-woocommerce'); ?></button> 26 <span id="ceovw-timer-text">(60s)</span> 23 <span class="ceovw-resend-q"><?php esc_html_e("Didn't get it?", 'naimur-email-otp-for-woocommerce'); ?></span> 24 <button type="button" id="ceovw-btn-resend" class="ceovw-link" disabled><?php esc_html_e('Resend Code', 'naimur-email-otp-for-woocommerce'); ?></button> 25 <span id="ceovw-timer-text"></span> 27 26 </div> 27 <p class="ceovw-modal-tip"><?php esc_html_e('Tip: you can paste the whole code into the first box. Check your spam folder if it has not arrived.', 'naimur-email-otp-for-woocommerce'); ?></p> 28 28 </div> 29 29 </div> -
naimur-email-otp-for-woocommerce/tags/2.1.2/templates/woocommerce/myaccount/form-login.php
r3587442 r3780739 1 <?php 2 defined('ABSPATH') || exit; 1 <?php 2 defined('ABSPATH') || exit; 3 3 4 // ক্যাপচা টাইপ এবং সাইট কি (Site Key) ফেচ করা 5 $captcha_type = get_option('naimur-email-otp-for-woocommerce_captcha_type', 'none'); 6 $captcha_html = ''; 7 8 if ($captcha_type === 'recaptcha') { 9 $site_key = get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key'); 10 if (!empty($site_key)) { 11 $captcha_html = '<div class="g-recaptcha" data-sitekey="' . esc_attr($site_key) . '" style="margin-bottom: 15px; display: flex; justify-content: center;"></div>'; 12 } 13 } elseif ($captcha_type === 'turnstile') { 14 $site_key = get_option('naimur-email-otp-for-woocommerce_turnstile_site_key'); 15 if (!empty($site_key)) { 16 $captcha_html = '<div class="cf-turnstile" data-sitekey="' . esc_attr($site_key) . '" style="margin-bottom: 15px; display: flex; justify-content: center;"></div>'; 17 } 18 } 4 $ceovw_reg_on = (bool) CEOVW_Options::get('registration_enabled'); 5 $ceovw_forgot_on = (bool) CEOVW_Options::get('forgot_enabled'); 19 6 ?> 20 7 21 <div id="ceovw-corporate-app" style="--ceovw-brand: <?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_brand_color', '#2563eb')); ?>;">8 <div id="ceovw-corporate-app" style="--ceovw-brand: <?php echo esc_attr(CEOVW_Options::get('brand_color')); ?>;"> 22 9 <div class="ceovw-app-card"> 23 10 24 11 <!-- 1. LOGIN VIEW --> 25 12 <div id="ceovw-view-login" class="ceovw-view-pane active"> 26 13 <div class="ceovw-header"> 27 14 <h2><?php esc_html_e('Welcome Back', 'naimur-email-otp-for-woocommerce'); ?></h2> 28 <p><?php esc_html_e(' Access your dashboard', 'naimur-email-otp-for-woocommerce'); ?></p>15 <p><?php esc_html_e('Sign in to your account', 'naimur-email-otp-for-woocommerce'); ?></p> 29 16 </div> 30 17 <form id="ceovw-form-login" method="post"> 31 18 <?php wp_nonce_field( 'woocommerce-login', 'woocommerce-login-nonce' ); ?> 32 19 <div class="ceovw-field-group"> 33 <label ><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label>34 <input type="email" name="username" required autocomplete="username" />20 <label for="ceovw-login-email"><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label> 21 <input type="email" id="ceovw-login-email" name="username" required autocomplete="username" /> 35 22 </div> 36 23 <div class="ceovw-field-group"> 37 <label ><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label>38 <input type="password" name="password" required autocomplete="current-password" />24 <label for="ceovw-login-pass"><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label> 25 <input type="password" id="ceovw-login-pass" name="password" required autocomplete="current-password" /> 39 26 </div> 40 41 <!-- ডাইনামিক ক্যাপচা রেন্ডার -->42 <?php echo $captcha_html; ?>43 27 44 28 <div class="ceovw-actions-row"> 45 29 <label class="ceovw-checkbox"><input type="checkbox" name="rememberme" value="forever" /> <?php esc_html_e('Remember me', 'naimur-email-otp-for-woocommerce'); ?></label> 46 < a href="#" data-target="forgot" class="ceovw-link ceovw-switch-view"><?php esc_html_e('Forgot password?', 'naimur-email-otp-for-woocommerce'); ?></a>30 <?php if ($ceovw_forgot_on) : ?><a href="#" data-target="forgot" class="ceovw-link ceovw-switch-view"><?php esc_html_e('Forgot password?', 'naimur-email-otp-for-woocommerce'); ?></a><?php else : ?><a href="<?php echo esc_url(wc_lostpassword_url()); ?>" class="ceovw-link"><?php esc_html_e('Forgot password?', 'naimur-email-otp-for-woocommerce'); ?></a><?php endif; ?> 47 31 </div> 32 <div class="ceovw-form-msg" role="status" aria-live="polite" hidden></div> 48 33 <button type="submit" name="login" class="ceovw-btn ceovw-btn-primary"> 49 34 <span class="btn-text"><?php esc_html_e('Sign In', 'naimur-email-otp-for-woocommerce'); ?></span> 50 <span class="btn-loader" ></span>35 <span class="btn-loader" aria-hidden="true"></span> 51 36 </button> 52 37 </form> 38 <?php if ($ceovw_reg_on) : ?> 53 39 <div class="ceovw-footer-nav"> 54 40 <p><?php esc_html_e("Don't have an account?", 'naimur-email-otp-for-woocommerce'); ?> <a href="#" data-target="register" class="ceovw-link ceovw-switch-view"><?php esc_html_e('Sign up', 'naimur-email-otp-for-woocommerce'); ?></a></p> 55 41 </div> 42 <?php endif; ?> 56 43 </div> 57 44 45 <?php if ($ceovw_reg_on) : ?> 58 46 <!-- 2. REGISTER VIEW --> 59 47 <div id="ceovw-view-register" class="ceovw-view-pane" style="display:none;"> 60 48 <div class="ceovw-header"> 61 49 <h2><?php esc_html_e('Create Account', 'naimur-email-otp-for-woocommerce'); ?></h2> 62 <p><?php esc_html_e( 'Join our platform today', 'naimur-email-otp-for-woocommerce'); ?></p>50 <p><?php esc_html_e("We'll email you a 6-digit code to confirm your address.", 'naimur-email-otp-for-woocommerce'); ?></p> 63 51 </div> 64 <form id="ceovw-form-register" method="post" >52 <form id="ceovw-form-register" method="post" novalidate> 65 53 <div class="ceovw-field-group"> 66 <label ><?php esc_html_e('Full Name', 'naimur-email-otp-for-woocommerce'); ?></label>67 <input type="text" name="ceovw_full_name" required/>54 <label for="ceovw-reg-name"><?php esc_html_e('Full Name', 'naimur-email-otp-for-woocommerce'); ?></label> 55 <input type="text" id="ceovw-reg-name" name="ceovw_full_name" required autocomplete="name" /> 68 56 </div> 69 57 <div class="ceovw-field-group"> 70 <label ><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label>71 <input type="email" name="email" required/>58 <label for="ceovw-reg-email"><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label> 59 <input type="email" id="ceovw-reg-email" name="email" required autocomplete="email" /> 72 60 </div> 73 61 <div class="ceovw-field-row"> 74 62 <div class="ceovw-field-group"> 75 <label ><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label>76 <input type="password" name="password" required/>63 <label for="ceovw-reg-pass"><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label> 64 <input type="password" id="ceovw-reg-pass" name="password" required autocomplete="new-password" /> 77 65 </div> 78 66 <div class="ceovw-field-group"> 79 <label ><?php esc_html_e('Confirm Password', 'naimur-email-otp-for-woocommerce'); ?></label>80 <input type="password" name="password_confirm" required/>67 <label for="ceovw-reg-pass2"><?php esc_html_e('Confirm Password', 'naimur-email-otp-for-woocommerce'); ?></label> 68 <input type="password" id="ceovw-reg-pass2" name="password_confirm" required autocomplete="new-password" /> 81 69 </div> 82 70 </div> 83 84 <!-- ডাইনামিক ক্যাপচা রেন্ডার --> 85 <?php echo $captcha_html; ?> 86 71 <div class="ceovw-form-msg" role="alert" aria-live="assertive" hidden></div> 87 72 <button type="submit" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-reg"> 88 73 <span class="btn-text"><?php esc_html_e('Sign Up & Verify', 'naimur-email-otp-for-woocommerce'); ?></span> 89 <span class="btn-loader" ></span>74 <span class="btn-loader" aria-hidden="true"></span> 90 75 </button> 91 76 </form> … … 94 79 </div> 95 80 </div> 81 <?php endif; ?> 96 82 83 <?php if ($ceovw_forgot_on) : ?> 97 84 <!-- 3. FORGOT PASSWORD VIEW --> 98 85 <div id="ceovw-view-forgot" class="ceovw-view-pane" style="display:none;"> 99 86 <div class="ceovw-header"> 100 87 <h2><?php esc_html_e('Reset Password', 'naimur-email-otp-for-woocommerce'); ?></h2> 101 <p><?php esc_html_e( 'Enter your email to receive a secure OTP', 'naimur-email-otp-for-woocommerce'); ?></p>88 <p><?php esc_html_e("Enter your email and we'll send you a 6-digit code.", 'naimur-email-otp-for-woocommerce'); ?></p> 102 89 </div> 103 <form id="ceovw-form-forgot" method="post" >90 <form id="ceovw-form-forgot" method="post" novalidate> 104 91 <div class="ceovw-field-group"> 105 <label ><?php esc_html_e('Registered Email', 'naimur-email-otp-for-woocommerce'); ?></label>106 <input type="email" name="reset_email" required/>92 <label for="ceovw-forgot-email"><?php esc_html_e('Registered Email', 'naimur-email-otp-for-woocommerce'); ?></label> 93 <input type="email" id="ceovw-forgot-email" name="reset_email" required autocomplete="email" /> 107 94 </div> 108 109 <!-- ডাইনামিক ক্যাপচা রেন্ডার --> 110 <?php echo $captcha_html; ?> 111 95 <div class="ceovw-form-msg" role="alert" aria-live="assertive" hidden></div> 112 96 <button type="submit" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-forgot"> 113 97 <span class="btn-text"><?php esc_html_e('Send Verification Code', 'naimur-email-otp-for-woocommerce'); ?></span> 114 <span class="btn-loader" ></span>98 <span class="btn-loader" aria-hidden="true"></span> 115 99 </button> 116 100 </form> … … 124 108 <div class="ceovw-header"> 125 109 <h2><?php esc_html_e('Set New Password', 'naimur-email-otp-for-woocommerce'); ?></h2> 126 <p><?php esc_html_e(' Secure your account with a new password', 'naimur-email-otp-for-woocommerce'); ?></p>110 <p><?php esc_html_e('Choose a new password for your account.', 'naimur-email-otp-for-woocommerce'); ?></p> 127 111 </div> 128 <form id="ceovw-form-newpass" method="post" >112 <form id="ceovw-form-newpass" method="post" novalidate> 129 113 <input type="hidden" name="verified_email" id="ceovw-verified-email" value=""> 130 114 <div class="ceovw-field-group"> 131 <label ><?php esc_html_e('New Password', 'naimur-email-otp-for-woocommerce'); ?></label>132 <input type="password" name="new_password" required/>115 <label for="ceovw-new-pass"><?php esc_html_e('New Password', 'naimur-email-otp-for-woocommerce'); ?></label> 116 <input type="password" id="ceovw-new-pass" name="new_password" required autocomplete="new-password" /> 133 117 </div> 134 118 <div class="ceovw-field-group"> 135 <label ><?php esc_html_e('Confirm New Password', 'naimur-email-otp-for-woocommerce'); ?></label>136 <input type="password" name="new_password_confirm" required/>119 <label for="ceovw-new-pass2"><?php esc_html_e('Confirm New Password', 'naimur-email-otp-for-woocommerce'); ?></label> 120 <input type="password" id="ceovw-new-pass2" name="new_password_confirm" required autocomplete="new-password" /> 137 121 </div> 122 <div class="ceovw-form-msg" role="alert" aria-live="assertive" hidden></div> 138 123 <button type="submit" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-setpass"> 139 <span class="btn-text"><?php esc_html_e('Update & Login', 'naimur-email-otp-for-woocommerce'); ?></span>140 <span class="btn-loader" ></span>124 <span class="btn-text"><?php esc_html_e('Update Password', 'naimur-email-otp-for-woocommerce'); ?></span> 125 <span class="btn-loader" aria-hidden="true"></span> 141 126 </button> 142 127 </form> 143 128 </div> 129 <?php endif; ?> 144 130 145 131 </div> -
naimur-email-otp-for-woocommerce/tags/2.1.2/uninstall.php
r3780003 r3780739 16 16 delete_option('ceovw_db_version'); 17 17 delete_option('ceovw_upgrade_lock'); 18 delete_option('ceovw_last_cleanup'); 19 20 // Settings and email templates are kept unless the store owner chose to delete them (General Settings). 21 $ceovw_settings = get_option('ceovw_settings', array()); 22 if (is_array($ceovw_settings) && !empty($ceovw_settings['delete_data_on_uninstall'])) { 23 foreach (array('ceovw_settings', 'ceovw_email_templates', 'ceovw_test_email_sent_at') as $ceovw_option) { 24 delete_option($ceovw_option); 25 } 26 $ceovw_legacy = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('naimur-email-otp-for-woocommerce_') . '%')); 27 foreach ($ceovw_legacy as $ceovw_option) { 28 delete_option($ceovw_option); 29 } 30 } -
naimur-email-otp-for-woocommerce/trunk/assets/css/override-myaccount.css
r3587442 r3780739 88 88 #ceovw-corporate-app .ceovw-footer-nav { text-align: center; margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid #e5e7eb; font-size: 0.875rem; color: #6b7280; } 89 89 90 /* OTP Modal */ 91 #ceovw-corporate-app .ceovw-modal-overlay { 92 position: fixed; top: 0; left: 0; width: 100%; height: 100%; 93 background: rgba(17, 24, 39, 0.7); backdrop-filter: blur(4px); 94 display: flex; justify-content: center; align-items: center; z-index: 99999; 95 } 96 #ceovw-corporate-app .ceovw-modal-content { 97 background: #fff; padding: 2.5rem; border-radius: 1rem; width: 100%; max-width: 24rem; 98 position: relative; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); text-align: center; 99 animation: ceovw-slide-up 0.3s ease-out; 100 } 101 @keyframes ceovw-slide-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } 102 #ceovw-corporate-app .ceovw-modal-close { 103 position: absolute; top: 1rem; right: 1rem; background: none; border: none; font-size: 1.5rem; 104 cursor: pointer; color: #9ca3af; transition: color 0.2s; 105 } 106 #ceovw-corporate-app .ceovw-modal-close:hover { color: #111827; } 107 #ceovw-corporate-app .ceovw-otp-inputs { display: flex; justify-content: center; gap: 0.5rem; margin: 2rem 0; } 108 #ceovw-corporate-app .ceovw-otp-box { 109 width: 3rem !important; height: 3.5rem !important; text-align: center; font-size: 1.5rem !important; 110 font-weight: 700; border: 2px solid #e5e7eb !important; border-radius: 0.5rem !important; padding: 0 !important; 111 } 112 #ceovw-corporate-app .ceovw-otp-box:focus { border-color: var(--ceovw-brand) !important; } 113 #ceovw-corporate-app .ceovw-error-msg { color: #dc2626; font-size: 0.875rem; margin-bottom: 1rem; height: 1.25rem; } 114 #ceovw-corporate-app .ceovw-resend-wrapper { margin-top: 1.5rem; font-size: 0.875rem; color: #6b7280; } 115 #ceovw-corporate-app #ceovw-btn-resend:disabled { color: #9ca3af; cursor: not-allowed; text-decoration: none; } 90 /* Inline form messages (replace browser alerts) */ 91 #ceovw-corporate-app .ceovw-form-msg { margin: 0 0 1rem; padding: 0.7rem 0.9rem; border-radius: 0.6rem; font-size: 0.875rem; line-height: 1.45; text-align: left; } 92 #ceovw-corporate-app .ceovw-form-msg[hidden] { display: none; } 93 #ceovw-corporate-app .ceovw-form-msg.is-error { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; } 94 #ceovw-corporate-app .ceovw-form-msg.is-success { background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; } 95 #ceovw-corporate-app input:focus-visible, #ceovw-corporate-app button:focus-visible, #ceovw-corporate-app a:focus-visible { outline: 2px solid var(--ceovw-brand, #2563eb); outline-offset: 2px; } 96 #ceovw-corporate-app .ceovw-btn:disabled { cursor: default; opacity: .85; } 97 98 /* OTP Modal: same visual language as the checkout popup */ 99 #ceovw-corporate-app .ceovw-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; padding: 16px; background: rgba(15, 23, 42, 0.55); display: flex; justify-content: center; align-items: center; z-index: 99999; box-sizing: border-box; } 100 #ceovw-corporate-app .ceovw-modal-content { background: #fff; padding: 2rem 1.75rem 1.25rem; border-radius: 18px; width: 100%; max-width: 420px; max-height: calc(100vh - 32px); overflow-y: auto; position: relative; box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28); text-align: center; animation: ceovw-slide-up 0.2s ease-out; } 101 @keyframes ceovw-slide-up { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } } 102 #ceovw-corporate-app .ceovw-modal-close { position: absolute; top: 10px; right: 12px; width: 36px; height: 36px; padding: 0; border: none; border-radius: 10px; background: transparent; font-size: 26px; line-height: 1; cursor: pointer; color: #6b7280; } 103 #ceovw-corporate-app .ceovw-modal-close:hover { background: #f3f4f6; color: #111827; } 104 #ceovw-corporate-app .ceovw-modal-icon { display: flex; align-items: center; justify-content: center; width: 64px; height: 64px; margin: 0 auto 14px; border-radius: 50%; color: var(--ceovw-brand, #2563eb); background: #eef2ff; background: color-mix(in srgb, var(--ceovw-brand, #2563eb) 12%, #fff); } 105 #ceovw-corporate-app .ceovw-modal-header h3 { margin: 0 0 8px; font-size: 21px; font-weight: 700; line-height: 1.3; color: #111827; } 106 #ceovw-corporate-app .ceovw-modal-header p { margin: 0; font-size: 14.5px; line-height: 1.55; color: #4b5563; overflow-wrap: anywhere; } 107 #ceovw-corporate-app .ceovw-otp-inputs { display: flex; justify-content: center; gap: 8px; margin: 1.25rem 0 0.25rem; } 108 #ceovw-corporate-app .ceovw-otp-box { width: 46px !important; height: 56px !important; margin: 0 !important; padding: 0 !important; text-align: center; font-size: 24px !important; font-weight: 700; color: #111827; background: #f9fafb !important; border: 1.5px solid #d1d5db !important; border-radius: 12px !important; box-shadow: none; } 109 #ceovw-corporate-app .ceovw-otp-box:focus { border-color: var(--ceovw-brand, #2563eb) !important; background: #fff !important; box-shadow: 0 0 0 3px rgba(37, 99, 235, .15); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ceovw-brand, #2563eb) 15%, transparent); outline: none; } 110 #ceovw-corporate-app .ceovw-error-msg { min-height: 1.4rem; margin: 10px 0; color: #b91c1c; font-size: 13.5px; line-height: 1.4; } 111 #ceovw-corporate-app .ceovw-error-msg.is-success { color: #047857; font-weight: 600; } 112 #ceovw-corporate-app #ceovw-btn-verify { height: 48px; border-radius: 12px; } 113 #ceovw-corporate-app .ceovw-resend-wrapper { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin-top: 1rem; font-size: 0.875rem; color: #6b7280; } 114 #ceovw-corporate-app #ceovw-btn-resend { padding: 4px 2px; border: 0; background: none; font: inherit; font-weight: 600; color: var(--ceovw-brand, #2563eb); cursor: pointer; } 115 #ceovw-corporate-app #ceovw-btn-resend:disabled { color: #9ca3af; cursor: not-allowed; text-decoration: none; font-weight: 500; } 116 #ceovw-corporate-app .ceovw-modal-tip { margin: 12px 0 0; padding-top: 12px; border-top: 1px solid #f1f5f9; font-size: 12.5px; line-height: 1.5; color: #6b7280; } 116 117 117 118 /* Responsive */ … … 119 120 #ceovw-corporate-app .ceovw-app-card { padding: 2rem 1.5rem; } 120 121 #ceovw-corporate-app .ceovw-field-row { flex-direction: column; gap: 0; } 121 #ceovw-corporate-app .ceovw-otp-box { width: 2.5rem !important; height: 3rem !important; font-size: 1.25rem !important; } 122 #ceovw-corporate-app .ceovw-modal-overlay { align-items: flex-end; padding: 0; } 123 #ceovw-corporate-app .ceovw-modal-content { max-width: none; border-radius: 18px 18px 0 0; padding: 1.75rem 1.1rem 1.1rem; } 124 #ceovw-corporate-app .ceovw-otp-inputs { gap: 6px; } 125 #ceovw-corporate-app .ceovw-otp-box { width: calc((100% - 30px) / 6) !important; max-width: 48px; height: 52px !important; font-size: 22px !important; } 122 126 } 127 @media (prefers-reduced-motion: reduce) { 128 #ceovw-corporate-app .ceovw-modal-content, #ceovw-corporate-app .btn-loader { animation: none; } 129 } -
naimur-email-otp-for-woocommerce/trunk/assets/js/main.js
r3780003 r3780739 2 2 "use strict"; 3 3 4 let currentFlow = ''; 4 const T = (window.CEOVW_APP && CEOVW_APP.i18n) || {}; 5 let currentFlow = ''; 5 6 let targetEmail = ''; 6 7 let resendTimer = null; 7 8 let resetToken = ''; // Password reset authorization: kept in memory only, never persisted or logged 9 let lastFocus = null; 10 let verifying = false; 11 12 // Inline form messages (replace browser alerts; announced by screen readers) 13 const formMsg = ($form, text, kind) => { 14 const $m = $form.find('.ceovw-form-msg'); 15 if (!text) { $m.prop('hidden', true).text('').removeClass('is-error is-success'); return; } 16 $m.text(text).prop('hidden', false).toggleClass('is-success', kind === 'success').toggleClass('is-error', kind !== 'success'); 17 }; 18 const showView = (target, focus) => { 19 $('.ceovw-view-pane').hide(); 20 const $v = $(`#ceovw-view-${target}`).fadeIn(300); 21 $v.find('.ceovw-form-msg').prop('hidden', true).text(''); 22 if (focus !== false) { setTimeout(() => $v.find('input:not([type=hidden]):visible').first().trigger('focus'), 320); } 23 return $v; 24 }; 25 const missing = ($form) => $form.find('input[required]').filter(function(){ return !$.trim(this.value); }).first(); 26 const validEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($.trim(v)); 8 27 9 28 // View Switcher (Smooth Fade) … … 12 31 const target = $(this).data('target'); 13 32 if (target !== 'newpass') resetToken = ''; 14 $('.ceovw-view-pane').hide(); 15 $(`#ceovw-view-${target}`).fadeIn(300); 33 showView(target); 16 34 }); 17 35 18 36 const setBtnLoading = ($btn, isLoading) => { 19 37 if(isLoading) { 20 $btn.prop('disabled', true).addClass('loading') ;38 $btn.prop('disabled', true).addClass('loading').attr('aria-busy', 'true'); 21 39 } else { 22 $btn.prop('disabled', false).removeClass('loading') ;40 $btn.prop('disabled', false).removeClass('loading').attr('aria-busy', 'false'); 23 41 } 24 42 }; 25 43 26 const getCaptchaToken = ($form) => { 27 if (CEOVW_APP.settings.captcha === 'turnstile') { 28 return $form.find('[name="cf-turnstile-response"]').val() || ''; 29 } else if (CEOVW_APP.settings.captcha === 'recaptcha') { 30 return $form.find('[name="g-recaptcha-response"]').val() || ''; 31 } 32 return ''; 44 // Resend delay: server value when provided, otherwise the per-flow setting 45 const resendDelay = (res) => { 46 const s = res && res.data ? parseInt(res.data.resend_in, 10) : 0; 47 const cfg = (CEOVW_APP.settings && CEOVW_APP.settings.resend) || {}; 48 return s > 0 ? s : (parseInt(cfg[currentFlow], 10) || 60); 33 49 }; 34 50 … … 39 55 let pass = $form.find('input[name="password"]').val(); 40 56 let confirm = $form.find('input[name="password_confirm"]').val(); 41 42 if (pass !== confirm) {43 alert("Passwords do not match!");44 return;45 }46 47 57 let email = $form.find('input[name="email"]').val(); 48 58 let name = $form.find('input[name="ceovw_full_name"]').val(); 59 const $empty = missing($form); 60 61 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 62 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="email"]').trigger('focus'); return; } 63 if (pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="password_confirm"]').trigger('focus'); return; } 64 formMsg($form, ''); 65 49 66 let $btn = $('#ceovw-btn-reg'); 50 51 67 setBtnLoading($btn, true); 52 68 … … 54 70 action: 'ceovw_process_registration_req', 55 71 nonce: CEOVW_APP.nonce, 56 email: email, name: name, password: pass, 57 captcha: getCaptchaToken($form) 72 email: email, name: name, password: pass 58 73 }, function(res){ 59 74 setBtnLoading($btn, false); … … 62 77 targetEmail = email; 63 78 resetToken = ''; 64 openOTPModal(email); // JSON Success হলে এই পপআপটি ওপেন হবে 65 } else { 66 alert(res.data.message || CEOVW_APP.i18n.error); 67 } 68 }).fail(function(xhr, status, error) { 69 setBtnLoading($btn, false); 70 console.error("AJAX Error:", error); 71 alert(CEOVW_APP.i18n.error); 79 openOTPModal(email, resendDelay(res)); 80 } else { 81 formMsg($form, (res.data && res.data.message) || T.error); 82 } 83 }).fail(function() { 84 setBtnLoading($btn, false); 85 formMsg($form, T.error); 72 86 }); 73 87 }); … … 78 92 let $form = $(this); 79 93 let email = $form.find('input[name="reset_email"]').val(); 94 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="reset_email"]').trigger('focus'); return; } 95 formMsg($form, ''); 80 96 let $btn = $('#ceovw-btn-forgot'); 81 97 resetToken = ''; … … 86 102 action: 'ceovw_process_forgot_req', 87 103 nonce: CEOVW_APP.nonce, 88 email: email, 89 captcha: getCaptchaToken($form) 104 email: email 90 105 }, function(res){ 91 106 setBtnLoading($btn, false); … … 93 108 currentFlow = 'forgot'; 94 109 targetEmail = email; 95 openOTPModal(email );96 } else { 97 alert(res.data.message || CEOVW_APP.i18n.error);98 } 99 }).fail(function() { 100 setBtnLoading($btn, false); 101 alert(CEOVW_APP.i18n.error);110 openOTPModal(email, resendDelay(res)); 111 } else { 112 formMsg($form, (res.data && res.data.message) || T.error); 113 } 114 }).fail(function() { 115 setBtnLoading($btn, false); 116 formMsg($form, T.error); 102 117 }); 103 118 }); … … 106 121 const $modal = $('#ceovw-otp-modal'); 107 122 const $otpInputs = $('.ceovw-otp-box'); 108 109 function openOTPModal(email) { 123 const $otpMsg = $('#ceovw-otp-error'); 124 const otpMessage = (text, kind) => { $otpMsg.text(text || '').toggleClass('is-success', kind === 'success'); }; 125 const code = () => $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 126 127 function openOTPModal(email, wait) { 128 lastFocus = document.activeElement; 110 129 $('#ceovw-target-email').text(email); 111 130 $otpInputs.val(''); 112 $('#ceovw-otp-error').text(''); 131 otpMessage(''); 132 verifying = false; 113 133 $modal.fadeIn(200); 114 $otpInputs.first(). focus();115 startResendTimer( 60);134 $otpInputs.first().trigger('focus'); 135 startResendTimer(wait || resendDelay()); 116 136 } 117 137 118 $modal.find('.ceovw-modal-close').on('click', () => $modal.fadeOut(200)); 138 function closeOTPModal() { 139 $modal.fadeOut(200); 140 if (lastFocus && document.body.contains(lastFocus)) { lastFocus.focus(); } 141 } 142 143 $modal.find('.ceovw-modal-close').on('click', closeOTPModal); 144 $modal.on('click', function(e){ if (e.target === this) { closeOTPModal(); } }); 145 146 // Keyboard: Escape closes, Tab stays inside the dialog 147 $modal.on('keydown', function(e){ 148 if (e.key === 'Escape') { e.preventDefault(); closeOTPModal(); return; } 149 if (e.key !== 'Tab') return; 150 const $f = $modal.find('button:not(:disabled), input').filter(':visible'); 151 if (!$f.length) return; 152 const first = $f[0], last = $f[$f.length - 1]; 153 if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } 154 else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } 155 }); 156 157 // Spread several digits (paste or one-time-code autofill) across the boxes starting at index `from` 158 const fill = (digits, from) => { 159 digits.split('').slice(0, 6 - from).forEach((d, i) => { $otpInputs.eq(from + i).val(d); }); 160 const next = Math.min(from + digits.length, 5); 161 $otpInputs.eq(next).trigger('focus'); 162 }; 163 const maybeSubmit = () => { if (code().length === 6) $('#ceovw-btn-verify').trigger('click'); }; 119 164 120 165 $otpInputs.on('input', function() { 121 this.value = this.value.replace(/\D/g, ''); 122 if(this.value && $(this).next('.ceovw-otp-box').length) { 123 $(this).next('.ceovw-otp-box').focus(); 166 const digits = this.value.replace(/\D/g, ''); 167 const i = $otpInputs.index(this); 168 if (digits.length > 1) { this.value = ''; fill(digits, digits.length >= 6 ? 0 : i); } 169 else { 170 this.value = digits; 171 if (digits && i < 5) { $otpInputs.eq(i + 1).trigger('focus'); } 124 172 } 125 let fullCode = $otpInputs.map(function(){ return $(this).val(); }).get().join('');126 if(fullCode.length === 6) $('#ceovw-btn-verify').click();173 if (digits) otpMessage(''); 174 maybeSubmit(); 127 175 }); 128 176 129 177 $otpInputs.on('keydown', function(e) { 130 if(e.key === "Backspace" && !this.value && $(this).prev('.ceovw-otp-box').length) { 131 $(this).prev('.ceovw-otp-box').focus().val(''); 132 } 133 }); 178 const i = $otpInputs.index(this); 179 if (e.key === "Backspace" && !this.value && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).val('').trigger('focus'); } 180 else if (e.key === 'ArrowLeft' && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).trigger('focus'); } 181 else if (e.key === 'ArrowRight' && i < 5) { e.preventDefault(); $otpInputs.eq(i + 1).trigger('focus'); } 182 else if (e.key === 'Enter') { e.preventDefault(); $('#ceovw-btn-verify').trigger('click'); } 183 }); 184 185 $otpInputs.on('focus', function(){ this.select(); }); 134 186 135 187 $otpInputs.on('paste', function(e) { 136 188 e.preventDefault(); 137 let pasteData = (e.originalEvent || e).clipboardData.getData('text').replace(/\D/g, "").slice(0, 6); 138 $otpInputs.each(function(i) { 139 if(pasteData[i]) $(this).val(pasteData[i]); 140 }); 141 if(pasteData.length === 6) $('#ceovw-btn-verify').click(); 189 const pasteData = ((e.originalEvent || e).clipboardData.getData('text') || '').replace(/\D/g, "").slice(0, 6); 190 if (!pasteData) return; 191 fill(pasteData, pasteData.length >= 6 ? 0 : $otpInputs.index(this)); 192 maybeSubmit(); 142 193 }); 143 194 144 195 // Verify OTP 145 196 $('#ceovw-btn-verify').on('click', function(){ 146 let code = $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 147 if(code.length !== 6) return; 197 const value = code(); 198 if (verifying) return; 199 if (value.length !== 6) { otpMessage(T.enterCode); $otpInputs.filter(function(){ return !this.value; }).first().trigger('focus'); return; } 148 200 149 201 let $btn = $(this); 202 verifying = true; 150 203 setBtnLoading($btn, true); 151 $('#ceovw-otp-error').text('');204 otpMessage(''); 152 205 153 206 $.post(CEOVW_APP.ajax_url, { 154 207 action: 'ceovw_verify_otp', 155 208 nonce: CEOVW_APP.nonce, 156 email: targetEmail, code: code, flow: currentFlow 157 }, function(res){ 158 if(res.success) { 159 $btn.find('.btn-text').text(CEOVW_APP.i18n.verified); 209 email: targetEmail, code: value, flow: currentFlow 210 }, function(res){ 211 if(res.success) { 212 $btn.find('.btn-text').text(T.verified); 213 otpMessage(T.verified, 'success'); 160 214 setTimeout(() => { 161 215 $modal.fadeOut(200); … … 164 218 } else if (currentFlow === 'forgot') { 165 219 resetToken = (res.data && res.data.reset_token) || ''; 166 $('.ceovw-view-pane').hide(); 167 $('#ceovw-view-newpass').fadeIn(300); 220 showView('newpass'); 168 221 $('#ceovw-verified-email').val(targetEmail); 169 222 setBtnLoading($btn, false); 170 $btn.find('.btn-text').text('Verify Code'); 223 $btn.find('.btn-text').text(T.verifyBtn); 224 verifying = false; 171 225 } 172 226 }, 1000); 173 227 } else { 228 verifying = false; 174 229 setBtnLoading($btn, false); 175 $('#ceovw-otp-error').text(res.data.message || CEOVW_APP.i18n.error);230 otpMessage((res.data && res.data.message) || T.error); 176 231 $otpInputs.val(''); 177 $otpInputs.first().focus(); 178 } 179 }).fail(function() { 180 setBtnLoading($btn, false); 181 $('#ceovw-otp-error').text(CEOVW_APP.i18n.error); 232 $otpInputs.first().trigger('focus'); 233 } 234 }).fail(function() { 235 verifying = false; 236 setBtnLoading($btn, false); 237 otpMessage(T.error); 182 238 }); 183 239 }); … … 189 245 let $txt = $('#ceovw-timer-text'); 190 246 $btn.prop('disabled', true); 191 247 $txt.text(`(${seconds}s)`); 248 192 249 resendTimer = setInterval(() => { 193 250 seconds--; … … 205 262 let $btn = $(this); 206 263 $btn.prop('disabled', true); 207 $('#ceovw-timer-text').text(' ...');208 264 $('#ceovw-timer-text').text('…'); 265 209 266 $.post(CEOVW_APP.ajax_url, { 210 267 action: currentFlow === 'register' ? 'ceovw_process_registration_req' : 'ceovw_process_forgot_req', … … 214 271 }, function(res){ 215 272 if(res.success) { 216 startResendTimer(60); 217 $('#ceovw-otp-error').text('New code sent!').css('color', 'green'); 218 setTimeout(()=> $('#ceovw-otp-error').text('').css('color', '#dc2626'), 3000); 219 } else { 220 alert(res.data.message); 221 $btn.prop('disabled', false); 222 } 223 }); 273 startResendTimer(resendDelay(res)); 274 otpMessage(T.codeSent, 'success'); 275 $otpInputs.val(''); $otpInputs.first().trigger('focus'); 276 } else { 277 otpMessage((res.data && res.data.message) || T.error); 278 if (res.data && parseInt(res.data.resend_in, 10) > 0) { startResendTimer(parseInt(res.data.resend_in, 10)); } 279 else { $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); } 280 } 281 }).fail(() => { otpMessage(T.error); $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); }); 224 282 }); 225 283 … … 230 288 let pass = $form.find('input[name="new_password"]').val(); 231 289 let confirm = $form.find('input[name="new_password_confirm"]').val(); 232 233 if(pass !== confirm) { alert("Passwords do not match."); return; } 234 290 const $empty = missing($form); 291 292 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 293 if(pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="new_password_confirm"]').trigger('focus'); return; } 294 formMsg($form, ''); 295 235 296 let $btn = $('#ceovw-btn-setpass'); 236 297 setBtnLoading($btn, true); … … 246 307 setBtnLoading($btn, false); 247 308 $form[0].reset(); 248 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);249 $ ('.ceovw-view-pane').hide();250 $('#ceovw-form-login input[name="username"]').val(targetEmail);251 $('#ceovw-view-login').fadeIn(300);309 const $login = showView('login', false); 310 $login.find('input[name="username"]').val(targetEmail); 311 formMsg($login.find('form'), (res.data && res.data.message) || T.updated, 'success'); 312 setTimeout(() => $login.find('input[name="password"]').trigger('focus'), 320); 252 313 } else { 253 314 setBtnLoading($btn, false); 254 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);255 315 if (res.data && res.data.code === 'reset_expired') { 256 316 // Authorization expired or already used: restart the forgot password flow 257 317 resetToken = ''; 258 318 $form[0].reset(); 259 $('.ceovw-view-pane').hide(); 260 $('#ceovw-view-forgot').fadeIn(300); 319 const $forgot = showView('forgot'); 320 formMsg($forgot.find('form'), (res.data && res.data.message) || T.error); 321 } else { 322 formMsg($form, (res.data && res.data.message) || T.error); 261 323 } 262 324 } 263 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 264 }); 265 325 }).fail(() => { setBtnLoading($btn, false); formMsg($form, T.error); }); 326 }); 266 327 })(jQuery); -
naimur-email-otp-for-woocommerce/trunk/assets/src/js/main.js
r3780003 r3780739 2 2 "use strict"; 3 3 4 let currentFlow = ''; 4 const T = (window.CEOVW_APP && CEOVW_APP.i18n) || {}; 5 let currentFlow = ''; 5 6 let targetEmail = ''; 6 7 let resendTimer = null; 7 8 let resetToken = ''; // Password reset authorization: kept in memory only, never persisted or logged 9 let lastFocus = null; 10 let verifying = false; 11 12 // Inline form messages (replace browser alerts; announced by screen readers) 13 const formMsg = ($form, text, kind) => { 14 const $m = $form.find('.ceovw-form-msg'); 15 if (!text) { $m.prop('hidden', true).text('').removeClass('is-error is-success'); return; } 16 $m.text(text).prop('hidden', false).toggleClass('is-success', kind === 'success').toggleClass('is-error', kind !== 'success'); 17 }; 18 const showView = (target, focus) => { 19 $('.ceovw-view-pane').hide(); 20 const $v = $(`#ceovw-view-${target}`).fadeIn(300); 21 $v.find('.ceovw-form-msg').prop('hidden', true).text(''); 22 if (focus !== false) { setTimeout(() => $v.find('input:not([type=hidden]):visible').first().trigger('focus'), 320); } 23 return $v; 24 }; 25 const missing = ($form) => $form.find('input[required]').filter(function(){ return !$.trim(this.value); }).first(); 26 const validEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($.trim(v)); 8 27 9 28 // View Switcher (Smooth Fade) … … 12 31 const target = $(this).data('target'); 13 32 if (target !== 'newpass') resetToken = ''; 14 $('.ceovw-view-pane').hide(); 15 $(`#ceovw-view-${target}`).fadeIn(300); 16 }); 17 18 // Helper: Button Loading State 33 showView(target); 34 }); 35 19 36 const setBtnLoading = ($btn, isLoading) => { 20 37 if(isLoading) { 21 $btn.prop('disabled', true).addClass('loading') ;38 $btn.prop('disabled', true).addClass('loading').attr('aria-busy', 'true'); 22 39 } else { 23 $btn.prop('disabled', false).removeClass('loading') ;40 $btn.prop('disabled', false).removeClass('loading').attr('aria-busy', 'false'); 24 41 } 25 42 }; 26 43 27 // Helper: Captcha Token 28 const getCaptchaToken = ($form) => { 29 if (CEOVW_APP.settings.captcha === 'turnstile') { 30 return $form.find('[name="cf-turnstile-response"]').val() || ''; 31 } else if (CEOVW_APP.settings.captcha === 'recaptcha') { 32 return $form.find('[name="g-recaptcha-response"]').val() || ''; 33 } 34 return ''; 35 }; 36 37 // Registration Form Submit 44 // Resend delay: server value when provided, otherwise the per-flow setting 45 const resendDelay = (res) => { 46 const s = res && res.data ? parseInt(res.data.resend_in, 10) : 0; 47 const cfg = (CEOVW_APP.settings && CEOVW_APP.settings.resend) || {}; 48 return s > 0 ? s : (parseInt(cfg[currentFlow], 10) || 60); 49 }; 50 51 // Registration Flow 38 52 $('#ceovw-form-register').on('submit', function(e){ 39 53 e.preventDefault(); … … 41 55 let pass = $form.find('input[name="password"]').val(); 42 56 let confirm = $form.find('input[name="password_confirm"]').val(); 43 44 if (pass !== confirm) {45 alert("Passwords do not match!");46 return;47 }48 49 57 let email = $form.find('input[name="email"]').val(); 50 58 let name = $form.find('input[name="ceovw_full_name"]').val(); 59 const $empty = missing($form); 60 61 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 62 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="email"]').trigger('focus'); return; } 63 if (pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="password_confirm"]').trigger('focus'); return; } 64 formMsg($form, ''); 65 51 66 let $btn = $('#ceovw-btn-reg'); 52 53 67 setBtnLoading($btn, true); 54 68 … … 56 70 action: 'ceovw_process_registration_req', 57 71 nonce: CEOVW_APP.nonce, 58 email: email, name: name, password: pass, 59 captcha: getCaptchaToken($form) 72 email: email, name: name, password: pass 60 73 }, function(res){ 61 74 setBtnLoading($btn, false); … … 64 77 targetEmail = email; 65 78 resetToken = ''; 66 openOTPModal(email); 67 } else { 68 alert(res.data.message || CEOVW_APP.i18n.error); 69 } 70 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 71 }); 72 73 // Forgot Password Form Submit 79 openOTPModal(email, resendDelay(res)); 80 } else { 81 formMsg($form, (res.data && res.data.message) || T.error); 82 } 83 }).fail(function() { 84 setBtnLoading($btn, false); 85 formMsg($form, T.error); 86 }); 87 }); 88 89 // Forgot Password Flow 74 90 $('#ceovw-form-forgot').on('submit', function(e){ 75 91 e.preventDefault(); 76 92 let $form = $(this); 77 93 let email = $form.find('input[name="reset_email"]').val(); 94 if (!validEmail(email)) { formMsg($form, T.badEmail); $form.find('input[name="reset_email"]').trigger('focus'); return; } 95 formMsg($form, ''); 78 96 let $btn = $('#ceovw-btn-forgot'); 79 97 resetToken = ''; … … 84 102 action: 'ceovw_process_forgot_req', 85 103 nonce: CEOVW_APP.nonce, 86 email: email, 87 captcha: getCaptchaToken($form) 104 email: email 88 105 }, function(res){ 89 106 setBtnLoading($btn, false); … … 91 108 currentFlow = 'forgot'; 92 109 targetEmail = email; 93 openOTPModal(email); 94 } else { 95 alert(res.data.message || CEOVW_APP.i18n.error); 96 } 97 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 98 }); 99 100 // OTP Modal Logic & Auto-tab 110 openOTPModal(email, resendDelay(res)); 111 } else { 112 formMsg($form, (res.data && res.data.message) || T.error); 113 } 114 }).fail(function() { 115 setBtnLoading($btn, false); 116 formMsg($form, T.error); 117 }); 118 }); 119 120 // OTP Modal Logic 101 121 const $modal = $('#ceovw-otp-modal'); 102 122 const $otpInputs = $('.ceovw-otp-box'); 103 104 function openOTPModal(email) { 123 const $otpMsg = $('#ceovw-otp-error'); 124 const otpMessage = (text, kind) => { $otpMsg.text(text || '').toggleClass('is-success', kind === 'success'); }; 125 const code = () => $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 126 127 function openOTPModal(email, wait) { 128 lastFocus = document.activeElement; 105 129 $('#ceovw-target-email').text(email); 106 130 $otpInputs.val(''); 107 $('#ceovw-otp-error').text(''); 131 otpMessage(''); 132 verifying = false; 108 133 $modal.fadeIn(200); 109 $otpInputs.first(). focus();110 startResendTimer( 60); // 60s cooldown start134 $otpInputs.first().trigger('focus'); 135 startResendTimer(wait || resendDelay()); 111 136 } 112 137 113 $modal.find('.ceovw-modal-close').on('click', () => $modal.fadeOut(200)); 138 function closeOTPModal() { 139 $modal.fadeOut(200); 140 if (lastFocus && document.body.contains(lastFocus)) { lastFocus.focus(); } 141 } 142 143 $modal.find('.ceovw-modal-close').on('click', closeOTPModal); 144 $modal.on('click', function(e){ if (e.target === this) { closeOTPModal(); } }); 145 146 // Keyboard: Escape closes, Tab stays inside the dialog 147 $modal.on('keydown', function(e){ 148 if (e.key === 'Escape') { e.preventDefault(); closeOTPModal(); return; } 149 if (e.key !== 'Tab') return; 150 const $f = $modal.find('button:not(:disabled), input').filter(':visible'); 151 if (!$f.length) return; 152 const first = $f[0], last = $f[$f.length - 1]; 153 if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } 154 else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } 155 }); 156 157 // Spread several digits (paste or one-time-code autofill) across the boxes starting at index `from` 158 const fill = (digits, from) => { 159 digits.split('').slice(0, 6 - from).forEach((d, i) => { $otpInputs.eq(from + i).val(d); }); 160 const next = Math.min(from + digits.length, 5); 161 $otpInputs.eq(next).trigger('focus'); 162 }; 163 const maybeSubmit = () => { if (code().length === 6) $('#ceovw-btn-verify').trigger('click'); }; 114 164 115 165 $otpInputs.on('input', function() { 116 this.value = this.value.replace(/\D/g, ''); 117 if(this.value && $(this).next('.ceovw-otp-box').length) { 118 $(this).next('.ceovw-otp-box').focus(); 166 const digits = this.value.replace(/\D/g, ''); 167 const i = $otpInputs.index(this); 168 if (digits.length > 1) { this.value = ''; fill(digits, digits.length >= 6 ? 0 : i); } 169 else { 170 this.value = digits; 171 if (digits && i < 5) { $otpInputs.eq(i + 1).trigger('focus'); } 119 172 } 120 let fullCode = $otpInputs.map(function(){ return $(this).val(); }).get().join('');121 if(fullCode.length === 6) $('#ceovw-btn-verify').click();173 if (digits) otpMessage(''); 174 maybeSubmit(); 122 175 }); 123 176 124 177 $otpInputs.on('keydown', function(e) { 125 if(e.key === "Backspace" && !this.value && $(this).prev('.ceovw-otp-box').length) { 126 $(this).prev('.ceovw-otp-box').focus().val(''); 127 } 128 }); 178 const i = $otpInputs.index(this); 179 if (e.key === "Backspace" && !this.value && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).val('').trigger('focus'); } 180 else if (e.key === 'ArrowLeft' && i > 0) { e.preventDefault(); $otpInputs.eq(i - 1).trigger('focus'); } 181 else if (e.key === 'ArrowRight' && i < 5) { e.preventDefault(); $otpInputs.eq(i + 1).trigger('focus'); } 182 else if (e.key === 'Enter') { e.preventDefault(); $('#ceovw-btn-verify').trigger('click'); } 183 }); 184 185 $otpInputs.on('focus', function(){ this.select(); }); 129 186 130 187 $otpInputs.on('paste', function(e) { 131 188 e.preventDefault(); 132 let pasteData = (e.originalEvent || e).clipboardData.getData('text').replace(/\D/g, "").slice(0, 6); 133 $otpInputs.each(function(i) { 134 if(pasteData[i]) $(this).val(pasteData[i]); 135 }); 136 if(pasteData.length === 6) $('#ceovw-btn-verify').click(); 137 }); 138 139 // Verify OTP Action 189 const pasteData = ((e.originalEvent || e).clipboardData.getData('text') || '').replace(/\D/g, "").slice(0, 6); 190 if (!pasteData) return; 191 fill(pasteData, pasteData.length >= 6 ? 0 : $otpInputs.index(this)); 192 maybeSubmit(); 193 }); 194 195 // Verify OTP 140 196 $('#ceovw-btn-verify').on('click', function(){ 141 let code = $otpInputs.map(function(){ return $(this).val(); }).get().join(''); 142 if(code.length !== 6) return; 197 const value = code(); 198 if (verifying) return; 199 if (value.length !== 6) { otpMessage(T.enterCode); $otpInputs.filter(function(){ return !this.value; }).first().trigger('focus'); return; } 143 200 144 201 let $btn = $(this); 202 verifying = true; 145 203 setBtnLoading($btn, true); 146 $('#ceovw-otp-error').text('');204 otpMessage(''); 147 205 148 206 $.post(CEOVW_APP.ajax_url, { 149 207 action: 'ceovw_verify_otp', 150 208 nonce: CEOVW_APP.nonce, 151 email: targetEmail, code: code, flow: currentFlow 152 }, function(res){ 153 if(res.success) { 154 $btn.find('.btn-text').text(CEOVW_APP.i18n.verified); 209 email: targetEmail, code: value, flow: currentFlow 210 }, function(res){ 211 if(res.success) { 212 $btn.find('.btn-text').text(T.verified); 213 otpMessage(T.verified, 'success'); 155 214 setTimeout(() => { 156 215 $modal.fadeOut(200); … … 159 218 } else if (currentFlow === 'forgot') { 160 219 resetToken = (res.data && res.data.reset_token) || ''; 161 $('.ceovw-view-pane').hide(); 162 $('#ceovw-view-newpass').fadeIn(300); 220 showView('newpass'); 163 221 $('#ceovw-verified-email').val(targetEmail); 164 222 setBtnLoading($btn, false); 165 $btn.find('.btn-text').text('Verify Code'); 223 $btn.find('.btn-text').text(T.verifyBtn); 224 verifying = false; 166 225 } 167 226 }, 1000); 168 227 } else { 228 verifying = false; 169 229 setBtnLoading($btn, false); 170 $('#ceovw-otp-error').text(res.data.message || CEOVW_APP.i18n.error);230 otpMessage((res.data && res.data.message) || T.error); 171 231 $otpInputs.val(''); 172 $otpInputs.first().focus(); 173 } 174 }).fail(() => { setBtnLoading($btn, false); $('#ceovw-otp-error').text(CEOVW_APP.i18n.error); }); 175 }); 176 177 // Timer Logic for Resend 232 $otpInputs.first().trigger('focus'); 233 } 234 }).fail(function() { 235 verifying = false; 236 setBtnLoading($btn, false); 237 otpMessage(T.error); 238 }); 239 }); 240 241 // Resend Timer 178 242 function startResendTimer(seconds) { 179 243 clearInterval(resendTimer); … … 181 245 let $txt = $('#ceovw-timer-text'); 182 246 $btn.prop('disabled', true); 183 247 $txt.text(`(${seconds}s)`); 248 184 249 resendTimer = setInterval(() => { 185 250 seconds--; … … 197 262 let $btn = $(this); 198 263 $btn.prop('disabled', true); 199 $('#ceovw-timer-text').text(' ...');200 264 $('#ceovw-timer-text').text('…'); 265 201 266 $.post(CEOVW_APP.ajax_url, { 202 267 action: currentFlow === 'register' ? 'ceovw_process_registration_req' : 'ceovw_process_forgot_req', … … 206 271 }, function(res){ 207 272 if(res.success) { 208 startResendTimer(60); 209 $('#ceovw-otp-error').text('New code sent!').css('color', 'green'); 210 setTimeout(()=> $('#ceovw-otp-error').text('').css('color', '#dc2626'), 3000); 211 } else { 212 alert(res.data.message); 213 $btn.prop('disabled', false); 214 } 215 }); 216 }); 217 218 // Set New Password Action 273 startResendTimer(resendDelay(res)); 274 otpMessage(T.codeSent, 'success'); 275 $otpInputs.val(''); $otpInputs.first().trigger('focus'); 276 } else { 277 otpMessage((res.data && res.data.message) || T.error); 278 if (res.data && parseInt(res.data.resend_in, 10) > 0) { startResendTimer(parseInt(res.data.resend_in, 10)); } 279 else { $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); } 280 } 281 }).fail(() => { otpMessage(T.error); $btn.prop('disabled', false); $('#ceovw-timer-text').text(''); }); 282 }); 283 284 // Set New Password 219 285 $('#ceovw-form-newpass').on('submit', function(e){ 220 286 e.preventDefault(); … … 222 288 let pass = $form.find('input[name="new_password"]').val(); 223 289 let confirm = $form.find('input[name="new_password_confirm"]').val(); 224 225 if(pass !== confirm) { alert("Passwords do not match."); return; } 226 290 const $empty = missing($form); 291 292 if ($empty.length) { formMsg($form, T.required); $empty.trigger('focus'); return; } 293 if(pass !== confirm) { formMsg($form, T.mismatch); $form.find('input[name="new_password_confirm"]').trigger('focus'); return; } 294 formMsg($form, ''); 295 227 296 let $btn = $('#ceovw-btn-setpass'); 228 297 setBtnLoading($btn, true); … … 238 307 setBtnLoading($btn, false); 239 308 $form[0].reset(); 240 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);241 $ ('.ceovw-view-pane').hide();242 $('#ceovw-form-login input[name="username"]').val(targetEmail);243 $('#ceovw-view-login').fadeIn(300);309 const $login = showView('login', false); 310 $login.find('input[name="username"]').val(targetEmail); 311 formMsg($login.find('form'), (res.data && res.data.message) || T.updated, 'success'); 312 setTimeout(() => $login.find('input[name="password"]').trigger('focus'), 320); 244 313 } else { 245 314 setBtnLoading($btn, false); 246 alert((res.data && res.data.message) || CEOVW_APP.i18n.error);247 315 if (res.data && res.data.code === 'reset_expired') { 248 316 // Authorization expired or already used: restart the forgot password flow 249 317 resetToken = ''; 250 318 $form[0].reset(); 251 $('.ceovw-view-pane').hide(); 252 $('#ceovw-view-forgot').fadeIn(300); 319 const $forgot = showView('forgot'); 320 formMsg($forgot.find('form'), (res.data && res.data.message) || T.error); 321 } else { 322 formMsg($form, (res.data && res.data.message) || T.error); 253 323 } 254 324 } 255 }).fail(() => { setBtnLoading($btn, false); alert(CEOVW_APP.i18n.error); }); 256 }); 257 325 }).fail(() => { setBtnLoading($btn, false); formMsg($form, T.error); }); 326 }); 258 327 })(jQuery); -
naimur-email-otp-for-woocommerce/trunk/includes/class-ajax.php
r3780003 r3780739 22 22 return ($ip === '::1' || $ip === '127.0.0.1') ? '127.0.0.1 (Localhost)' : $ip; 23 23 } 24 25 /** Coarse device label for activity logs (no full user agent is stored). */ 26 public static function device_label() { return self::get_user_device(); } 24 27 25 28 private static function get_user_device() { … … 78 81 $is_resend = ! empty( $_POST['is_resend'] ); 79 82 83 if ( ! CEOVW_Options::get( 'registration_enabled' ) ) { wp_send_json_error(['message' => __( 'Registration is not available.', 'naimur-email-otp-for-woocommerce' )]); } 80 84 if ( ! is_email( $email ) ) { wp_send_json_error(['message' => 'Invalid email address.']); } 81 85 if ( email_exists( $email ) ) { wp_send_json_error(['message' => 'An account is already registered with your email address.']); } 82 86 if ( CEOVW_Security::is_rate_limited( 'send' ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } 83 87 if ( ! CEOVW_Security::can_request_otp( $email ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } 88 $wait = CEOVW_Security::resend_wait( $email, 'register' ); 89 /* translators: %d: seconds */ 90 if ( $wait > 0 ) { wp_send_json_error(['message' => sprintf( __( 'Please wait %d seconds before requesting a new code.', 'naimur-email-otp-for-woocommerce' ), $wait ), 'resend_in' => $wait]); } 84 91 85 92 if ( $is_resend && $password === '' ) { … … 101 108 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Success', 'message' => 'Registration OTP sent via email.']); 102 109 CEOVW_Security::record_otp_request($email); 103 wp_send_json_success(['message' => 'OTP sent successfully.' ]);110 wp_send_json_success(['message' => 'OTP sent successfully.', 'resend_in' => CEOVW_Options::resend_seconds( 'register' )]); 104 111 } else { 105 CEOVW_Security::clear_ otp( $email);112 CEOVW_Security::clear_flow( $email, 'register' ); 106 113 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Failed', 'message' => 'Server failed to send email.']); 107 114 wp_send_json_error(['message' => 'Failed to send email. Check SMTP settings.']); … … 117 124 $generic = 'If an account exists for this email, a verification code has been sent.'; 118 125 126 if ( ! CEOVW_Options::get( 'forgot_enabled' ) ) { wp_send_json_error(['message' => __( 'Password reset by code is not available. Please use the standard lost password link.', 'naimur-email-otp-for-woocommerce' )]); } 119 127 if ( ! is_email( $email ) ) { wp_send_json_error(['message' => 'Please enter a valid email address.']); } 120 128 if ( CEOVW_Security::is_rate_limited( 'send' ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } … … 131 139 } 132 140 141 // Server-side resend cooldown. The response stays identical, so the cooldown cannot reveal whether an account exists. 142 if ( CEOVW_Security::resend_wait( $email, 'forgot' ) > 0 ) { 143 self::clean_buffer(); 144 wp_send_json_success(['message' => $generic]); 145 } 146 133 147 $otp = CEOVW_Security::generate_and_save_otp( $email, 'forgot' ); 134 $sent = $otp !== false && CEOVW_Emails::send_otp_email( $email, $otp, 'forgot' );148 $sent = $otp !== false && CEOVW_Emails::send_otp_email( $email, $otp, 'forgot', $user->display_name ); 135 149 136 150 self::clean_buffer(); … … 139 153 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Success', 'message' => 'Password reset OTP sent.']); 140 154 } else { 141 CEOVW_Security::clear_ otp( $email);155 CEOVW_Security::clear_flow( $email, 'forgot' ); 142 156 CEOVW_Logs::push_email_log(['time' => time(), 'email' => $email, 'status' => 'Failed', 'message' => 'Server failed to send email.']); 143 157 } … … 154 168 $code = sanitize_text_field( $_POST['code'] ?? '' ); 155 169 $flow = sanitize_text_field( $_POST['flow'] ?? '' ); 170 171 // This endpoint serves the My Account flows only; checkout has its own session-bound endpoint. 172 if ( ! in_array( $flow, ['register', 'forgot'], true ) ) { wp_send_json_error(['message' => 'Invalid code. Please try again.']); } 173 if ( ( 'register' === $flow && ! CEOVW_Options::get( 'registration_enabled' ) ) || ( 'forgot' === $flow && ! CEOVW_Options::get( 'forgot_enabled' ) ) ) { 174 wp_send_json_error(['message' => __( 'This verification is not available.', 'naimur-email-otp-for-woocommerce' )]); 175 } 156 176 157 177 $row = CEOVW_Security::verify_otp_code( $email, $code, $flow ); … … 175 195 CEOVW_Users::auto_login( $user_id ); 176 196 CEOVW_Security::clear_otp( $email ); 177 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user_id, 'email' => $email, 'ip' => $real_ip, 'device' => $device ]);197 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user_id, 'email' => $email, 'ip' => $real_ip, 'device' => $device, 'event' => 'registration_verified']); 178 198 wp_send_json_success(['message' => 'Account created and verified.']); 179 199 } else { … … 188 208 189 209 $user = get_user_by( 'email', $email ); 190 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user ? $user->ID : 0, 'email' => $email, 'ip' => $real_ip, 'device' => $device ]);210 CEOVW_Logs::push_activity_log(['time' => time(), 'user_id' => $user ? $user->ID : 0, 'email' => $email, 'ip' => $real_ip, 'device' => $device, 'event' => 'reset_verified']); 191 211 // The raw token is only returned to this browser flow; the database stores its SHA-256 hash. 192 212 wp_send_json_success(['message' => 'Code verified.', 'reset_token' => $token]); … … 199 219 200 220 $expired = ['message' => 'Your password reset session has expired. Please request a new code.', 'code' => 'reset_expired']; 221 if ( ! CEOVW_Options::get( 'forgot_enabled' ) ) { wp_send_json_error( $expired ); } 201 222 202 223 if ( CEOVW_Security::is_rate_limited( 'reset' ) ) { wp_send_json_error(['message' => 'Too many requests. Please try again later.']); } -
naimur-email-otp-for-woocommerce/trunk/includes/class-emails.php
r3587442 r3780739 4 4 class CEOVW_Emails { 5 5 6 /** Internal flow name -> template type. */ 7 public static function type_for_flow( $flow ) { 8 $map = array( 'register' => 'registration', 'forgot' => 'forgot', 'checkout' => 'checkout' ); 9 return isset( $map[ $flow ] ) ? $map[ $flow ] : 'registration'; 10 } 11 12 /** 13 * Sends an OTP email using the stored (sanitized) template for the flow. 14 * Templates are stored as HTML in the database; no template file is ever built from a setting value. 15 * Uses wp_mail(), so SMTP plugins handle delivery. 16 */ 6 17 public static function send_otp_email( $email, $otp, $flow, $name = '' ) { 7 $subject = get_option('naimur-email-otp-for-woocommerce_email_subject', 'Your Verification Code'); 8 $template_id = get_option('naimur-email-otp-for-woocommerce_email_template', '1'); 9 10 $message = ''; 11 if ( $template_id === 'custom' ) { 12 $message = str_replace( '[naimur_email_otp_code]', $otp, get_option('naimur-email-otp-for-woocommerce_custom_email_html', 'Your code is [naimur_email_otp_code]') ); 13 } else { 14 // Include your default template files here 15 ob_start(); 16 include NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH . "templates/email-template-$template_id.php"; 17 $message = ob_get_clean(); 18 $message = str_replace( '[naimur_email_otp_code]', $otp, $message ); 18 $type = self::type_for_flow( $flow ); 19 $out = CEOVW_Templates::render( $type, array( 20 'customer_name' => $name, 21 'customer_email' => $email, 22 'otp_code' => $otp, 23 'expiry_minutes' => (int) ceil( CEOVW_Options::ttl( $flow ) / 60 ), 24 ) ); 25 return wp_mail( $email, $out['subject'], $out['html'], self::headers() ); 26 } 27 28 /** Test email from the admin (sample code, never a real OTP). */ 29 public static function send_test( $type, $to, $override = null ) { 30 $out = CEOVW_Templates::render( $type, CEOVW_Templates::sample_vars( $type ), $override ); 31 $ok = wp_mail( $to, '[' . __( 'Test', 'naimur-email-otp-for-woocommerce' ) . '] ' . $out['subject'], $out['html'], self::headers() ); 32 if ( $ok ) { update_option( 'ceovw_test_email_sent_at', time(), false ); } 33 return $ok; 34 } 35 36 /** Headers for this plugin's emails only (no global From filter, so other plugins' emails are unaffected). */ 37 public static function headers() { 38 $h = array( 'Content-Type: text/html; charset=UTF-8' ); 39 $from = CEOVW_Options::get( 'sender_email' ); 40 if ( $from ) { 41 $name = str_replace( array( "\r", "\n", '"', '<', '>' ), '', (string) CEOVW_Options::get( 'sender_name' ) ); 42 $h[] = 'From: ' . ( '' !== $name ? '"' . $name . '" ' : '' ) . '<' . $from . '>'; 19 43 } 20 21 return wp_mail( $email, $subject, $message, ['Content-Type: text/html; charset=UTF-8'] ); 44 return $h; 22 45 } 23 46 } -
naimur-email-otp-for-woocommerce/trunk/includes/class-logs.php
r3780003 r3780739 7 7 const EMAIL_T_TTL = 7; // days 8 8 9 public static function init(){ add_action('admin_menu', [__CLASS__, 'menu']); } 10 11 public static function menu(){ 12 add_submenu_page('woocommerce', esc_html__('Email/Activity Logs', 'naimur-email-otp-for-woocommerce'), esc_html__('Email Verification Logs', 'naimur-email-otp-for-woocommerce'), 'manage_woocommerce', 'naimur-email-otp-for-woocommerce-logs', [__CLASS__, 'render']); 13 } 14 9 // Logs are shown on the "Activity & Diagnostics" admin page (includes/admin/views/diagnostics.php). 10 15 11 public static function get_email_logs(){ $logs = get_transient(self::EMAIL_T_KEY); return is_array($logs)?$logs:[]; } 16 12 … … 41 37 update_option(self::ACTIVITY_KEY, $logs, false); 42 38 } 43 44 public static function render(){45 if (isset($_POST['ceovw_clear_logs']) && check_admin_referer('ceovw_logs')){46 if ($_POST['ceovw_clear_logs'] === 'email') { delete_transient(self::EMAIL_T_KEY); }47 if ($_POST['ceovw_clear_logs'] === 'activity') { update_option(self::ACTIVITY_KEY, [] ); }48 echo '<div class="updated"><p>'.esc_html__('Logs cleared.', 'naimur-email-otp-for-woocommerce').'</p></div>';49 }50 51 $email_logs = self::get_email_logs();52 $activity = self::get_activity_logs();53 ?>54 55 <div class="wrap">56 <h1><?php esc_html_e('Email & Activity Logs', 'naimur-email-otp-for-woocommerce'); ?></h1>57 58 <h2><?php esc_html_e('Last Sent OTP Emails', 'naimur-email-otp-for-woocommerce'); ?></h2>59 <form method="post">60 <?php wp_nonce_field('ceovw_logs'); ?>61 <input type="hidden" name="ceovw_clear_logs" value="email" />62 <button class="button"><?php esc_html_e('Clear Email Logs','naimur-email-otp-for-woocommerce'); ?></button>63 </form>64 <table class="widefat striped">65 <thead>66 <tr>67 <th><?php esc_html_e('Time','naimur-email-otp-for-woocommerce'); ?></th>68 <th><?php esc_html_e('Email','naimur-email-otp-for-woocommerce'); ?></th>69 <th><?php esc_html_e('Code','naimur-email-otp-for-woocommerce'); ?></th>70 <th><?php esc_html_e('Status','naimur-email-otp-for-woocommerce'); ?></th>71 <th><?php esc_html_e('Message','naimur-email-otp-for-woocommerce'); ?></th>72 </tr>73 </thead>74 <tbody>75 <?php if (empty($email_logs)): ?>76 <tr><td colspan="5"><?php esc_html_e('No logs yet.','naimur-email-otp-for-woocommerce'); ?></td></tr>77 <?php else: foreach (array_reverse($email_logs) as $row): ?>78 <tr>79 <td><?php echo esc_html( date_i18n( get_option('date_format').' '.get_option('time_format'), $row['time'] ?? time() ) ); ?></td>80 <td><?php echo esc_html(ceovw_mask_email($row['email'] ?? '')); ?></td>81 <td><code>••••••</code></td>82 <td><?php echo esc_html($row['status'] ?? ''); ?></td>83 <td><?php echo esc_html($row['message'] ?? ''); ?></td>84 </tr>85 <?php endforeach; endif; ?>86 </tbody>87 </table>88 89 <h2 style="margin-top:28px;"><?php esc_html_e('Activity (Who verified when)','naimur-email-otp-for-woocommerce'); ?></h2>90 <form method="post">91 <?php wp_nonce_field('ceovw_logs'); ?>92 <input type="hidden" name="ceovw_clear_logs" value="activity" />93 <button class="button"><?php esc_html_e('Clear Activity Logs','naimur-email-otp-for-woocommerce'); ?></button>94 </form>95 <table class="widefat striped">96 <thead>97 <tr>98 <th><?php esc_html_e('Time','naimur-email-otp-for-woocommerce'); ?></th>99 <th><?php esc_html_e('User ID','naimur-email-otp-for-woocommerce'); ?></th>100 <th><?php esc_html_e('Email','naimur-email-otp-for-woocommerce'); ?></th>101 <th><?php esc_html_e('Device','naimur-email-otp-for-woocommerce'); ?></th>102 <th><?php esc_html_e('IP','naimur-email-otp-for-woocommerce'); ?></th>103 </tr>104 </thead>105 <tbody>106 <?php if (empty($activity)): ?>107 <tr><td colspan="5"><?php esc_html_e('No activity yet.','naimur-email-otp-for-woocommerce'); ?></td></tr>108 <?php else: foreach (array_reverse($activity) as $row): ?>109 <tr>110 <td><?php echo esc_html( date_i18n( get_option('date_format').' '.get_option('time_format'), $row['time'] ?? time() ) ); ?></td>111 <td><?php echo esc_html($row['user_id'] ?? ''); ?></td>112 <td><?php echo esc_html(ceovw_mask_email($row['email'] ?? '')); ?></td>113 <td><strong><?php echo esc_html($row['device'] ?? '-'); ?></strong></td>114 <td><?php echo esc_html($row['ip'] ?? ''); ?></td>115 </tr>116 <?php endforeach; endif; ?>117 </tbody>118 </table>119 </div>120 <?php121 }122 39 } 123 CEOVW_Logs::init(); -
naimur-email-otp-for-woocommerce/trunk/includes/class-security.php
r3780003 r3780739 4 4 class CEOVW_Security { 5 5 6 const MAX_ATTEMPTS = 5; 6 const MAX_ATTEMPTS = 5; // default; per-flow value comes from CEOVW_Options (range 3-10) 7 7 8 8 private static function table() { … … 18 18 * @return string|false The plain OTP (for the email only), or false on failure. 19 19 */ 20 public static function generate_and_save_otp( $email, $flow, $password = '', $name = '', $temp_data = null ) {20 public static function generate_and_save_otp( $email, $flow, $password = '', $name = '', $temp_data = null, $context_hash = null ) { 21 21 global $wpdb; 22 22 $table = self::table(); … … 32 32 } 33 33 34 $wpdb->delete( $table, ['email' => $email] ); 34 // Replace only this email's pending row for the same flow (and, for checkout, the same session), 35 // so a new code for one flow can never cancel another flow's authorization. 36 if ( null !== $context_hash ) { 37 $wpdb->delete( $table, ['email' => $email, 'flow' => $flow, 'context_hash' => $context_hash] ); 38 } else { 39 $wpdb->delete( $table, ['email' => $email, 'flow' => $flow] ); 40 } 35 41 36 42 $inserted = $wpdb->insert( $table, [ … … 40 46 'payload' => $payload, 41 47 'attempts' => 0, 42 'token_hash' => null, 43 'expires_at' => time() + ceovw_otp_ttl(), 44 'created_at' => current_time( 'mysql' ) 48 'token_hash' => null, 49 'expires_at' => time() + ceovw_otp_ttl( $flow ), 50 'sent_at' => time(), 51 'context_hash' => $context_hash, 52 'created_at' => current_time( 'mysql' ) 45 53 ]); 46 54 … … 56 64 * @return object|WP_Error The consumed row (with payload) on success. 57 65 */ 58 public static function verify_otp_code( $email, $code, $flow ) {66 public static function verify_otp_code( $email, $code, $flow, $context_hash = null ) { 59 67 global $wpdb; 60 68 $table = self::table(); 61 69 $now = time(); 62 63 if ( ! in_array( $flow, ['register', 'forgot'], true ) || ! preg_match( '/^\d{6}$/', $code ) ) { 70 $max = class_exists( 'CEOVW_Options' ) ? CEOVW_Options::max_attempts( $flow ) : self::MAX_ATTEMPTS; 71 72 if ( ! in_array( $flow, ['register', 'forgot', 'checkout'], true ) || ! is_string( $code ) || ! preg_match( '/^\d{6}$/', $code ) ) { 64 73 return new WP_Error( 'invalid', 'Invalid code. Please try again.' ); 65 74 } 66 67 $row = $wpdb->get_row( $wpdb->prepare( 68 "SELECT * FROM $table WHERE email = %s AND flow = %s AND token_hash IS NULL AND otp <> '' ORDER BY id DESC LIMIT 1", 69 $email, $flow 70 ) ); 75 // Checkout codes are bound to the WooCommerce session that requested them. 76 if ( 'checkout' === $flow && ( ! is_string( $context_hash ) || '' === $context_hash ) ) { 77 return new WP_Error( 'invalid', 'Invalid code. Please try again.' ); 78 } 79 80 $row = 'checkout' === $flow 81 ? $wpdb->get_row( $wpdb->prepare( 82 "SELECT * FROM $table WHERE email = %s AND flow = %s AND context_hash = %s AND token_hash IS NULL AND otp <> '' ORDER BY id DESC LIMIT 1", 83 $email, $flow, $context_hash ) ) 84 : $wpdb->get_row( $wpdb->prepare( 85 "SELECT * FROM $table WHERE email = %s AND flow = %s AND token_hash IS NULL AND otp <> '' ORDER BY id DESC LIMIT 1", 86 $email, $flow ) ); 71 87 72 88 if ( ! $row ) { … … 82 98 $reserved = $wpdb->query( $wpdb->prepare( 83 99 "UPDATE $table SET attempts = attempts + 1 WHERE id = %d AND token_hash IS NULL AND attempts < %d AND expires_at > %d", 84 $row->id, self::MAX_ATTEMPTS, $now100 $row->id, $max, $now 85 101 ) ); 86 102 … … 88 104 // so a parallel request that already issued a reset authorization is never undone. 89 105 if ( ! $reserved ) { 90 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, self::MAX_ATTEMPTS) );106 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, $max ) ); 91 107 return new WP_Error( 'locked', 'Too many incorrect attempts. Please request a new code.' ); 92 108 } … … 96 112 if ( ! hash_equals( (string) $row->otp, $expected ) ) { 97 113 // Invalidate the OTP once the attempt limit is reached. 98 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, self::MAX_ATTEMPTS) );114 $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE id = %d AND token_hash IS NULL AND attempts >= %d", $row->id, $max ) ); 99 115 return new WP_Error( 'invalid', 'Invalid code. Please try again.' ); 100 116 } … … 162 178 } 163 179 180 /** 181 * Seconds until another code may be emailed for this email/flow (server-side resend cooldown). 0 = allowed now. 182 * Only pending codes count; an issued authorization (token_hash set) does not block a new request. 183 */ 184 public static function resend_wait( $email, $flow, $context_hash = null ) { 185 global $wpdb; 186 $table = self::table(); 187 $sent = null !== $context_hash 188 ? (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX(sent_at) FROM $table WHERE email = %s AND flow = %s AND context_hash = %s AND token_hash IS NULL", $email, $flow, $context_hash ) ) 189 : (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX(sent_at) FROM $table WHERE email = %s AND flow = %s AND token_hash IS NULL", $email, $flow ) ); 190 $cool = class_exists( 'CEOVW_Options' ) ? CEOVW_Options::resend_seconds( $flow ) : 60; 191 return $sent ? max( 0, $sent + $cool - time() ) : 0; 192 } 193 194 /** 195 * Checkout: converts a verified checkout OTP into a single-use, short-lived authorization bound to the 196 * same email and WooCommerce session. Separate from password reset (flow = 'checkout'). 197 * Returns the raw token (kept only in the server-side WooCommerce session, never sent to the browser). 198 */ 199 public static function issue_checkout_auth( $row, $context_hash ) { 200 global $wpdb; 201 $table = self::table(); 202 $token = bin2hex( random_bytes( 32 ) ); 203 $ttl = max( 60, (int) apply_filters( 'ceovw_checkout_auth_ttl', 30 * MINUTE_IN_SECONDS ) ); 204 $updated = $wpdb->query( $wpdb->prepare( 205 "UPDATE $table SET otp = '', attempts = 0, token_hash = %s, expires_at = %d 206 WHERE id = %d AND flow = 'checkout' AND context_hash = %s AND otp = %s AND token_hash IS NULL AND expires_at > %d", 207 hash( 'sha256', $token ), time() + $ttl, $row->id, $context_hash, $row->otp, time() 208 ) ); 209 return $updated ? $token : false; 210 } 211 212 /** Non-consuming check: valid checkout authorization for this exact email + session. */ 213 public static function find_checkout_auth( $email, $context_hash, $token ) { 214 global $wpdb; 215 if ( ! is_string( $token ) || ! preg_match( '/^[a-f0-9]{64}$/', $token ) || ! $context_hash ) { return null; } 216 $table = self::table(); 217 return $wpdb->get_row( $wpdb->prepare( 218 "SELECT id, email, token_hash FROM $table WHERE token_hash = %s AND flow = 'checkout' AND email = %s AND context_hash = %s AND expires_at > %d LIMIT 1", 219 hash( 'sha256', $token ), $email, $context_hash, time() 220 ) ); 221 } 222 223 /** Consumes a checkout authorization exactly once (atomic DELETE). */ 224 public static function consume_checkout_auth( $email, $context_hash, $token ) { 225 global $wpdb; 226 if ( ! is_string( $token ) || ! preg_match( '/^[a-f0-9]{64}$/', $token ) || ! $context_hash ) { return false; } 227 $table = self::table(); 228 return (bool) $wpdb->query( $wpdb->prepare( 229 "DELETE FROM $table WHERE token_hash = %s AND flow = 'checkout' AND email = %s AND context_hash = %s AND expires_at > %d", 230 hash( 'sha256', $token ), $email, $context_hash, time() 231 ) ); 232 } 233 164 234 public static function can_request_otp( $email ) { 165 235 global $wpdb; … … 168 238 169 239 $count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $table WHERE email = %s AND action_type = 'otp_request' AND created_at > %s", $email, $twenty_four_hours_ago) ); 170 return $count < 5; 240 $limit = class_exists( 'CEOVW_Options' ) ? (int) CEOVW_Options::get( 'rl_email_per_day' ) : 5; 241 return $count < $limit; 171 242 } 172 243 … … 193 264 $table = $wpdb->prefix . 'ceovw_logs'; 194 265 266 $o = class_exists( 'CEOVW_Options' ); 195 267 $defaults = [ 196 'send' => ['limit' => 10, 'window' => HOUR_IN_SECONDS],197 'verify' => ['limit' => 30, 'window' => 15 * MINUTE_IN_SECONDS],198 'reset' => ['limit' => 10, 'window' => 15 * MINUTE_IN_SECONDS],268 'send' => ['limit' => $o ? (int) CEOVW_Options::get( 'rl_send_per_hour' ) : 10, 'window' => HOUR_IN_SECONDS], 269 'verify' => ['limit' => $o ? (int) CEOVW_Options::get( 'rl_verify_per_15min' ) : 30, 'window' => 15 * MINUTE_IN_SECONDS], 270 'reset' => ['limit' => $o ? (int) CEOVW_Options::get( 'rl_reset_per_15min' ) : 10, 'window' => 15 * MINUTE_IN_SECONDS], 199 271 ]; 200 272 if ( ! isset( $defaults[ $action ] ) ) { return false; } … … 242 314 } 243 315 316 /** Removes this email's rows for one flow only. */ 317 public static function clear_flow( $email, $flow ) { 318 global $wpdb; 319 $wpdb->delete( self::table(), ['email' => $email, 'flow' => $flow] ); 320 } 321 244 322 public static function clear_otp( $email ) { 245 323 global $wpdb; -
naimur-email-otp-for-woocommerce/trunk/includes/class-settings.php
r3780003 r3780739 2 2 if (!defined('ABSPATH')) { exit; } 3 3 4 /** 5 * Settings now live in CEOVW_Options and the "Naimur OTP" admin pages (includes/admin). 6 * This class only keeps the legacy [naimur_email_otp_code] shortcode registered so content 7 * written for versions before 2.1.2 does not show a raw shortcode. 8 */ 4 9 class CEOVW_Settings { 5 10 public static function init(){ 6 add_action('admin_menu', [__CLASS__, 'menu']);7 add_action('admin_init', [__CLASS__, 'register']);8 11 add_shortcode('naimur_email_otp_code', [__CLASS__, 'shortcode_code']); 9 add_action('wp_ajax_ceovw_preview_template', [__CLASS__, 'preview_template']);10 }11 12 public static function menu(){13 // Main Settings Page14 add_submenu_page('woocommerce', esc_html__('Email Verification', 'naimur-email-otp-for-woocommerce'), esc_html__('Email Verification', 'naimur-email-otp-for-woocommerce'), 'manage_woocommerce', 'naimur-email-otp-for-woocommerce-settings', [__CLASS__, 'render']);15 16 // Help, Docs & Support Page17 add_submenu_page('woocommerce', esc_html__('OTP Help & Docs', 'naimur-email-otp-for-woocommerce'), esc_html__('OTP Help & Docs', 'naimur-email-otp-for-woocommerce'), 'manage_woocommerce', 'naimur-email-otp-for-woocommerce-help', [__CLASS__, 'render_help_page']);18 }19 20 public static function register(){21 $sanitize_text = 'sanitize_text_field';22 23 // Email Template Settings24 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_email_subject', ['sanitize_callback' => $sanitize_text]);25 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_email_template', ['sanitize_callback' => $sanitize_text]);26 27 // Custom Email HTML with capability check (allows <style>, <html> tags for admins)28 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_custom_email_html', [29 'sanitize_callback' => function($html) {30 return current_user_can('unfiltered_html') ? $html : wp_kses_post($html);31 }32 ]);33 34 // Design Settings35 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_brand_color', ['sanitize_callback' => 'sanitize_hex_color']);36 37 // OTP Logic Settings38 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_code_length', ['sanitize_callback' => 'absint']);39 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_resend_cooldown', ['sanitize_callback' => 'absint']);40 41 // Override Settings42 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_override_enabled', ['sanitize_callback' => 'rest_sanitize_boolean']);43 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_myaccount_page_id', ['sanitize_callback' => 'absint']);44 45 // Captcha Settings46 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_captcha_type', ['sanitize_callback' => $sanitize_text]);47 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_recaptcha_site_key', ['sanitize_callback' => $sanitize_text]);48 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_recaptcha_secret_key', ['sanitize_callback' => $sanitize_text]);49 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_turnstile_site_key', ['sanitize_callback' => $sanitize_text]);50 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_turnstile_secret_key', ['sanitize_callback' => $sanitize_text]);51 52 // Form Header Settings53 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_login_header_html', ['sanitize_callback' => 'wp_kses_post']);54 register_setting('naimur-email-otp-for-woocommerce', 'naimur-email-otp-for-woocommerce_register_header_html', ['sanitize_callback' => 'wp_kses_post']);55 }56 57 public static function render(){58 if ( ! current_user_can( 'manage_woocommerce' ) ) { return; }59 ?>60 <div class="wrap">61 <h1><?php /* translators: %s: plugin version */ echo esc_html( sprintf( __('Customer Email OTP Verification for WooCommerce (v%s)', 'naimur-email-otp-for-woocommerce'), NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION ) ); ?></h1>62 <form method="post" action="options.php">63 <?php settings_fields('naimur-email-otp-for-woocommerce'); ?>64 <table class="form-table" role="presentation">65 <!-- Email Template Section -->66 <tr>67 <th><label for="naimur-email-otp-for-woocommerce_email_subject"><?php esc_html_e('Email Subject', 'naimur-email-otp-for-woocommerce'); ?></label></th>68 <td>69 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_email_subject" id="naimur-email-otp-for-woocommerce_email_subject" value="<?php echo esc_attr(get_option('naimur-email-otp-for-woocommerce_email_subject', 'Your Verification Code')); ?>" />70 </td>71 </tr>72 <tr>73 <th><label for="naimur-email-otp-for-woocommerce_email_template"><?php esc_html_e('Select Email Template', 'naimur-email-otp-for-woocommerce'); ?></label></th>74 <td>75 <select name="naimur-email-otp-for-woocommerce_email_template" id="naimur-email-otp-for-woocommerce_email_template">76 <option value="1" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template', '1'), '1'); ?>><?php esc_html_e('Template 1 (Simple)', 'naimur-email-otp-for-woocommerce'); ?></option>77 <option value="2" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template'), '2'); ?>><?php esc_html_e('Template 2 (Modern)', 'naimur-email-otp-for-woocommerce'); ?></option>78 <option value="3" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template'), '3'); ?>><?php esc_html_e('Template 3 (Classic)', 'naimur-email-otp-for-woocommerce'); ?></option>79 <option value="custom" <?php selected(get_option('naimur-email-otp-for-woocommerce_email_template'), 'custom'); ?>><?php esc_html_e('Custom HTML', 'naimur-email-otp-for-woocommerce'); ?></option>80 </select>81 </td>82 </tr>83 84 <!-- Live Gutenberg-like HTML Editor -->85 <tr id="ceovw_custom_html_row" style="display: none;">86 <th><label for="naimur-email-otp-for-woocommerce_custom_email_html"><?php esc_html_e('Write Custom HTML/CSS', 'naimur-email-otp-for-woocommerce'); ?></label></th>87 <td>88 <textarea name="naimur-email-otp-for-woocommerce_custom_email_html" id="naimur-email-otp-for-woocommerce_custom_email_html" rows="12" class="large-text code" placeholder="<html><body><h1>Your Code: [naimur_email_otp_code]</h1></body></html>"><?php echo esc_textarea(get_option('naimur-email-otp-for-woocommerce_custom_email_html', '')); ?></textarea>89 <p class="description"><?php esc_html_e('Use the shortcode [naimur_email_otp_code] where you want the 6-digit OTP to appear. Changes will preview live below.', 'naimur-email-otp-for-woocommerce'); ?></p>90 </td>91 </tr>92 93 <tr>94 <th><?php esc_html_e('Live Visual Preview', 'naimur-email-otp-for-woocommerce'); ?></th>95 <td>96 <div id="ceovw_template_preview_container" style="border: 1px solid #dcdcdc; border-radius: 8px; padding: 15px; background: #f9f9f9; max-width: 600px; min-height: 200px; box-shadow: inset 0 0 10px rgba(0,0,0,0.02);">97 <!-- Template Preview will be loaded here dynamically -->98 </div>99 </td>100 </tr>101 102 <!-- Design Section -->103 <tr>104 <th><label for="naimur-email-otp-for-woocommerce_brand_color"><?php esc_html_e('Brand color (HEX)', 'naimur-email-otp-for-woocommerce'); ?></label></th>105 <td><input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_brand_color" id="naimur-email-otp-for-woocommerce_brand_color" value="<?php echo esc_attr(get_option('naimur-email-otp-for-woocommerce_brand_color', '#3b82f6')); ?>" placeholder="#3b82f6" /></td>106 </tr>107 108 <!-- Override UI Section -->109 <tr>110 <th><?php esc_html_e('Modern Login/Register UI', 'naimur-email-otp-for-woocommerce'); ?></th>111 <td>112 <label><input type="checkbox" name="naimur-email-otp-for-woocommerce_override_enabled" value="1" <?php checked(get_option('naimur-email-otp-for-woocommerce_override_enabled', 0), 1); ?> /> <?php esc_html_e('Enable custom modern design', 'naimur-email-otp-for-woocommerce'); ?></label><br/>113 <div style="margin-top: 10px;">114 <label for="naimur-email-otp-for-woocommerce_myaccount_page_id" style="display:block; margin-bottom:5px; font-weight:500;"><?php esc_html_e('Select My Account page', 'naimur-email-otp-for-woocommerce'); ?></label>115 <?php wp_dropdown_pages(['name'=>'naimur-email-otp-for-woocommerce_myaccount_page_id', 'show_option_none'=>'— Select —', 'option_none_value'=>'0', 'selected'=>(int)get_option('naimur-email-otp-for-woocommerce_myaccount_page_id', 0)]); ?>116 </div>117 </td>118 </tr>119 120 <!-- Captcha Section -->121 <tr>122 <th><label for="naimur-email-otp-for-woocommerce_captcha_type"><?php esc_html_e('Captcha Provider', 'naimur-email-otp-for-woocommerce'); ?></label></th>123 <td>124 <select name="naimur-email-otp-for-woocommerce_captcha_type" id="naimur-email-otp-for-woocommerce_captcha_type">125 <option value="none" <?php selected(get_option('naimur-email-otp-for-woocommerce_captcha_type'), 'none'); ?>><?php esc_html_e('None', 'naimur-email-otp-for-woocommerce'); ?></option>126 <option value="recaptcha" <?php selected(get_option('naimur-email-otp-for-woocommerce_captcha_type'), 'recaptcha'); ?>><?php esc_html_e('Google reCAPTCHA v2', 'naimur-email-otp-for-woocommerce'); ?></option>127 <option value="turnstile" <?php selected(get_option('naimur-email-otp-for-woocommerce_captcha_type'), 'turnstile'); ?>><?php esc_html_e('Cloudflare Turnstile', 'naimur-email-otp-for-woocommerce'); ?></option>128 </select>129 </td>130 </tr>131 <tr>132 <th><?php esc_html_e('Google reCAPTCHA Keys', 'naimur-email-otp-for-woocommerce'); ?></th>133 <td>134 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_recaptcha_site_key" placeholder="Site Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key','') ); ?>" style="margin-bottom: 5px;" /><br/>135 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_recaptcha_secret_key" placeholder="Secret Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_recaptcha_secret_key','') ); ?>" />136 </td>137 </tr>138 <tr>139 <th><?php esc_html_e('Cloudflare Turnstile Keys', 'naimur-email-otp-for-woocommerce'); ?></th>140 <td>141 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_turnstile_site_key" placeholder="Site Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_turnstile_site_key','') ); ?>" style="margin-bottom: 5px;" /><br/>142 <input type="text" class="regular-text" name="naimur-email-otp-for-woocommerce_turnstile_secret_key" placeholder="Secret Key" value="<?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_turnstile_secret_key','') ); ?>" />143 </td>144 </tr>145 146 <!-- Form Texts -->147 <tr>148 <th><label for="naimur-email-otp-for-woocommerce_login_header_html"><?php esc_html_e( 'Login header HTML', 'naimur-email-otp-for-woocommerce' ); ?></label></th>149 <td><?php wp_editor( get_option( 'naimur-email-otp-for-woocommerce_login_header_html', '<p class="ceovw-muted">Please enter your details</p><h1>Welcome back</h1>' ), 'naimur-email-otp-for-woocommerce_login_header_html', [ 'textarea_rows' => 5 ] ); ?></td>150 </tr>151 <tr>152 <th><label for="naimur-email-otp-for-woocommerce_register_header_html"><?php esc_html_e( 'Register header HTML', 'naimur-email-otp-for-woocommerce' ); ?></label></th>153 <td><?php wp_editor( get_option( 'naimur-email-otp-for-woocommerce_register_header_html', '<h1>Create your account</h1>' ), 'naimur-email-otp-for-woocommerce_register_header_html', [ 'textarea_rows' => 5 ] ); ?></td>154 </tr>155 </table>156 <?php submit_button(); ?>157 </form>158 </div>159 160 <script>161 jQuery(document).ready(function($) {162 var templateSelect = $('#naimur-email-otp-for-woocommerce_email_template');163 var customRow = $('#ceovw_custom_html_row');164 var customTextarea = $('#naimur-email-otp-for-woocommerce_custom_email_html');165 var previewContainer = $('#ceovw_template_preview_container');166 167 function renderIframe(htmlContent) {168 var encodedStr = htmlContent.replace(/"/g, '"').replace(/'/g, ''');169 previewContainer.html('<iframe srcdoc="' + encodedStr + '" style="width:100%; height:450px; border:1px solid #eaeaea; background:#ffffff; border-radius: 4px;"></iframe>');170 }171 172 function updatePreview() {173 var selected_template = templateSelect.val();174 175 if (selected_template === 'custom') {176 // Show Custom HTML Textarea177 customRow.show();178 // Live Gutenberg-style rendering179 var rawHtml = customTextarea.val();180 var liveHtml = rawHtml.replace(/\[naimur_email_otp_code\]/g, '<strong style="color:var(--ceovw-brand, #3b82f6); font-size:24px;">123456</strong>');181 renderIframe(liveHtml);182 } else {183 // Hide Custom HTML Textarea & Load Pre-defined Template via AJAX184 customRow.hide();185 previewContainer.html('<p style="color:#666; font-style:italic;">Loading template preview...</p>');186 187 $.post(ajaxurl, {188 action: 'ceovw_preview_template',189 template_id: selected_template,190 _ajax_nonce: '<?php echo wp_create_nonce('ceovw_preview_nonce'); ?>'191 }, function(response) {192 renderIframe(response);193 });194 }195 }196 197 // Initialize198 updatePreview();199 200 // Trigger on Dropdown Change201 templateSelect.on('change', updatePreview);202 203 // Trigger Live Preview on Typing (The Gutenberg effect!)204 customTextarea.on('input', updatePreview);205 });206 </script>207 <?php }208 209 public static function render_help_page(){210 if ( ! current_user_can( 'manage_woocommerce' ) ) { return; }211 ?>212 <div class="wrap">213 <h1><?php esc_html_e('Help, Documentation & Support', 'naimur-email-otp-for-woocommerce'); ?></h1>214 <p><?php esc_html_e('Thank you for using Naimur Email OTP Verification for WooCommerce. Below you will find useful information to manage and troubleshoot the plugin.', 'naimur-email-otp-for-woocommerce'); ?></p>215 216 <div class="card" style="max-width: 800px; margin-top: 20px; padding: 20px;">217 <h2 style="margin-top: 0;"><?php esc_html_e('How to Enable the Modern Login/Register UI?', 'naimur-email-otp-for-woocommerce'); ?></h2>218 <p><?php esc_html_e('You do not need to use any shortcodes manually. The plugin comes with an auto-execution system.', 'naimur-email-otp-for-woocommerce'); ?></p>219 <ol style="font-size: 14px; line-height: 1.6;">220 <li><?php esc_html_e('Go to the plugin Settings tab.', 'naimur-email-otp-for-woocommerce'); ?></li>221 <li><?php esc_html_e('Check the box "Enable custom modern design".', 'naimur-email-otp-for-woocommerce'); ?></li>222 <li><?php esc_html_e('From the dropdown, select your WooCommerce "My Account" page.', 'naimur-email-otp-for-woocommerce'); ?></li>223 <li><?php esc_html_e('Save changes.', 'naimur-email-otp-for-woocommerce'); ?></li>224 </ol>225 <p style="color: #646970;"><em><?php esc_html_e('That is it! The plugin will automatically override the default boring WooCommerce form with our modern, responsive, and secure UI design.', 'naimur-email-otp-for-woocommerce'); ?></em></p>226 </div>227 228 <div class="card" style="max-width: 800px; margin-top: 20px; padding: 20px;">229 <h2 style="margin-top: 0;"><?php esc_html_e('Need Support or Found a Bug?', 'naimur-email-otp-for-woocommerce'); ?></h2>230 <p><?php esc_html_e('If you are facing any issues, conflicts, or have questions about how to set up the plugin, please visit our official support forum on WordPress.org. Explain your issue in detail, and we will try to resolve it as soon as possible.', 'naimur-email-otp-for-woocommerce'); ?></p>231 <a href="https://wordpress.org/support/plugin/naimur-email-otp-for-woocommerce/" target="_blank" class="button button-primary"><?php esc_html_e('Get Support on WordPress.org', 'naimur-email-otp-for-woocommerce'); ?></a>232 </div>233 234 <div class="card" style="max-width: 800px; margin-top: 20px; padding: 20px;">235 <h2 style="margin-top: 0;"><?php esc_html_e('Author Personal Consultation', 'naimur-email-otp-for-woocommerce'); ?></h2>236 <p><?php esc_html_e('Need a custom feature, specialized WordPress & WooCommerce development, or one-on-one consultation? You can reach out to the author (Naimur Rahman Nahid) directly through the personal contact form.', 'naimur-email-otp-for-woocommerce'); ?></p>237 <a href="https://naimurrahmannahid.com/contact/" target="_blank" class="button"><?php esc_html_e('Contact for Consultation', 'naimur-email-otp-for-woocommerce'); ?></a>238 </div>239 </div>240 <?php241 12 } 242 13 243 14 public static function shortcode_code($atts, $content = ''){ return $content; } 244 245 // AJAX Handler for Pre-defined Template Preview246 public static function preview_template(){247 check_ajax_referer('ceovw_preview_nonce');248 if (!current_user_can('manage_woocommerce')) wp_die('-');249 250 // Only the bundled templates can be previewed. The ID must exactly match the allowlist251 // before it is used in a file path; anything else (missing, arrays, path characters) is rejected.252 $allowed_templates = ['1', '2', '3'];253 $template = ( isset($_POST['template_id']) && is_string($_POST['template_id']) ) ? wp_unslash($_POST['template_id']) : '';254 if ( ! in_array( $template, $allowed_templates, true ) ) {255 wp_die( esc_html__( 'Invalid template.', 'naimur-email-otp-for-woocommerce' ), '', array( 'response' => 400 ) );256 }257 258 $code = '<strong style="color:#3b82f6; font-size:24px;">123456</strong>';259 $file = NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH . 'templates/email-template-' . $template . '.php';260 if (file_exists($file)) {261 // Read file and replace shortcode for preview262 $content = file_get_contents($file);263 echo str_replace('[naimur_email_otp_code]', $code, $content);264 } else {265 echo '<p>Template file not found.</p>';266 }267 wp_die();268 }269 15 } 270 16 CEOVW_Settings::init(); -
naimur-email-otp-for-woocommerce/trunk/includes/helpers.php
r3780003 r3780739 23 23 // token_hash: SHA-256 of the single-use password reset token 24 24 // expires_at: unix timestamp (UTC) after which the row is invalid 25 // sent_at: unix timestamp of the last email for this row (server-side resend cooldown, 2.1.2) 26 // context_hash: HMAC of the WooCommerce session a checkout verification is bound to (2.1.2) 25 27 $sql2 = "CREATE TABLE {$wpdb->prefix}ceovw_otps ( 26 28 id mediumint(9) NOT NULL AUTO_INCREMENT, … … 32 34 token_hash char(64) DEFAULT NULL, 33 35 expires_at bigint(20) unsigned NOT NULL DEFAULT 0, 36 sent_at bigint(20) unsigned NOT NULL DEFAULT 0, 37 context_hash char(64) DEFAULT NULL, 34 38 created_at datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, 35 39 PRIMARY KEY (id), 36 40 KEY email (email), 37 41 KEY token_hash (token_hash), 38 KEY expires_at (expires_at) 42 KEY expires_at (expires_at), 43 KEY context_hash (context_hash) 39 44 ) $charset_collate;"; 40 45 … … 45 50 46 51 /** 47 * Confirms the OTP table has the columns required by the 2.1.1authorization logic.52 * Confirms the OTP table has the columns required by the current authorization logic. 48 53 */ 49 54 function ceovw_schema_is_valid() { … … 54 59 if ( empty( $columns ) ) { return false; } 55 60 56 foreach ( ['attempts', 'token_hash', 'expires_at', 'otp' ] as $column ) {61 foreach ( ['attempts', 'token_hash', 'expires_at', 'otp', 'sent_at', 'context_hash'] as $column ) { 57 62 if ( ! isset( $columns[ $column ] ) ) { return false; } 58 63 } … … 84 89 85 90 /** 86 * 2.1.0 -> 2.1.1 migration. Saves the new DB version only when every required step succeeds.91 * Database/settings migration (2.1.0 / 2.1.1 -> current). Saves the new DB version only when every required step succeeds. 87 92 */ 88 93 function ceovw_run_upgrade() { … … 122 127 // 7. Ensure the cleanup event exists. 123 128 ceovw_schedule_cleanup(); 129 130 // 7b. 2.1.2: carry the pre-2.1.2 email template settings (custom HTML / subject) into the new template store. 131 // Deferred to init when needed: the default templates are translated, and translations must not load earlier. 132 if ( class_exists( 'CEOVW_Templates' ) ) { 133 if ( did_action( 'init' ) ) { CEOVW_Templates::migrate_legacy(); } 134 else { add_action( 'init', array( 'CEOVW_Templates', 'migrate_legacy' ), 1 ); } 135 } 124 136 125 137 // 8. Mark the migration as complete. … … 230 242 $cutoff = wp_date( 'Y-m-d H:i:s', time() - 2 * DAY_IN_SECONDS ); 231 243 $wpdb->query( $wpdb->prepare( "DELETE FROM $logs WHERE created_at < %s", $cutoff ) ); 244 245 // Activity log retention (General settings). 246 if ( class_exists( 'CEOVW_Options' ) ) { 247 $keep_after = time() - DAY_IN_SECONDS * (int) CEOVW_Options::get( 'log_retention_days' ); 248 $activity = get_option( 'ceovw_activity_logs', array() ); 249 if ( is_array( $activity ) && $activity ) { 250 $kept = array_values( array_filter( $activity, function ( $e ) use ( $keep_after ) { return is_array( $e ) && (int) ( $e['time'] ?? 0 ) >= $keep_after; } ) ); 251 if ( count( $kept ) !== count( $activity ) ) { update_option( 'ceovw_activity_logs', $kept, false ); } 252 } 253 } 254 update_option( 'ceovw_last_cleanup', time(), false ); 232 255 } 233 256 … … 235 258 * Seconds an emailed OTP (and its temporary registration data) stays valid. 236 259 */ 237 function ceovw_otp_ttl( ) {238 return max( 60, (int) apply_filters( 'ceovw_otp_expiry', 300) );260 function ceovw_otp_ttl( $flow = 'register' ) { 261 return class_exists( 'CEOVW_Options' ) ? CEOVW_Options::ttl( $flow ) : max( 60, (int) apply_filters( 'ceovw_otp_expiry', 300, $flow ) ); 239 262 } 240 263 -
naimur-email-otp-for-woocommerce/trunk/languages/naimur-email-otp-for-woocommerce.pot
r3587442 r3780739 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Naimur Email OTP Verification for WooCommerce 1.2.0\n"5 "Project-Id-Version: Naimur Email OTP Verification for WooCommerce 2.1.2\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/naimur-email-otp-for-woocommerce\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026- 06-22T09:54:30+06:00\n"12 "POT-Creation-Date: 2026-11-03T00:02:16+06:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.10.0\n"14 "X-Generator: GNU xgettext\n" 15 15 "X-Domain: naimur-email-otp-for-woocommerce\n" 16 16 … … 20 20 21 21 #. Description of the plugin 22 msgid "Verify WooCommerce customer emails using a 6-digit OTP before account creation. Modern UI with Google reCAPTCHA & Cloudflare Turnstile protection."22 msgid "Verify WooCommerce customer emails with a 6-digit OTP for registration, password reset and (optionally) checkout, with editable email templates and a modern admin." 23 23 msgstr "" 24 24 … … 27 27 msgstr "" 28 28 29 #: naimur-email-otp-for-woocommerce.php:68 30 #: includes/class-settings.php:13 31 #: includes/class-settings.php:13 32 msgid "Email Verification" 33 msgstr "" 34 35 #: naimur-email-otp-for-woocommerce.php:64 29 #: includes/admin/class-admin.php:27 30 msgid "Overview" 31 msgstr "" 32 33 #: includes/admin/class-admin.php:27 34 msgid "See what is protected, what still needs setting up, and what happened recently." 35 msgstr "" 36 37 #: includes/admin/class-admin.php:28 38 msgid "Choose where customers must confirm their email address with a 6-digit code." 39 msgstr "" 40 41 #: includes/admin/class-admin.php:28 includes/admin/class-admin.php:168 42 #: includes/admin/class-admin.php:169 includes/admin/class-admin.php:170 43 #: includes/admin/class-admin.php:171 includes/admin/class-admin.php:172 44 #: includes/admin/class-admin.php:173 includes/admin/class-admin.php:174 45 #: includes/admin/class-admin.php:175 includes/admin/views/general.php:10 46 #: includes/admin/views/help.php:10 includes/admin/views/security.php:21 47 msgid "Verification" 48 msgstr "" 49 50 #: includes/admin/class-admin.php:29 51 msgid "Change the wording and design of the emails that carry the verification codes." 52 msgstr "" 53 54 #: includes/admin/class-admin.php:29 includes/admin/class-admin.php:176 55 #: includes/admin/views/help.php:65 56 msgid "Email Templates" 57 msgstr "" 58 59 #: includes/admin/class-admin.php:30 60 msgid "Limits that stop abuse, such as too many codes or too many guesses." 61 msgstr "" 62 63 #: includes/admin/class-admin.php:30 includes/admin/class-admin.php:177 64 #: includes/admin/class-admin.php:178 includes/admin/views/help.php:75 65 msgid "Security" 66 msgstr "" 67 68 #: includes/admin/class-admin.php:31 includes/admin/class-admin.php:179 69 #: includes/admin/class-admin.php:180 includes/admin/class-admin.php:181 70 #: includes/admin/views/help.php:21 71 msgid "Activity & Diagnostics" 72 msgstr "" 73 74 #: includes/admin/class-admin.php:31 75 msgid "Send a test email, review recent emails and check that everything is working." 76 msgstr "" 77 78 #: includes/admin/class-admin.php:32 includes/admin/class-admin.php:182 79 #: includes/admin/class-admin.php:183 includes/admin/class-admin.php:184 80 #: includes/admin/class-admin.php:185 includes/admin/views/help.php:19 81 msgid "General Settings" 82 msgstr "" 83 84 #: includes/admin/class-admin.php:32 85 msgid "Sender details, branding and what happens to your data when the plugin is deleted." 86 msgstr "" 87 88 #: includes/admin/class-admin.php:33 89 msgid "Help & Documentation" 90 msgstr "" 91 92 #: includes/admin/class-admin.php:33 93 msgid "Step-by-step guides and answers to common questions." 94 msgstr "" 95 96 #: includes/admin/class-admin.php:66 includes/admin/class-admin.php:215 97 msgid "Naimur OTP" 98 msgstr "" 99 100 #: includes/admin/class-admin.php:123 101 msgid "Settings saved." 102 msgstr "" 103 104 #: includes/admin/class-admin.php:124 105 msgid "All changes saved" 106 msgstr "" 107 108 #: includes/admin/class-admin.php:125 109 msgid "Saving…" 110 msgstr "" 111 112 #: includes/admin/class-admin.php:126 113 msgid "Something went wrong. Please try again." 114 msgstr "" 115 116 #: includes/admin/class-admin.php:127 117 msgid "Saved. Some values were adjusted to the allowed range." 118 msgstr "" 119 120 #: includes/admin/class-admin.php:128 121 msgid "Please correct the highlighted fields." 122 msgstr "" 123 124 #. translators: 1: minimum, 2: maximum 125 #: includes/admin/class-admin.php:130 126 #, php-format 127 msgid "Enter a whole number from %1$d to %2$d." 128 msgstr "" 129 130 #: includes/admin/class-admin.php:131 131 msgid "Enter a valid email address, for example name@yourstore.com." 132 msgstr "" 133 134 #: includes/admin/class-admin.php:132 135 msgid "Enter a full web address starting with https://" 136 msgstr "" 137 138 #: includes/admin/class-admin.php:133 139 msgid "Confirm" 140 msgstr "" 141 142 #: includes/admin/class-admin.php:134 includes/class-checkout.php:256 143 msgid "Cancel" 144 msgstr "" 145 146 #: includes/admin/class-admin.php:135 147 msgid "Placeholder inserted." 148 msgstr "" 149 150 #: includes/admin/class-admin.php:136 151 msgid "Test email sent." 152 msgstr "" 153 154 #: includes/admin/class-admin.php:137 155 msgid "Choose logo" 156 msgstr "" 157 158 #: includes/admin/class-admin.php:138 159 msgid "Use this image" 160 msgstr "" 161 162 #: includes/admin/class-admin.php:139 includes/admin/views/general.php:30 163 msgid "No logo" 164 msgstr "" 165 166 #: includes/admin/class-admin.php:140 includes/admin/views/help.php:156 167 msgid "No help topics match your search." 168 msgstr "" 169 170 #: includes/admin/class-admin.php:141 171 msgid "No matching settings." 172 msgstr "" 173 174 #: includes/admin/class-admin.php:142 175 msgid "Unsaved changes" 176 msgstr "" 177 178 #: includes/admin/class-admin.php:143 179 msgid "You have unsaved changes. Leave this page?" 180 msgstr "" 181 182 #: includes/admin/class-admin.php:144 183 msgid "Changes discarded." 184 msgstr "" 185 186 #: includes/admin/class-admin.php:145 187 msgid "Customized template" 188 msgstr "" 189 190 #: includes/admin/class-admin.php:146 191 msgid "Built-in default template" 192 msgstr "" 193 194 #: includes/admin/class-admin.php:147 includes/admin/views/templates.php:19 195 msgid "Customized" 196 msgstr "" 197 198 #: includes/admin/class-admin.php:148 includes/admin/views/templates.php:19 199 msgid "Default" 200 msgstr "" 201 202 #: includes/admin/class-admin.php:149 203 msgid "You have unsaved changes. Discard them and switch template?" 204 msgstr "" 205 206 #: includes/admin/class-admin.php:150 207 msgid "Template saved." 208 msgstr "" 209 210 #: includes/admin/class-admin.php:151 211 msgid "Template restored to the default." 212 msgstr "" 213 214 #: includes/admin/class-admin.php:152 215 msgid "Just now" 216 msgstr "" 217 218 #: includes/admin/class-admin.php:153 219 msgid "Log cleared." 220 msgstr "" 221 222 #: includes/admin/class-admin.php:154 includes/admin/views/dashboard.php:10 223 #: includes/admin/views/templates.php:13 224 #: includes/admin/views/verification.php:10 225 #: includes/admin/views/verification.php:64 226 msgid "On" 227 msgstr "" 228 229 #: includes/admin/class-admin.php:155 includes/admin/views/dashboard.php:11 230 #: includes/admin/views/templates.php:13 231 #: includes/admin/views/verification.php:11 232 #: includes/admin/views/verification.php:64 233 msgid "Off" 234 msgstr "" 235 236 #: includes/admin/class-admin.php:156 includes/admin/views/verification.php:129 237 msgid "Guests and logged-in customers" 238 msgstr "" 239 240 #: includes/admin/class-admin.php:157 includes/admin/views/verification.php:129 241 msgid "Guest customers only" 242 msgstr "" 243 244 #. translators: %d: number of matching help topics 245 #: includes/admin/class-admin.php:159 246 #, php-format 247 msgid "%d matching topics" 248 msgstr "" 249 250 #: includes/admin/class-admin.php:168 251 msgid "Customer login and registration forms" 252 msgstr "" 253 254 #: includes/admin/class-admin.php:169 includes/admin/class-admin.php:231 255 #: includes/class-templates.php:14 256 msgid "Registration OTP" 257 msgstr "" 258 259 #: includes/admin/class-admin.php:170 includes/admin/class-admin.php:232 260 #: includes/class-templates.php:15 261 msgid "Password Reset OTP" 262 msgstr "" 263 264 #: includes/admin/class-admin.php:171 includes/admin/class-admin.php:233 265 #: includes/class-templates.php:16 266 msgid "Checkout OTP" 267 msgstr "" 268 269 #: includes/admin/class-admin.php:172 includes/admin/views/verification.php:33 270 msgid "Code Expiration Time" 271 msgstr "" 272 273 #: includes/admin/class-admin.php:173 includes/admin/views/verification.php:34 274 msgid "Wait Before Resending" 275 msgstr "" 276 277 #: includes/admin/class-admin.php:174 includes/admin/views/verification.php:35 278 msgid "Wrong Code Attempts Allowed" 279 msgstr "" 280 281 #: includes/admin/class-admin.php:175 includes/admin/views/verification.php:112 282 msgid "Also verify logged-in customers" 283 msgstr "" 284 285 #: includes/admin/class-admin.php:176 286 msgid "Email templates" 287 msgstr "" 288 289 #: includes/admin/class-admin.php:177 includes/admin/views/security.php:7 290 msgid "Rate limits" 291 msgstr "" 292 293 #: includes/admin/class-admin.php:178 294 msgid "Visitor IP address and proxies" 295 msgstr "" 296 297 #: includes/admin/class-admin.php:179 includes/admin/views/dashboard.php:51 298 #: includes/admin/views/diagnostics.php:31 includes/admin/views/help.php:86 299 msgid "Send a test email" 300 msgstr "" 301 302 #: includes/admin/class-admin.php:180 includes/admin/views/diagnostics.php:48 303 msgid "Email log" 304 msgstr "" 305 306 #: includes/admin/class-admin.php:181 includes/admin/views/diagnostics.php:99 307 msgid "Keep activity history for" 308 msgstr "" 309 310 #: includes/admin/class-admin.php:182 311 msgid "Sender name and email" 312 msgstr "" 313 314 #: includes/admin/class-admin.php:183 includes/admin/views/general.php:28 315 msgid "Store logo for emails" 316 msgstr "" 317 318 #: includes/admin/class-admin.php:184 includes/admin/views/general.php:42 319 msgid "Brand colour" 320 msgstr "" 321 322 #: includes/admin/class-admin.php:185 323 msgid "Delete data when the plugin is deleted" 324 msgstr "" 325 326 #: includes/admin/class-admin.php:198 327 msgid "You do not have permission to access this page." 328 msgstr "" 329 330 #: includes/admin/class-admin.php:203 331 msgid "Naimur Email OTP" 332 msgstr "" 333 334 #. translators: %s: plugin version 335 #: includes/admin/class-admin.php:203 336 #, php-format 337 msgid "Version %s" 338 msgstr "" 339 340 #: includes/admin/class-admin.php:204 341 msgid "Find a setting" 342 msgstr "" 343 344 #: includes/admin/class-admin.php:205 345 msgid "Find a setting…" 346 msgstr "" 347 348 #: includes/admin/class-admin.php:205 349 msgid "Type to search, then use the arrow keys to choose a result." 350 msgstr "" 351 352 #: includes/admin/class-admin.php:207 353 msgid "Naimur OTP sections" 354 msgstr "" 355 356 #: includes/admin/class-admin.php:214 357 msgid "Breadcrumb" 358 msgstr "" 359 360 #. translators: 1: minimum, 2: maximum 361 #: includes/admin/class-admin.php:276 362 #, php-format 363 msgid "Allowed range: %1$d to %2$d." 364 msgstr "" 365 366 #: includes/admin/class-admin.php:300 367 msgid "Learn more" 368 msgstr "" 369 370 #: includes/admin/class-admin.php:305 includes/admin/views/templates.php:67 371 msgid "Discard changes" 372 msgstr "" 373 374 #: includes/admin/class-admin.php:305 375 msgid "Save changes" 376 msgstr "" 377 378 #: includes/admin/class-admin.php:384 379 msgid "Never" 380 msgstr "" 381 382 #: includes/admin/class-admin.php:386 383 #, php-format 384 msgid "%s ago" 385 msgstr "" 386 387 #. translators: %s: human time difference 388 #: includes/admin/class-admin.php:386 389 #, php-format 390 msgid "in %s" 391 msgstr "" 392 393 #: includes/admin/class-admin.php:392 394 msgid "Your session expired. Please reload the page." 395 msgstr "" 396 397 #: includes/admin/class-admin.php:393 includes/admin/class-admin.php:408 398 msgid "You do not have permission to do this." 399 msgstr "" 400 401 #: includes/admin/class-admin.php:407 402 msgid "Unknown settings section." 403 msgstr "" 404 405 #: includes/admin/class-admin.php:415 406 msgid "Please enter a valid sender email address." 407 msgstr "" 408 409 #: includes/admin/class-admin.php:450 naimur-email-otp-for-woocommerce.php:116 410 msgid "Please enter a valid email address." 411 msgstr "" 412 413 #: includes/admin/class-admin.php:458 414 msgid "WordPress could not send the email. Check your mail or SMTP configuration." 415 msgstr "" 416 417 #. translators: %s: recipient email address 418 #: includes/admin/class-admin.php:459 419 #, php-format 420 msgid "Test email sent to %s. Check the inbox (and the spam folder)." 421 msgstr "" 422 423 #: includes/admin/class-admin.php:467 424 msgid "Unknown log." 425 msgstr "" 426 427 #: includes/admin/class-admin.php:468 428 msgid "Logs cleared." 429 msgstr "" 430 431 #: includes/admin/class-admin.php:473 432 msgid "The database upgrade has not completed yet." 433 msgstr "" 434 435 #: includes/admin/class-admin.php:476 436 msgid "Cleanup finished." 437 msgstr "" 438 439 #: includes/admin/views/dashboard.php:12 includes/admin/views/templates.php:13 440 #: includes/admin/views/verification.php:12 441 msgid "Needs setup" 442 msgstr "" 443 444 #: includes/admin/views/dashboard.php:17 445 msgid "Registration Verification" 446 msgstr "" 447 448 #: includes/admin/views/dashboard.php:19 449 msgid "New accounts are created without an email code." 450 msgstr "" 451 452 #: includes/admin/views/dashboard.php:19 453 msgid "New customers confirm their email before their account is created." 454 msgstr "" 455 456 #: includes/admin/views/dashboard.php:19 457 msgid "Turned on, but the customer login and registration forms are off, so no code is asked for yet." 458 msgstr "" 459 460 #: includes/admin/views/dashboard.php:21 461 msgid "Registration settings" 462 msgstr "" 463 464 #: includes/admin/views/dashboard.php:25 465 msgid "Password Reset Verification" 466 msgstr "" 467 468 #: includes/admin/views/dashboard.php:27 469 msgid "Customers reset their password with an emailed code." 470 msgstr "" 471 472 #: includes/admin/views/dashboard.php:27 473 msgid "The standard WooCommerce reset link is used." 474 msgstr "" 475 476 #: includes/admin/views/dashboard.php:27 477 msgid "Turned on, but the customer login and registration forms are off, so the standard WooCommerce reset link is used." 478 msgstr "" 479 480 #: includes/admin/views/dashboard.php:29 481 msgid "Password reset settings" 482 msgstr "" 483 484 #: includes/admin/views/dashboard.php:33 485 msgid "Checkout Verification" 486 msgstr "" 487 488 #: includes/admin/views/dashboard.php:35 489 msgid "Optional. Orders are placed without an email code." 490 msgstr "" 491 492 #: includes/admin/views/dashboard.php:35 493 msgid "Orders are created only after the billing email is confirmed." 494 msgstr "" 495 496 #: includes/admin/views/dashboard.php:37 497 msgid "Checkout settings" 498 msgstr "" 499 500 #: includes/admin/views/dashboard.php:41 501 msgid "Email Delivery" 502 msgstr "" 503 504 #: includes/admin/views/dashboard.php:43 505 msgid "Working" 506 msgstr "" 507 508 #: includes/admin/views/dashboard.php:44 509 msgid "Not tested yet" 510 msgstr "" 511 512 #: includes/admin/views/dashboard.php:45 513 msgid "Problem found" 514 msgstr "" 515 516 #: includes/admin/views/dashboard.php:47 517 msgid "The most recent email could not be sent. Check your mail or SMTP setup." 518 msgstr "" 519 520 #. translators: %s: relative time, e.g. "5 mins ago" 521 #: includes/admin/views/dashboard.php:48 522 #, php-format 523 msgid "Last test email sent %s." 524 msgstr "" 525 526 #: includes/admin/views/dashboard.php:49 527 msgid "Emails are being handed to your mail system." 528 msgstr "" 529 530 #: includes/admin/views/dashboard.php:49 531 msgid "Send a test email to make sure codes reach your customers." 532 msgstr "" 533 534 #: includes/admin/views/dashboard.php:55 535 msgid "Security Health" 536 msgstr "" 537 538 #: includes/admin/views/dashboard.php:56 539 #: includes/admin/views/diagnostics.php:82 540 msgid "Healthy" 541 msgstr "" 542 543 #: includes/admin/views/dashboard.php:56 544 msgid "Needs attention" 545 msgstr "" 546 547 #: includes/admin/views/dashboard.php:57 548 msgid "A database update is still running. Verification is paused until it finishes." 549 msgstr "" 550 551 #: includes/admin/views/dashboard.php:58 552 msgid "Rate limits are active and expired codes are removed automatically." 553 msgstr "" 554 555 #: includes/admin/views/dashboard.php:58 556 msgid "The automatic cleanup of expired codes is not running on time." 557 msgstr "" 558 559 #: includes/admin/views/dashboard.php:60 includes/admin/views/help.php:69 560 msgid "Security settings" 561 msgstr "" 562 563 #: includes/admin/views/dashboard.php:60 564 msgid "View diagnostics" 565 msgstr "" 566 567 #: includes/admin/views/dashboard.php:65 568 msgid "Registration and password reset codes appear inside these forms on your My Account page." 569 msgstr "" 570 571 #: includes/admin/views/dashboard.php:65 572 msgid "Turn on the customer login and registration forms" 573 msgstr "" 574 575 #: includes/admin/views/dashboard.php:66 576 msgid "Emails from your own domain are less likely to land in spam." 577 msgstr "" 578 579 #: includes/admin/views/dashboard.php:66 580 msgid "Set the sender name and email address" 581 msgstr "" 582 583 #: includes/admin/views/dashboard.php:67 584 msgid "Add your store logo to the emails" 585 msgstr "" 586 587 #: includes/admin/views/dashboard.php:67 588 msgid "Customers trust emails that look like your store." 589 msgstr "" 590 591 #: includes/admin/views/dashboard.php:68 592 msgid "Confirms that your site can deliver the codes." 593 msgstr "" 594 595 #: includes/admin/views/dashboard.php:68 596 msgid "Send a test email and check that it arrives" 597 msgstr "" 598 599 #: includes/admin/views/dashboard.php:69 600 msgid "Place a test order yourself first." 601 msgstr "" 602 603 #: includes/admin/views/dashboard.php:69 604 msgid "Turn on checkout verification" 605 msgstr "" 606 607 #: includes/admin/views/dashboard.php:79 608 msgid "Welcome to Naimur Email OTP" 609 msgstr "" 610 611 #: includes/admin/views/dashboard.php:80 612 msgid "Stop fake accounts and protect your customers by asking for a one-time code sent by email when they register, reset a password or check out." 613 msgstr "" 614 615 #: includes/admin/views/dashboard.php:85 616 msgid "Everything looks good" 617 msgstr "" 618 619 #. translators: %d: number of items that need attention 620 #: includes/admin/views/dashboard.php:88 621 #, php-format 622 msgid "%d item needs attention" 623 msgid_plural "%d items need attention" 624 msgstr[0] "" 625 msgstr[1] "" 626 627 #. translators: %s: plugin version 628 #: includes/admin/views/dashboard.php:91 629 #, php-format 630 msgid "Plugin version %s" 631 msgstr "" 632 633 #: includes/admin/views/dashboard.php:95 includes/admin/views/dashboard.php:147 634 #: includes/admin/views/dashboard.php:149 635 #: includes/admin/views/diagnostics.php:53 636 #: includes/admin/views/diagnostics.php:55 637 msgid "Status" 638 msgstr "" 639 640 #. translators: 1: completed steps, 2: total steps 641 #: includes/admin/views/dashboard.php:109 642 #, php-format 643 msgid "%1$d of %2$d done" 644 msgstr "" 645 646 #: includes/admin/views/dashboard.php:109 647 msgid "Setup checklist" 648 msgstr "" 649 650 #: includes/admin/views/dashboard.php:110 651 msgid "Setup progress" 652 msgstr "" 653 654 #: includes/admin/views/dashboard.php:117 655 msgid "Optional" 656 msgstr "" 657 658 #: includes/admin/views/dashboard.php:120 659 msgid "Done" 660 msgstr "" 661 662 #: includes/admin/views/dashboard.php:120 663 msgid "Not done yet" 664 msgstr "" 665 666 #: includes/admin/views/dashboard.php:124 667 msgid "Sender and logo settings can be changed by a site administrator." 668 msgstr "" 669 670 #: includes/admin/views/dashboard.php:127 671 msgid "Quick actions" 672 msgstr "" 673 674 #: includes/admin/views/dashboard.php:129 675 msgid "Configure Checkout OTP" 676 msgstr "" 677 678 #: includes/admin/views/dashboard.php:129 679 msgid "Turn it on or off and choose who is affected." 680 msgstr "" 681 682 #: includes/admin/views/dashboard.php:131 683 msgid "Change the subject, text and design." 684 msgstr "" 685 686 #: includes/admin/views/dashboard.php:131 687 msgid "Edit Email Template" 688 msgstr "" 689 690 #: includes/admin/views/dashboard.php:133 691 msgid "Check that codes reach the inbox." 692 msgstr "" 693 694 #: includes/admin/views/dashboard.php:133 695 msgid "Send a Test Email" 696 msgstr "" 697 698 #: includes/admin/views/dashboard.php:135 699 msgid "Limits for codes and wrong guesses." 700 msgstr "" 701 702 #: includes/admin/views/dashboard.php:135 703 msgid "Open Security Settings" 704 msgstr "" 705 706 #: includes/admin/views/dashboard.php:137 707 msgid "Step-by-step guides and answers." 708 msgstr "" 709 710 #: includes/admin/views/dashboard.php:137 711 msgid "View Documentation" 712 msgstr "" 713 714 #: includes/admin/views/dashboard.php:143 715 msgid "Recent emails" 716 msgstr "" 717 718 #: includes/admin/views/dashboard.php:143 719 msgid "View all" 720 msgstr "" 721 722 #: includes/admin/views/dashboard.php:145 723 msgid "No verification emails yet. They appear here once customers start receiving codes." 724 msgstr "" 725 726 #: includes/admin/views/dashboard.php:147 727 #: includes/admin/views/dashboard.php:149 728 #: includes/admin/views/diagnostics.php:35 729 #: includes/admin/views/diagnostics.php:53 730 #: includes/admin/views/diagnostics.php:55 731 #: includes/admin/views/diagnostics.php:66 732 #: includes/admin/views/diagnostics.php:68 733 msgid "Email" 734 msgstr "" 735 736 #: includes/admin/views/dashboard.php:147 737 #: includes/admin/views/dashboard.php:149 738 #: includes/admin/views/diagnostics.php:66 739 #: includes/admin/views/diagnostics.php:68 740 msgid "Event" 741 msgstr "" 742 743 #: includes/admin/views/dashboard.php:147 744 #: includes/admin/views/dashboard.php:149 745 #: includes/admin/views/diagnostics.php:53 746 #: includes/admin/views/diagnostics.php:55 747 #: includes/admin/views/diagnostics.php:66 748 #: includes/admin/views/diagnostics.php:68 749 msgid "Time" 750 msgstr "" 751 752 #: includes/admin/views/diagnostics.php:13 753 msgid "WooCommerce is not active. This plugin needs WooCommerce." 754 msgstr "" 755 756 #: includes/admin/views/diagnostics.php:14 757 msgid "The database update has not finished. Verification is paused until it does; reload this page to try again." 758 msgstr "" 759 760 #: includes/admin/views/diagnostics.php:15 761 msgid "No SMTP plugin found. Emails are sent with the basic server mail function, which is often marked as spam. An SMTP plugin is recommended." 762 msgstr "" 763 764 #: includes/admin/views/diagnostics.php:16 765 msgid "WP-Cron is turned off (DISABLE_WP_CRON). Make sure a server cron job runs wp-cron.php, or expired codes are only removed when you click \"Run cleanup now\"." 766 msgstr "" 767 768 #: includes/admin/views/diagnostics.php:17 769 msgid "Checkout verification is on, but the checkout page type was not recognised." 770 msgstr "" 771 772 #: includes/admin/views/diagnostics.php:20 773 msgid "Checkout email verified" 774 msgstr "" 775 776 #: includes/admin/views/diagnostics.php:21 777 msgid "Order placed with verified email" 778 msgstr "" 779 780 #: includes/admin/views/diagnostics.php:22 781 msgid "Registered with verified email" 782 msgstr "" 783 784 #: includes/admin/views/diagnostics.php:23 785 msgid "Password reset code verified" 786 msgstr "" 787 788 #: includes/admin/views/diagnostics.php:27 789 msgid "Things to check" 790 msgstr "" 791 792 #: includes/admin/views/diagnostics.php:31 793 msgid "Emails not arriving?" 794 msgstr "" 795 796 #: includes/admin/views/diagnostics.php:32 797 msgid "Sends one of your saved emails with sample data (code 123456) so you can check that it arrives and looks right." 798 msgstr "" 799 800 #: includes/admin/views/diagnostics.php:39 801 msgid "Send to" 802 msgstr "" 803 804 #: includes/admin/views/diagnostics.php:42 805 msgid "Send test email" 806 msgstr "" 807 808 #: includes/admin/views/diagnostics.php:44 809 msgid "If it does not arrive within a few minutes, check the spam folder and your SMTP plugin's log." 810 msgstr "" 811 812 #: includes/admin/views/diagnostics.php:48 813 #: includes/admin/views/diagnostics.php:62 814 msgid "Clear log" 815 msgstr "" 816 817 #: includes/admin/views/diagnostics.php:48 818 msgid "Clear the email log? This cannot be undone." 819 msgstr "" 820 821 #: includes/admin/views/diagnostics.php:49 822 msgid "\"Success\" means WordPress handed the email to your mail system. It does not guarantee it reached the inbox. The log keeps the last 100 emails for 7 days." 823 msgstr "" 824 825 #: includes/admin/views/diagnostics.php:51 826 msgid "No emails logged yet." 827 msgstr "" 828 829 #: includes/admin/views/diagnostics.php:53 830 #: includes/admin/views/diagnostics.php:55 831 msgid "Message" 832 msgstr "" 833 834 #: includes/admin/views/diagnostics.php:62 835 msgid "Clear the activity log? This cannot be undone." 836 msgstr "" 837 838 #: includes/admin/views/diagnostics.php:62 839 msgid "Verification activity" 840 msgstr "" 841 842 #: includes/admin/views/diagnostics.php:64 843 msgid "No verification activity yet." 844 msgstr "" 845 846 #: includes/admin/views/diagnostics.php:66 847 #: includes/admin/views/diagnostics.php:68 848 msgid "Device" 849 msgstr "" 850 851 #: includes/admin/views/diagnostics.php:66 852 #: includes/admin/views/diagnostics.php:68 853 msgid "User" 854 msgstr "" 855 856 #: includes/admin/views/diagnostics.php:68 857 msgid "Email verified" 858 msgstr "" 859 860 #: includes/admin/views/diagnostics.php:68 861 msgid "Guest" 862 msgstr "" 863 864 #: includes/admin/views/diagnostics.php:76 865 msgid "System information" 866 msgstr "" 867 868 #: includes/admin/views/diagnostics.php:78 869 msgid "Plugin" 870 msgstr "" 871 872 #: includes/admin/views/diagnostics.php:80 873 msgid "Not active" 874 msgstr "" 875 876 #: includes/admin/views/diagnostics.php:82 877 #: includes/admin/views/verification.php:162 878 msgid "Database" 879 msgstr "" 880 881 #: includes/admin/views/diagnostics.php:82 882 msgid "Needs update" 883 msgstr "" 884 885 #: includes/admin/views/diagnostics.php:83 886 msgid "Codes waiting to be used" 887 msgstr "" 888 889 #: includes/admin/views/diagnostics.php:84 890 #: includes/admin/views/verification.php:47 891 #: includes/admin/views/verification.php:152 892 msgid "Checkout block" 893 msgstr "" 894 895 #: includes/admin/views/diagnostics.php:84 896 msgid "Checkout page" 897 msgstr "" 898 899 #: includes/admin/views/diagnostics.php:84 900 #: includes/admin/views/verification.php:154 901 msgid "Classic checkout" 902 msgstr "" 903 904 #: includes/admin/views/diagnostics.php:84 905 msgid "Not recognised" 906 msgstr "" 907 908 #: includes/admin/views/diagnostics.php:84 909 msgid "Not set" 910 msgstr "" 911 912 #: includes/admin/views/diagnostics.php:85 913 msgid "High-performance order storage" 914 msgstr "" 915 916 #: includes/admin/views/diagnostics.php:85 917 #: includes/admin/views/verification.php:160 918 msgid "Order storage" 919 msgstr "" 920 921 #: includes/admin/views/diagnostics.php:85 922 msgid "WordPress posts" 923 msgstr "" 924 925 #: includes/admin/views/diagnostics.php:86 926 msgid "Mail" 927 msgstr "" 928 929 #: includes/admin/views/diagnostics.php:86 930 msgid "PHP mail (no SMTP plugin found)" 931 msgstr "" 932 933 #: includes/admin/views/diagnostics.php:88 934 msgid "Include these versions when you ask for support. Never share codes, passwords or customer data." 935 msgstr "" 936 937 #: includes/admin/views/diagnostics.php:91 938 msgid "Maintenance" 939 msgstr "" 940 941 #: includes/admin/views/diagnostics.php:93 942 msgid "Next cleanup" 943 msgstr "" 944 945 #: includes/admin/views/diagnostics.php:93 946 msgid "Not scheduled" 947 msgstr "" 948 949 #: includes/admin/views/diagnostics.php:94 950 msgid "Last cleanup" 951 msgstr "" 952 953 #: includes/admin/views/diagnostics.php:96 954 msgid "Expired codes and old history are removed automatically every hour. You can also run it now." 955 msgstr "" 956 957 #: includes/admin/views/diagnostics.php:97 958 msgid "Run cleanup now" 959 msgstr "" 960 961 #: includes/admin/views/diagnostics.php:99 962 msgid "Older verification activity is removed by the cleanup." 963 msgstr "" 964 965 #: includes/admin/views/diagnostics.php:99 966 msgid "days" 967 msgstr "" 968 969 #. translators: %s: link to the Verification page 970 #: includes/admin/views/general.php:9 971 #, php-format 972 msgid "Looking for the customer login and registration form settings? They are now on the %s page, next to the codes they control." 973 msgstr "" 974 975 #: includes/admin/views/general.php:14 976 msgid "Sender" 977 msgstr "" 978 979 #: includes/admin/views/general.php:15 980 msgid "Who the verification emails come from. Only emails sent by this plugin are changed. Leave empty to use your WordPress defaults." 981 msgstr "" 982 983 #: includes/admin/views/general.php:18 984 msgid "Sender name" 985 msgstr "" 986 987 #: includes/admin/views/general.php:18 988 msgid "Usually your store name." 989 msgstr "" 990 991 #: includes/admin/views/general.php:19 992 msgid "Sender email" 993 msgstr "" 994 995 #: includes/admin/views/general.php:19 996 msgid "Use an address on your own domain so emails are not marked as spam. Also shown to customers as the support address." 997 msgstr "" 998 999 #: includes/admin/views/general.php:25 1000 msgid "Branding" 1001 msgstr "" 1002 1003 #: includes/admin/views/general.php:34 1004 msgid "Choose from Media Library" 1005 msgstr "" 1006 1007 #: includes/admin/views/general.php:35 1008 msgid "Remove" 1009 msgstr "" 1010 1011 #: includes/admin/views/general.php:39 1012 msgid "When empty, the WooCommerce email header image is used." 1013 msgstr "" 1014 1015 #: includes/admin/views/general.php:39 1016 msgid "When empty, your store name is shown as text." 1017 msgstr "" 1018 1019 #: includes/admin/views/general.php:44 1020 msgid "Used for buttons and highlights in the emails, the checkout popup and the customer forms." 1021 msgstr "" 1022 1023 #: includes/admin/views/general.php:50 1024 msgid "When the plugin is deleted" 1025 msgstr "" 1026 1027 #: includes/admin/views/general.php:51 1028 msgid "Delete all plugin data when the plugin is deleted" 1029 msgstr "" 1030 1031 #: includes/admin/views/general.php:51 1032 msgid "When off, your settings and email templates are kept so you can reinstall later. Pending codes, reset authorizations and logs are always removed." 1033 msgstr "" 1034 1035 #: includes/admin/views/help.php:13 1036 msgid "Getting started" 1037 msgstr "" 1038 1039 #: includes/admin/views/help.php:15 1040 msgid "Follow these steps once. The Overview page shows a checklist that ticks itself off as you go." 1041 msgstr "" 1042 1043 #. translators: %s: link to the Verification page 1044 #: includes/admin/views/help.php:17 1045 #, php-format 1046 msgid "1. Open %s and turn on the customer login and registration forms (Step 1). Choose your My Account page." 1047 msgstr "" 1048 1049 #. translators: %s: link to General Settings 1050 #: includes/admin/views/help.php:19 1051 #, php-format 1052 msgid "2. In %s, set your sender name, sender email and store logo so emails look like they come from your store." 1053 msgstr "" 1054 1055 #. translators: %s: link to Activity & Diagnostics 1056 #: includes/admin/views/help.php:21 1057 #, php-format 1058 msgid "3. Send a test email from %s and make sure it arrives." 1059 msgstr "" 1060 1061 #: includes/admin/views/help.php:22 1062 msgid "4. Optional: turn on Checkout OTP after you have placed a test order yourself." 1063 msgstr "" 1064 1065 #: includes/admin/views/help.php:26 1066 msgid "Registration OTP setup" 1067 msgstr "" 1068 1069 #: includes/admin/views/help.php:28 1070 msgid "When a customer signs up on your My Account page, they receive a 6-digit code by email and must enter it before the account is created. Email addresses that are not confirmed never become accounts." 1071 msgstr "" 1072 1073 #: includes/admin/views/help.php:29 1074 msgid "It needs the customer login and registration forms (Step 1 on the Verification page). If those forms are off, the Registration tab shows \"Needs setup\"." 1075 msgstr "" 1076 1077 #: includes/admin/views/help.php:30 1078 msgid "Code Expiration Time, Wait Before Resending and Wrong Code Attempts Allowed can be set separately for registration." 1079 msgstr "" 1080 1081 #. translators: %s: link to the Registration tab 1082 #. translators: %s: link to the Password Reset tab 1083 #. translators: %s: link to the Checkout tab 1084 #. translators: %s: link to the test email section 1085 #: includes/admin/views/help.php:32 includes/admin/views/help.php:43 1086 #: includes/admin/views/help.php:54 includes/admin/views/help.php:86 1087 #, php-format 1088 msgid "Go to %s." 1089 msgstr "" 1090 1091 #: includes/admin/views/help.php:32 1092 msgid "Registration OTP settings" 1093 msgstr "" 1094 1095 #: includes/admin/views/help.php:36 1096 msgid "Password reset setup" 1097 msgstr "" 1098 1099 #: includes/admin/views/help.php:38 1100 msgid "The customer clicks \"Forgot password?\", enters their email, receives a code and, after entering it, chooses a new password with the \"Update Password\" button." 1101 msgstr "" 1102 1103 #: includes/admin/views/help.php:39 1104 msgid "The reset permission works once and expires after 5 minutes. The customer then signs in with the new password; they are never signed in automatically." 1105 msgstr "" 1106 1107 #: includes/admin/views/help.php:40 1108 msgid "The form shows the same message whether or not an account exists, so it cannot be used to find out who is registered." 1109 msgstr "" 1110 1111 #: includes/admin/views/help.php:41 1112 msgid "If you turn this off, the standard WooCommerce \"Lost your password?\" link is used instead." 1113 msgstr "" 1114 1115 #: includes/admin/views/help.php:43 1116 msgid "Password Reset OTP settings" 1117 msgstr "" 1118 1119 #: includes/admin/views/help.php:47 1120 msgid "Checkout OTP setup" 1121 msgstr "" 1122 1123 #: includes/admin/views/help.php:49 1124 msgid "Off by default. When on, clicking Place order opens a popup asking for the code sent to the billing email. The order is created only after the code is verified, so no payment is taken before that." 1125 msgstr "" 1126 1127 #: includes/admin/views/help.php:50 1128 msgid "Guests are always asked. Logged-in customers are asked too unless you turn off \"Also verify logged-in customers\"." 1129 msgstr "" 1130 1131 #: includes/admin/views/help.php:51 1132 msgid "A verification is valid once, for the same browser session and the exact same email. Changing the email needs a new code. If a payment fails, the customer can retry payment for the same order without a new code." 1133 msgstr "" 1134 1135 #: includes/admin/views/help.php:52 1136 msgid "Before turning it on: place a test order with each payment method you use and check that the popup appears and the order completes." 1137 msgstr "" 1138 1139 #: includes/admin/views/help.php:54 1140 msgid "Checkout OTP settings" 1141 msgstr "" 1142 1143 #: includes/admin/views/help.php:58 1144 msgid "Email template customization" 1145 msgstr "" 1146 1147 #: includes/admin/views/help.php:60 1148 msgid "Each email (Registration, Password Reset, Checkout) has its own subject, preheader and design. Choose one with the tabs at the top of the Email Templates page. The preview on the right updates as you type; use Desktop and Mobile to check both sizes." 1149 msgstr "" 1150 1151 #: includes/admin/views/help.php:61 1152 msgid "Click a placeholder to insert it where your cursor is. {{otp_code}} must appear in every email. Others: {{site_name}}, {{site_url}}, {{customer_name}}, {{customer_email}}, {{expiry_minutes}}, {{support_email}}, {{site_logo}}, {{brand_color}} and {{preheader}}." 1153 msgstr "" 1154 1155 #: includes/admin/views/help.php:62 1156 msgid "For safety, scripts, forms, iframes, event handlers and javascript: links are removed when you save, and PHP is never run. Placeholder values are escaped." 1157 msgstr "" 1158 1159 #: includes/admin/views/help.php:63 1160 msgid "\"Send test\" emails what is in the editor now. \"Discard changes\" returns to the last saved version, and \"Reset to default\" restores the built-in design." 1161 msgstr "" 1162 1163 #. translators: %s: link to Email Templates 1164 #: includes/admin/views/help.php:65 1165 #, php-format 1166 msgid "Go to %s. Only site administrators can edit email HTML." 1167 msgstr "" 1168 1169 #: includes/admin/views/help.php:71 1170 msgid "Rate limits count codes sent per visitor and per email address, and code checks per visitor. They reset automatically after their time window, so a blocked visitor can try again later." 1171 msgstr "" 1172 1173 #: includes/admin/views/help.php:72 1174 msgid "The visitor address comes from REMOTE_ADDR. Forwarded headers are ignored because they can be forged. Behind Cloudflare or another proxy, a developer can supply the real address with the ceovw_client_ip filter after checking the request came from the proxy." 1175 msgstr "" 1176 1177 #: includes/admin/views/help.php:73 1178 msgid "CAPTCHA is not available in this version. Earlier versions showed CAPTCHA widgets without checking them on the server, so they were removed rather than give a false sense of protection." 1179 msgstr "" 1180 1181 #. translators: %s: link to Security 1182 #: includes/admin/views/help.php:75 1183 #, php-format 1184 msgid "Go to %s. Only site administrators can change these limits." 1185 msgstr "" 1186 1187 #: includes/admin/views/help.php:79 1188 msgid "SMTP troubleshooting: emails are not arriving" 1189 msgstr "" 1190 1191 #: includes/admin/views/help.php:81 1192 msgid "1. Install and set up an SMTP plugin, for example WP Mail SMTP or FluentSMTP. Many hosts block or spam-filter the basic PHP mail function." 1193 msgstr "" 1194 1195 #: includes/admin/views/help.php:82 1196 msgid "2. Use a sender email address on your own domain (General Settings)." 1197 msgstr "" 1198 1199 #: includes/admin/views/help.php:83 1200 msgid "3. Send a test email and check the inbox and the spam folder." 1201 msgstr "" 1202 1203 #: includes/admin/views/help.php:84 1204 msgid "4. Look at the email log. \"Failed\" means WordPress could not hand the email over; check your SMTP plugin's settings and log. \"Success\" means it was handed over, not that it reached the inbox." 1205 msgstr "" 1206 1207 #: includes/admin/views/help.php:90 1208 msgid "WooCommerce compatibility" 1209 msgstr "" 1210 1211 #: includes/admin/views/help.php:92 1212 msgid "Checkout block and classic checkout ([woocommerce_checkout] shortcode): supported." 1213 msgstr "" 1214 1215 #: includes/admin/views/help.php:93 1216 msgid "High-performance order storage (HPOS) and WordPress posts storage: supported." 1217 msgstr "" 1218 1219 #: includes/admin/views/help.php:94 1220 msgid "Payment methods: tested with Cash on delivery and Direct bank transfer. Other gateways have not been tested, so place a test order with yours before turning Checkout OTP on." 1221 msgstr "" 1222 1223 #: includes/admin/views/help.php:95 1224 msgid "Express payment buttons outside the checkout form (for example Apple Pay or Google Pay on the cart page) cannot show the popup. While Checkout OTP is on, those orders are refused and the customer completes the purchase through the checkout form." 1225 msgstr "" 1226 1227 #: includes/admin/views/help.php:96 1228 msgid "Custom checkout pages that use neither the block nor the shortcode are still protected on the server, but the popup may not appear. Activity & Diagnostics shows which checkout type your store uses." 1229 msgstr "" 1230 1231 #: includes/admin/views/help.php:100 1232 msgid "Frequently asked questions" 1233 msgstr "" 1234 1235 #: includes/admin/views/help.php:102 1236 msgid "Does Checkout OTP turn on by itself after updating?" 1237 msgstr "" 1238 1239 #: includes/admin/views/help.php:102 1240 msgid "No. It stays off until you turn it on." 1241 msgstr "" 1242 1243 #: includes/admin/views/help.php:103 1244 msgid "Can a customer reuse a code?" 1245 msgstr "" 1246 1247 #: includes/admin/views/help.php:103 1248 msgid "No. Every code works once, only for the purpose it was sent for, and only until it expires." 1249 msgstr "" 1250 1251 #: includes/admin/views/help.php:104 1252 msgid "Shop managers can turn verification on or off and send test emails. Email HTML, security limits and general settings need a site administrator." 1253 msgstr "" 1254 1255 #: includes/admin/views/help.php:104 1256 msgid "Who can change these settings?" 1257 msgstr "" 1258 1259 #: includes/admin/views/help.php:105 1260 msgid "Pending codes, reset permissions and logs are always removed. Settings and templates are kept unless \"Delete all plugin data\" is on in General Settings." 1261 msgstr "" 1262 1263 #: includes/admin/views/help.php:105 1264 msgid "What happens when I delete the plugin?" 1265 msgstr "" 1266 1267 #: includes/admin/views/help.php:106 1268 msgid "Are codes or passwords stored in the logs?" 1269 msgstr "" 1270 1271 #: includes/admin/views/help.php:106 1272 msgid "No. Codes are stored only as hashes, logs never contain codes, passwords or tokens, and email addresses are masked." 1273 msgstr "" 1274 1275 #: includes/admin/views/help.php:110 1276 msgid "Common errors and solutions" 1277 msgstr "" 1278 1279 #: includes/admin/views/help.php:112 1280 msgid "\"Too many requests. Please try again later.\" or \"Please wait … seconds before requesting a new code.\"" 1281 msgstr "" 1282 1283 #: includes/admin/views/help.php:112 1284 msgid "A rate limit or the resend wait was reached. The customer can try again after the time shown. If many real customers see this, raise the limits on the Security page." 1285 msgstr "" 1286 1287 #: includes/admin/views/help.php:113 1288 msgid "\"That code is not correct.\" or \"This code has expired.\"" 1289 msgstr "" 1290 1291 #: includes/admin/views/help.php:113 1292 msgid "The code was mistyped, already used, replaced by a newer one, or older than the Code Expiration Time. Ask the customer to request a new code and use the newest email." 1293 msgstr "" 1294 1295 #: includes/admin/views/help.php:114 1296 msgid "\"Please verify your email address to place your order.\"" 1297 msgstr "" 1298 1299 #: includes/admin/views/help.php:114 1300 msgid "The order came from a place that cannot show the popup, such as an express payment button. The customer should use the checkout form." 1301 msgstr "" 1302 1303 #: includes/admin/views/help.php:115 1304 msgid "Registration shows no code step" 1305 msgstr "" 1306 1307 #: includes/admin/views/help.php:115 1308 msgid "The customer login and registration forms are off, or the wrong My Account page is selected. Check Step 1 on the Verification page." 1309 msgstr "" 1310 1311 #: includes/admin/views/help.php:116 1312 msgid "\"This service is temporarily unavailable.\" or \"Email verification is temporarily unavailable…\"" 1313 msgstr "" 1314 1315 #: includes/admin/views/help.php:116 1316 msgid "Usually the plugin's database update has not finished, so verification is paused for safety. Open any admin page to let it finish; if the message continues, check Activity & Diagnostics and ask for support." 1317 msgstr "" 1318 1319 #: includes/admin/views/help.php:120 1320 msgid "For developers" 1321 msgstr "" 1322 1323 #: includes/admin/views/help.php:122 1324 msgid "ceovw_client_ip (filter): return the visitor IP address used for rate limits." 1325 msgstr "" 1326 1327 #: includes/admin/views/help.php:123 1328 msgid "ceovw_otp_expiry (filter): code lifetime in seconds, receives the flow name. Minimum 60." 1329 msgstr "" 1330 1331 #: includes/admin/views/help.php:124 1332 msgid "ceovw_checkout_auth_ttl (filter): how long a verified checkout email stays valid before the order is placed, in seconds. Default 1800." 1333 msgstr "" 1334 1335 #: includes/admin/views/help.php:125 1336 msgid "ceovw_admin_capability (filter): capability needed for these pages. Default manage_woocommerce." 1337 msgstr "" 1338 1339 #: includes/admin/views/help.php:126 1340 msgid "ceovw_settings_capability (filter): capability needed for email HTML, security limits and general settings. Default manage_options." 1341 msgstr "" 1342 1343 #: includes/admin/views/help.php:127 1344 msgid "Codes and reset permissions are stored as hashes in the wp_ceovw_otps table; rate-limit history is in wp_ceovw_logs. A scheduled event removes expired records every hour." 1345 msgstr "" 1346 1347 #: includes/admin/views/help.php:134 1348 msgid "Topics" 1349 msgstr "" 1350 1351 #: includes/admin/views/help.php:143 1352 msgid "Search the documentation" 1353 msgstr "" 1354 1355 #: includes/admin/views/help.php:144 1356 msgid "For example: checkout, SMTP, placeholders" 1357 msgstr "" 1358 1359 #: includes/admin/views/help.php:159 1360 msgid "Still need help?" 1361 msgstr "" 1362 1363 #. translators: %s: link to the WordPress.org support forum 1364 #: includes/admin/views/help.php:163 1365 #, php-format 1366 msgid "Ask in the %s. Please include the versions shown under System information in Activity & Diagnostics, but never post codes, passwords or personal data." 1367 msgstr "" 1368 1369 #: includes/admin/views/help.php:164 1370 msgid "(opens in a new tab)" 1371 msgstr "" 1372 1373 #: includes/admin/views/help.php:164 1374 msgid "WordPress.org support forum" 1375 msgstr "" 1376 1377 #: includes/admin/views/security.php:8 1378 msgid "These limits apply to registration, password reset and checkout codes. They stop one visitor from flooding inboxes or guessing codes. The defaults suit most stores; values outside the safe range are adjusted automatically." 1379 msgstr "" 1380 1381 #: includes/admin/views/security.php:11 1382 msgid "Codes per visitor, per hour" 1383 msgstr "" 1384 1385 #: includes/admin/views/security.php:11 1386 msgid "How many codes one visitor (IP address) can request in an hour." 1387 msgstr "" 1388 1389 #: includes/admin/views/security.php:11 1390 msgid "per hour" 1391 msgstr "" 1392 1393 #: includes/admin/views/security.php:12 1394 msgid "Codes per email address, per day" 1395 msgstr "" 1396 1397 #: includes/admin/views/security.php:12 1398 msgid "Protects one email address from receiving too many codes." 1399 msgstr "" 1400 1401 #: includes/admin/views/security.php:12 1402 msgid "per day" 1403 msgstr "" 1404 1405 #: includes/admin/views/security.php:13 1406 msgid "Code checks per visitor, per 15 minutes" 1407 msgstr "" 1408 1409 #: includes/admin/views/security.php:13 1410 msgid "How many times one visitor can try codes. Slows down guessing." 1411 msgstr "" 1412 1413 #: includes/admin/views/security.php:13 includes/admin/views/security.php:14 1414 msgid "per 15 min" 1415 msgstr "" 1416 1417 #: includes/admin/views/security.php:14 1418 msgid "Applies to the last step of a password reset." 1419 msgstr "" 1420 1421 #: includes/admin/views/security.php:14 1422 msgid "Password changes per visitor, per 15 minutes" 1423 msgstr "" 1424 1425 #. translators: %s: link to the Verification page 1426 #: includes/admin/views/security.php:20 1427 #, php-format 1428 msgid "Code expiration time, resend wait and wrong attempts are set for each verification type on the %s page." 1429 msgstr "" 1430 1431 #: includes/admin/views/security.php:28 1432 msgid "Visitor IP address" 1433 msgstr "" 1434 1435 #: includes/admin/views/security.php:29 1436 msgid "Rate limits use the address your web server reports for each connection (REMOTE_ADDR). Headers such as X-Forwarded-For are ignored because visitors can forge them." 1437 msgstr "" 1438 1439 #: includes/admin/views/security.php:31 1440 msgid "Your address right now" 1441 msgstr "" 1442 1443 #: includes/admin/views/security.php:33 1444 msgid "Custom address resolver" 1445 msgstr "" 1446 1447 #: includes/admin/views/security.php:34 1448 msgid "Active (ceovw_client_ip filter)" 1449 msgstr "" 1450 1451 #: includes/admin/views/security.php:34 1452 msgid "None" 1453 msgstr "" 1454 1455 #: includes/admin/views/security.php:36 1456 msgid "Using Cloudflare or another proxy? Every visitor may then share the proxy's address. A developer can pass the real address with the ceovw_client_ip filter after checking the request really came from the proxy." 1457 msgstr "" 1458 1459 #: includes/admin/views/security.php:39 1460 msgid "CAPTCHA" 1461 msgstr "" 1462 1463 #: includes/admin/views/security.php:39 1464 msgid "Unavailable" 1465 msgstr "" 1466 1467 #: includes/admin/views/security.php:40 1468 msgid "CAPTCHA is not part of this version. Earlier versions showed reCAPTCHA or Turnstile widgets without verifying them on the server, so they gave no real protection and have been removed. Rate limits, per-email limits and attempt limits protect the forms instead." 1469 msgstr "" 1470 1471 #: includes/admin/views/security.php:42 1472 msgid "A CAPTCHA provider was selected in an earlier version. Its saved keys are kept but not used." 1473 msgstr "" 1474 1475 #: includes/admin/views/security.php:48 1476 msgid "Always-on protections" 1477 msgstr "" 1478 1479 #: includes/admin/views/security.php:49 1480 msgid "These cannot be turned off." 1481 msgstr "" 1482 1483 #: includes/admin/views/security.php:51 1484 msgid "Codes are random 6-digit numbers from a cryptographically secure generator" 1485 msgstr "" 1486 1487 #: includes/admin/views/security.php:52 1488 msgid "Codes are stored only as a keyed hash, never in plain text" 1489 msgstr "" 1490 1491 #: includes/admin/views/security.php:53 1492 msgid "Each code works once, and wrong attempts are counted safely" 1493 msgstr "" 1494 1495 #: includes/admin/views/security.php:54 1496 msgid "A registration code cannot reset a password or verify a checkout" 1497 msgstr "" 1498 1499 #: includes/admin/views/security.php:55 1500 msgid "A verified password reset is valid once, for 5 minutes, and never signs the customer in" 1501 msgstr "" 1502 1503 #: includes/admin/views/security.php:56 1504 msgid "Password reset shows the same message whether or not the account exists" 1505 msgstr "" 1506 1507 #: includes/admin/views/security.php:57 1508 msgid "Checkout verification is tied to the browser session and the exact email" 1509 msgstr "" 1510 1511 #: includes/admin/views/security.php:58 1512 msgid "Logs never contain codes, passwords or tokens, and emails are masked" 1513 msgstr "" 1514 1515 #: includes/admin/views/security.php:59 1516 msgid "Expired records are removed by a scheduled cleanup" 1517 msgstr "" 1518 1519 #: includes/admin/views/security.php:60 1520 msgid "Verification is blocked while a database update is pending" 1521 msgstr "" 1522 1523 #: includes/admin/views/templates.php:16 1524 msgid "Email template" 1525 msgstr "" 1526 1527 #. translators: 1: verification name, 2: On/Off, 3: link to the settings 1528 #: includes/admin/views/templates.php:34 1529 #, php-format 1530 msgid "Sent for %1$s (currently %2$s). %3$s" 1531 msgstr "" 1532 1533 #: includes/admin/views/templates.php:36 1534 msgid "Change settings" 1535 msgstr "" 1536 1537 #: includes/admin/views/templates.php:42 1538 msgid "Email subject" 1539 msgstr "" 1540 1541 #: includes/admin/views/templates.php:46 1542 msgid "Preheader" 1543 msgstr "" 1544 1545 #: includes/admin/views/templates.php:48 1546 msgid "The short preview line many inboxes show after the subject." 1547 msgstr "" 1548 1549 #: includes/admin/views/templates.php:51 1550 msgid "Email design (HTML)" 1551 msgstr "" 1552 1553 #: includes/admin/views/templates.php:53 1554 msgid "For safety, scripts, forms, iframes, event handlers and unsafe links are removed when you save. PHP is never run." 1555 msgstr "" 1556 1557 #: includes/admin/views/templates.php:56 1558 msgid "(click to insert at the cursor)" 1559 msgstr "" 1560 1561 #: includes/admin/views/templates.php:56 1562 msgid "Available placeholders" 1563 msgstr "" 1564 1565 #. translators: 1: placeholder, 2: description 1566 #: includes/admin/views/templates.php:59 1567 #, php-format 1568 msgid "Insert %1$s: %2$s" 1569 msgstr "" 1570 1571 #: includes/admin/views/templates.php:65 1572 msgid "Replace this template with the built-in default? Your changes will be lost." 1573 msgstr "" 1574 1575 #: includes/admin/views/templates.php:65 1576 msgid "Reset to default" 1577 msgstr "" 1578 1579 #: includes/admin/views/templates.php:68 1580 msgid "Save template" 1581 msgstr "" 1582 1583 #: includes/admin/views/templates.php:74 1584 msgid "Live preview" 1585 msgstr "" 1586 1587 #: includes/admin/views/templates.php:75 1588 msgid "Preview width" 1589 msgstr "" 1590 1591 #: includes/admin/views/templates.php:76 1592 msgid "Desktop" 1593 msgstr "" 1594 1595 #: includes/admin/views/templates.php:77 1596 msgid "Mobile" 1597 msgstr "" 1598 1599 #: includes/admin/views/templates.php:80 1600 msgid "Subject:" 1601 msgstr "" 1602 1603 #: includes/admin/views/templates.php:81 1604 msgid "Email preview" 1605 msgstr "" 1606 1607 #: includes/admin/views/templates.php:82 1608 msgid "The preview uses sample data (Jane Doe, code 123456) and runs with scripts disabled." 1609 msgstr "" 1610 1611 #: includes/admin/views/templates.php:84 1612 msgid "Send a test of this email" 1613 msgstr "" 1614 1615 #: includes/admin/views/templates.php:87 1616 msgid "Send test" 1617 msgstr "" 1618 1619 #: includes/admin/views/templates.php:89 1620 msgid "Sends what is in the editor now, with sample data. It does not create a real code." 1621 msgstr "" 1622 1623 #: includes/admin/views/templates.php:91 1624 msgid "Template guide" 1625 msgstr "" 1626 1627 #: includes/admin/views/verification.php:18 1628 msgid "Ask new customers for a code when they create an account" 1629 msgstr "" 1630 1631 #: includes/admin/views/verification.php:19 1632 msgid "The account is created only after the customer enters the 6-digit code sent to their email." 1633 msgstr "" 1634 1635 #: includes/admin/views/verification.php:20 1636 msgid "Stops fake sign-ups and typos in email addresses. Customers fill in the registration form, receive a code by email and enter it to finish creating their account." 1637 msgstr "" 1638 1639 #: includes/admin/views/verification.php:25 1640 msgid "Let customers reset their password with an emailed code" 1641 msgstr "" 1642 1643 #: includes/admin/views/verification.php:26 1644 msgid "When off, the standard WooCommerce \"Lost your password?\" link is used instead." 1645 msgstr "" 1646 1647 #: includes/admin/views/verification.php:27 1648 msgid "Customers enter their email, receive a code and then choose a new password. They are not signed in automatically afterwards, and the form never reveals whether an email is registered." 1649 msgstr "" 1650 1651 #: includes/admin/views/verification.php:33 1652 msgid "How long the verification code remains valid. 5 minutes is recommended." 1653 msgstr "" 1654 1655 #: includes/admin/views/verification.php:33 1656 msgid "minutes" 1657 msgstr "" 1658 1659 #: includes/admin/views/verification.php:34 1660 msgid "How long customers wait before they can ask for a new code." 1661 msgstr "" 1662 1663 #: includes/admin/views/verification.php:34 1664 msgid "seconds" 1665 msgstr "" 1666 1667 #: includes/admin/views/verification.php:35 1668 msgid "After this many wrong entries the code stops working and a new one is needed." 1669 msgstr "" 1670 1671 #: includes/admin/views/verification.php:35 1672 msgid "tries" 1673 msgstr "" 1674 1675 #: includes/admin/views/verification.php:39 1676 msgid "Related:" 1677 msgstr "" 1678 1679 #: includes/admin/views/verification.php:41 1680 msgid "Edit this email" 1681 msgstr "" 1682 1683 #: includes/admin/views/verification.php:42 1684 msgid "Security limits" 1685 msgstr "" 1686 1687 #: includes/admin/views/verification.php:44 1688 msgid "How it works" 1689 msgstr "" 1690 1691 #: includes/admin/views/verification.php:48 1692 msgid "Classic checkout (shortcode)" 1693 msgstr "" 1694 1695 #: includes/admin/views/verification.php:49 1696 msgid "Custom checkout page (not recognised)" 1697 msgstr "" 1698 1699 #: includes/admin/views/verification.php:50 1700 msgid "No checkout page set" 1701 msgstr "" 1702 1703 #: includes/admin/views/verification.php:53 1704 msgid "Verification type" 1705 msgstr "" 1706 1707 #: includes/admin/views/verification.php:63 1708 msgid "Step 1: Customer login and registration forms" 1709 msgstr "" 1710 1711 #: includes/admin/views/verification.php:66 1712 msgid "Registration and password reset codes are asked for inside the plugin's own sign-in, sign-up and password reset forms. They replace the standard forms on your My Account page for visitors who are not signed in. This setting is shared by the Registration and Password Reset tabs." 1713 msgstr "" 1714 1715 #: includes/admin/views/verification.php:67 1716 msgid "Required for registration and password reset codes. Checkout verification works without it." 1717 msgstr "" 1718 1719 #: includes/admin/views/verification.php:67 1720 msgid "Use the Naimur OTP login, registration and password reset forms" 1721 msgstr "" 1722 1723 #: includes/admin/views/verification.php:69 1724 msgid "My Account page" 1725 msgstr "" 1726 1727 #: includes/admin/views/verification.php:74 1728 msgid "— Select a page —" 1729 msgstr "" 1730 1731 #: includes/admin/views/verification.php:79 1732 msgid "Usually the same page as WooCommerce → Settings → Advanced → My account page." 1733 msgstr "" 1734 1735 #: includes/admin/views/verification.php:93 1736 msgid "Go to Step 1" 1737 msgstr "" 1738 1739 #: includes/admin/views/verification.php:93 1740 msgid "This needs the customer login and registration forms. Turn them on in Step 1 above." 1741 msgstr "" 1742 1743 #: includes/admin/views/verification.php:109 1744 msgid "Customers confirm their billing email with a 6-digit code before the order is created. The check runs on your server, so no order is created and no payment is taken until the code is verified." 1745 msgstr "" 1746 1747 #: includes/admin/views/verification.php:111 1748 msgid "Off by default. Place a test order with your own payment method before turning this on for customers." 1749 msgstr "" 1750 1751 #: includes/admin/views/verification.php:111 1752 msgid "Require email verification at checkout" 1753 msgstr "" 1754 1755 #: includes/admin/views/verification.php:112 1756 msgid "When off, only guest checkouts are asked for a code." 1757 msgstr "" 1758 1759 #: includes/admin/views/verification.php:120 1760 msgid "How checkout verification works" 1761 msgstr "" 1762 1763 #: includes/admin/views/verification.php:122 1764 msgid "Customer places order" 1765 msgstr "" 1766 1767 #: includes/admin/views/verification.php:122 1768 msgid "They fill in checkout and click Place order." 1769 msgstr "" 1770 1771 #: includes/admin/views/verification.php:123 1772 msgid "A popup opens and a code goes to the billing email." 1773 msgstr "" 1774 1775 #: includes/admin/views/verification.php:123 1776 msgid "Email code sent" 1777 msgstr "" 1778 1779 #: includes/admin/views/verification.php:124 1780 msgid "Customer verifies" 1781 msgstr "" 1782 1783 #: includes/admin/views/verification.php:124 1784 msgid "They type or paste the 6-digit code." 1785 msgstr "" 1786 1787 #: includes/admin/views/verification.php:125 1788 msgid "Checkout continues" 1789 msgstr "" 1790 1791 #: includes/admin/views/verification.php:125 1792 msgid "The order is placed automatically." 1793 msgstr "" 1794 1795 #: includes/admin/views/verification.php:128 1796 msgid "Which customers are affected?" 1797 msgstr "" 1798 1799 #: includes/admin/views/verification.php:129 1800 msgid "A verified email stays verified for that browser session, so customers are not asked twice." 1801 msgstr "" 1802 1803 #: includes/admin/views/verification.php:130 1804 msgid "When does the code popup appear?" 1805 msgstr "" 1806 1807 #: includes/admin/views/verification.php:131 1808 msgid "Right after the customer clicks Place order, before the order is created. If they change the billing email, a new code is needed." 1809 msgstr "" 1810 1811 #: includes/admin/views/verification.php:132 1812 msgid "How long does the code last?" 1813 msgstr "" 1814 1815 #. translators: %s: number of minutes 1816 #: includes/admin/views/verification.php:133 1817 #, php-format 1818 msgid "%s minutes, set by Code Expiration Time above." 1819 msgstr "" 1820 1821 #: includes/admin/views/verification.php:134 1822 msgid "What if the customer cannot verify?" 1823 msgstr "" 1824 1825 #. translators: %s: number of seconds 1826 #: includes/admin/views/verification.php:135 1827 #, php-format 1828 msgid "The order is not created and no payment is taken. They can ask for a new code after %s seconds, correct their email address, or close the popup and keep their cart." 1829 msgstr "" 1830 1831 #: includes/admin/views/verification.php:136 1832 msgid "Which email is sent?" 1833 msgstr "" 1834 1835 #: includes/admin/views/verification.php:139 1836 msgid "Checkout OTP email template" 1837 msgstr "" 1838 1839 #. translators: %s: link to the email template 1840 #: includes/admin/views/verification.php:139 1841 #, php-format 1842 msgid "The %s." 1843 msgstr "" 1844 1845 #: includes/admin/views/verification.php:141 1846 msgid "The Checkout OTP email template." 1847 msgstr "" 1848 1849 #: includes/admin/views/verification.php:148 1850 msgid "Compatibility" 1851 msgstr "" 1852 1853 #: includes/admin/views/verification.php:150 1854 msgid "Your checkout page" 1855 msgstr "" 1856 1857 #: includes/admin/views/verification.php:153 1858 #: includes/admin/views/verification.php:155 1859 msgid "Supported" 1860 msgstr "" 1861 1862 #: includes/admin/views/verification.php:153 1863 msgid "The code popup opens when Place order is clicked." 1864 msgstr "" 1865 1866 #: includes/admin/views/verification.php:155 1867 msgid "Pages using the [woocommerce_checkout] shortcode." 1868 msgstr "" 1869 1870 #: includes/admin/views/verification.php:156 1871 msgid "Payment methods" 1872 msgstr "" 1873 1874 #: includes/admin/views/verification.php:157 1875 msgid "Test yours first" 1876 msgstr "" 1877 1878 #: includes/admin/views/verification.php:157 1879 msgid "Tested with Cash on delivery and Direct bank transfer. Other payment gateways have not been tested; place a test order with yours before turning this on." 1880 msgstr "" 1881 1882 #: includes/admin/views/verification.php:158 1883 msgid "Express payment buttons" 1884 msgstr "" 1885 1886 #: includes/admin/views/verification.php:159 1887 msgid "Buttons such as Apple Pay or Google Pay outside the checkout form cannot show the popup. While verification is on, those orders are refused with \"Please verify your email address to place your order.\" and the customer uses the checkout form instead." 1888 msgstr "" 1889 1890 #: includes/admin/views/verification.php:159 1891 msgid "Not supported" 1892 msgstr "" 1893 1894 #: includes/admin/views/verification.php:161 1895 msgid "High-performance order storage (supported)" 1896 msgstr "" 1897 1898 #: includes/admin/views/verification.php:161 1899 msgid "WordPress posts storage (supported)" 1900 msgstr "" 1901 1902 #: includes/admin/views/verification.php:163 1903 msgid "Ready" 1904 msgstr "" 1905 1906 #: includes/admin/views/verification.php:163 1907 msgid "Update pending: checkout is blocked until it completes" 1908 msgstr "" 1909 1910 #: includes/admin/views/verification.php:166 1911 msgid "Your checkout page uses neither the Checkout block nor the [woocommerce_checkout] shortcode. Verification is still enforced on the server, but the code popup may not appear. Test before enabling." 1912 msgstr "" 1913 1914 #: includes/class-ajax.php:83 1915 msgid "Registration is not available." 1916 msgstr "" 1917 1918 #. translators: %d: seconds 1919 #: includes/class-ajax.php:90 1920 #, php-format 1921 msgid "Please wait %d seconds before requesting a new code." 1922 msgstr "" 1923 1924 #: includes/class-ajax.php:126 1925 msgid "Password reset by code is not available. Please use the standard lost password link." 1926 msgstr "" 1927 1928 #: includes/class-ajax.php:174 1929 msgid "This verification is not available." 1930 msgstr "" 1931 1932 #: includes/class-checkout.php:77 1933 msgid "Please verify your email address to place your order." 1934 msgstr "" 1935 1936 #: includes/class-checkout.php:86 1937 msgid "Email verification is temporarily unavailable, so orders cannot be placed right now. Please try again shortly." 1938 msgstr "" 1939 1940 #: includes/class-checkout.php:151 includes/class-checkout.php:165 1941 msgid "Your session has expired. Please refresh the page and try again." 1942 msgstr "" 1943 1944 #: includes/class-checkout.php:153 1945 msgid "Email verification is not required." 1946 msgstr "" 1947 1948 #: includes/class-checkout.php:157 1949 msgid "Please enter a valid billing email address first." 1950 msgstr "" 1951 1952 #: includes/class-checkout.php:163 1953 msgid "Too many requests. Please try again later." 1954 msgstr "" 1955 1956 #: includes/class-checkout.php:169 1957 msgid "A code was already sent. Please check your inbox." 1958 msgstr "" 1959 1960 #: includes/class-checkout.php:171 1961 msgid "Too many codes were requested for this email. Please try again later." 1962 msgstr "" 1963 1964 #: includes/class-checkout.php:179 1965 msgid "We could not send the verification email. Please check the address and try again." 1966 msgstr "" 1967 1968 #: includes/class-checkout.php:192 1969 msgid "Too many attempts. Please try again later." 1970 msgstr "" 1971 1972 #: includes/class-checkout.php:199 includes/class-checkout.php:254 1973 msgid "This code has expired. Please request a new one." 1974 msgstr "" 1975 1976 #: includes/class-checkout.php:200 1977 msgid "Too many incorrect attempts. Please request a new code." 1978 msgstr "" 1979 1980 #: includes/class-checkout.php:201 includes/class-checkout.php:207 1981 msgid "That code is not correct. Please try again." 1982 msgstr "" 1983 1984 #: includes/class-checkout.php:239 1985 msgid "Verify your email" 1986 msgstr "" 1987 1988 #. translators: %s: masked email address 1989 #: includes/class-checkout.php:241 1990 #, php-format 1991 msgid "We sent a 6-digit code to %s. Enter it below to place your order." 1992 msgstr "" 1993 1994 #: includes/class-checkout.php:242 1995 msgid "Sending your code…" 1996 msgstr "" 1997 1998 #. translators: %d: position of the digit (1-6) 1999 #. translators: %d: digit position 2000 #: includes/class-checkout.php:244 templates/modal.php:14 2001 #, php-format 2002 msgid "Digit %d of 6" 2003 msgstr "" 2004 2005 #: includes/class-checkout.php:245 2006 msgid "Verify and place order" 2007 msgstr "" 2008 2009 #: includes/class-checkout.php:246 2010 msgid "Verifying…" 2011 msgstr "" 2012 2013 #: includes/class-checkout.php:247 2014 msgid "Email verified. Placing your order…" 2015 msgstr "" 2016 2017 #: includes/class-checkout.php:248 2018 msgid "Resend code" 2019 msgstr "" 2020 2021 #. translators: %d: seconds 2022 #: includes/class-checkout.php:250 2023 #, php-format 2024 msgid "Resend code in %ds" 2025 msgstr "" 2026 2027 #: includes/class-checkout.php:251 naimur-email-otp-for-woocommerce.php:119 2028 msgid "A new code has been sent." 2029 msgstr "" 2030 2031 #. translators: %s: time like 4:59 2032 #: includes/class-checkout.php:253 2033 #, php-format 2034 msgid "Code expires in %s" 2035 msgstr "" 2036 2037 #: includes/class-checkout.php:255 2038 msgid "Change email" 2039 msgstr "" 2040 2041 #: includes/class-checkout.php:257 templates/modal.php:6 2042 msgid "Close" 2043 msgstr "" 2044 2045 #: includes/class-checkout.php:258 naimur-email-otp-for-woocommerce.php:118 2046 msgid "Please enter all 6 digits." 2047 msgstr "" 2048 2049 #: includes/class-checkout.php:259 2050 msgid "Something went wrong. Please check your connection and try again." 2051 msgstr "" 2052 2053 #: includes/class-checkout.php:260 2054 msgid "Try again" 2055 msgstr "" 2056 2057 #: includes/class-emails.php:31 2058 msgid "Test" 2059 msgstr "" 2060 2061 #: includes/class-templates.php:23 2062 msgid "Your store name" 2063 msgstr "" 2064 2065 #: includes/class-templates.php:24 2066 msgid "Your store address (URL)" 2067 msgstr "" 2068 2069 #: includes/class-templates.php:25 2070 msgid "Customer name (or \"there\" when unknown)" 2071 msgstr "" 2072 2073 #: includes/class-templates.php:26 2074 msgid "Customer email address" 2075 msgstr "" 2076 2077 #: includes/class-templates.php:27 2078 msgid "The 6-digit code (required)" 2079 msgstr "" 2080 2081 #: includes/class-templates.php:28 2082 msgid "Minutes until the code expires" 2083 msgstr "" 2084 2085 #: includes/class-templates.php:29 2086 msgid "Support email address" 2087 msgstr "" 2088 2089 #: includes/class-templates.php:30 2090 msgid "Store logo image (or the store name when no logo is set)" 2091 msgstr "" 2092 2093 #: includes/class-templates.php:31 2094 msgid "Brand accent colour (hex)" 2095 msgstr "" 2096 2097 #: includes/class-templates.php:32 2098 msgid "Preheader text (inbox preview line)" 2099 msgstr "" 2100 2101 #: includes/class-templates.php:39 2102 msgid "Your {{site_name}} verification code" 2103 msgstr "" 2104 2105 #: includes/class-templates.php:40 2106 msgid "Use this code to confirm your email address." 2107 msgstr "" 2108 2109 #: includes/class-templates.php:41 2110 msgid "Confirm your email address" 2111 msgstr "" 2112 2113 #: includes/class-templates.php:42 2114 msgid "Hi {{customer_name}}, use the code below to finish creating your {{site_name}} account." 2115 msgstr "" 2116 2117 #: includes/class-templates.php:45 2118 msgid "Your {{site_name}} password reset code" 2119 msgstr "" 2120 2121 #: includes/class-templates.php:46 2122 msgid "Use this code to reset your password." 2123 msgstr "" 2124 2125 #: includes/class-templates.php:47 2126 msgid "Reset your password" 2127 msgstr "" 2128 2129 #: includes/class-templates.php:48 2130 msgid "We received a request to reset the password for {{customer_email}}. Enter the code below to continue." 2131 msgstr "" 2132 2133 #: includes/class-templates.php:51 2134 msgid "Your {{site_name}} checkout verification code" 2135 msgstr "" 2136 2137 #: includes/class-templates.php:52 2138 msgid "Use this code to confirm your email and place your order." 2139 msgstr "" 2140 2141 #: includes/class-templates.php:53 2142 msgid "Verify your email to place your order" 2143 msgstr "" 2144 2145 #: includes/class-templates.php:54 2146 msgid "Hi {{customer_name}}, enter the code below on the checkout page to confirm {{customer_email}} and complete your order." 2147 msgstr "" 2148 2149 #: includes/class-templates.php:63 2150 msgid "This code expires in {{expiry_minutes}} minutes and can be used once." 2151 msgstr "" 2152 2153 #: includes/class-templates.php:64 2154 msgid "If you did not request this code, you can ignore this email. Never share this code with anyone. We will never ask you for it." 2155 msgstr "" 2156 2157 #: includes/class-templates.php:65 2158 msgid "Need help? Contact us at {{support_email}}." 2159 msgstr "" 2160 2161 #: includes/class-templates.php:159 2162 msgid "The email subject is required." 2163 msgstr "" 2164 2165 #: includes/class-templates.php:160 2166 msgid "The email subject must be 200 characters or fewer." 2167 msgstr "" 2168 2169 #: includes/class-templates.php:161 2170 msgid "The preheader must be 255 characters or fewer." 2171 msgstr "" 2172 2173 #: includes/class-templates.php:162 2174 msgid "The HTML is too large (maximum 100 KB)." 2175 msgstr "" 2176 2177 #: includes/class-templates.php:165 2178 msgid "The HTML must contain the {{otp_code}} placeholder, otherwise customers will not receive their code." 2179 msgstr "" 2180 2181 #: includes/class-templates.php:168 2182 msgid "Some HTML was removed for security: scripts, forms, event handlers, unsafe links or unsupported tags are not allowed in emails." 2183 msgstr "" 2184 2185 #: includes/class-templates.php:228 2186 msgid "there" 2187 msgstr "" 2188 2189 #: includes/class-users.php:12 2190 msgid "An account is already registered with your email address." 2191 msgstr "" 2192 2193 #: naimur-email-otp-for-woocommerce.php:110 2194 msgid "Processing..." 2195 msgstr "" 2196 2197 #: naimur-email-otp-for-woocommerce.php:111 2198 msgid "Verified! Redirecting..." 2199 msgstr "" 2200 2201 #: naimur-email-otp-for-woocommerce.php:112 2202 msgid "An error occurred. Please try again." 2203 msgstr "" 2204 2205 #: naimur-email-otp-for-woocommerce.php:113 2206 msgid "Resend in" 2207 msgstr "" 2208 2209 #: naimur-email-otp-for-woocommerce.php:115 2210 msgid "Please fill in all fields." 2211 msgstr "" 2212 2213 #: naimur-email-otp-for-woocommerce.php:117 2214 msgid "The passwords do not match." 2215 msgstr "" 2216 2217 #: naimur-email-otp-for-woocommerce.php:120 templates/modal.php:19 2218 msgid "Verify Code" 2219 msgstr "" 2220 2221 #: naimur-email-otp-for-woocommerce.php:121 2222 msgid "Your password has been updated. Please sign in." 2223 msgstr "" 2224 2225 #: naimur-email-otp-for-woocommerce.php:134 2226 msgid "Settings" 2227 msgstr "" 2228 2229 #: templates/modal.php:9 2230 msgid "Check your email" 2231 msgstr "" 2232 2233 #: templates/modal.php:10 36 2234 msgid "Enter the 6-digit code sent to" 37 2235 msgstr "" 38 2236 39 #: naimur-email-otp-for-woocommerce.php:65 40 #: templates/modal.php:16 41 msgid "Resend code" 42 msgstr "" 43 44 #: naimur-email-otp-for-woocommerce.php:66 45 #: templates/modal.php:15 46 msgid "Verify & Create Account" 47 msgstr "" 48 49 #: naimur-email-otp-for-woocommerce.php:67 50 msgid "Invalid or expired code. Try again." 51 msgstr "" 52 53 #: naimur-email-otp-for-woocommerce.php:78 54 msgid "Settings" 55 msgstr "" 56 57 #: naimur-email-otp-for-woocommerce.php:79 58 msgid "Logs" 59 msgstr "" 60 61 #: includes/class-ajax.php:19 62 msgid "Invalid form data." 63 msgstr "" 64 65 #: includes/class-ajax.php:22 66 msgid "Email already registered. Please log in." 67 msgstr "" 68 69 #: includes/class-ajax.php:28 70 msgid "Security verification failed. Please try again." 71 msgstr "" 72 73 #: includes/class-ajax.php:41 74 msgid "Failed to send email. Contact support." 75 msgstr "" 76 77 #: includes/class-ajax.php:44 78 msgid "Code sent. Check your inbox." 79 msgstr "" 80 81 #: includes/class-ajax.php:55 82 msgid "No pending verification. Please resend code." 83 msgstr "" 84 85 #: includes/class-ajax.php:63 86 msgid "Too many attempts. Start over." 87 msgstr "" 88 89 #: includes/class-ajax.php:67 90 msgid "Invalid or expired code." 91 msgstr "" 92 93 #: includes/class-security.php:35 94 msgid "Please complete the security check." 95 msgstr "" 96 97 #: includes/class-security.php:43 98 msgid "Security verification failed." 99 msgstr "" 100 101 #: includes/class-settings.php:44 102 msgid "Customer Email OTP Verification for WooCommerce (v1.2.0)" 103 msgstr "" 104 105 #: includes/class-settings.php:49 106 msgid "Brand color (HEX)" 107 msgstr "" 108 109 #: includes/class-settings.php:53 110 msgid "Modern Login/Register UI" 111 msgstr "" 112 113 #: includes/class-settings.php:55 114 msgid "Enable custom modern design" 115 msgstr "" 116 117 #: includes/class-settings.php:56 118 msgid "Select My Account page" 119 msgstr "" 120 121 #: includes/class-settings.php:61 122 msgid "Captcha Provider" 123 msgstr "" 124 125 #: includes/class-settings.php:64 126 msgid "None" 127 msgstr "" 128 129 #: includes/class-settings.php:65 130 msgid "Google reCAPTCHA v2" 131 msgstr "" 132 133 #: includes/class-settings.php:66 134 msgid "Cloudflare Turnstile" 135 msgstr "" 136 137 #: includes/class-settings.php:71 138 msgid "Google reCAPTCHA Keys" 139 msgstr "" 140 141 #: includes/class-settings.php:78 142 msgid "Cloudflare Turnstile Keys" 143 msgstr "" 144 145 #: includes/class-settings.php:85 146 msgid "Login header HTML" 147 msgstr "" 148 149 #: includes/class-settings.php:89 150 msgid "Register header HTML" 151 msgstr "" 152 153 #: includes/class-settings.php:95 154 msgid "View Logs" 155 msgstr "" 156 157 #: includes/class-users.php:10 158 msgid "Authenticated" 159 msgstr "" 160 161 #: templates/modal.php:4 162 msgid "Close" 163 msgstr "" 164 165 #: templates/modal.php:6 166 msgid "Verify your email" 167 msgstr "" 168 169 #: templates/woocommerce/myaccount/form-login.php:13 170 #: templates/woocommerce/myaccount/form-login.php:55 171 msgid "Email address" 172 msgstr "" 173 174 #: templates/woocommerce/myaccount/form-login.php:17 175 #: templates/woocommerce/myaccount/form-login.php:59 2237 #: templates/modal.php:12 2238 msgid "Verification code" 2239 msgstr "" 2240 2241 #: templates/modal.php:23 2242 msgid "Didn't get it?" 2243 msgstr "" 2244 2245 #: templates/modal.php:24 2246 msgid "Resend Code" 2247 msgstr "" 2248 2249 #: templates/modal.php:27 2250 msgid "Tip: you can paste the whole code into the first box. Check your spam folder if it has not arrived." 2251 msgstr "" 2252 2253 #: templates/woocommerce/myaccount/form-login.php:14 2254 msgid "Welcome Back" 2255 msgstr "" 2256 2257 #: templates/woocommerce/myaccount/form-login.php:15 2258 msgid "Sign in to your account" 2259 msgstr "" 2260 2261 #: templates/woocommerce/myaccount/form-login.php:20 2262 #: templates/woocommerce/myaccount/form-login.php:58 2263 msgid "Email Address" 2264 msgstr "" 2265 2266 #: templates/woocommerce/myaccount/form-login.php:24 2267 #: templates/woocommerce/myaccount/form-login.php:63 176 2268 msgid "Password" 177 2269 msgstr "" 178 2270 2271 #: templates/woocommerce/myaccount/form-login.php:29 2272 msgid "Remember me" 2273 msgstr "" 2274 179 2275 #: templates/woocommerce/myaccount/form-login.php:30 180 msgid "Remember me"181 msgstr ""182 183 #: templates/woocommerce/myaccount/form-login.php:31184 2276 msgid "Forgot password?" 185 2277 msgstr "" 186 2278 187 #: templates/woocommerce/myaccount/form-login.php:36 2279 #: templates/woocommerce/myaccount/form-login.php:34 2280 msgid "Sign In" 2281 msgstr "" 2282 2283 #: templates/woocommerce/myaccount/form-login.php:40 2284 msgid "Don't have an account?" 2285 msgstr "" 2286 2287 #: templates/woocommerce/myaccount/form-login.php:40 2288 msgid "Sign up" 2289 msgstr "" 2290 2291 #: templates/woocommerce/myaccount/form-login.php:49 2292 msgid "Create Account" 2293 msgstr "" 2294 2295 #: templates/woocommerce/myaccount/form-login.php:50 2296 msgid "We'll email you a 6-digit code to confirm your address." 2297 msgstr "" 2298 2299 #: templates/woocommerce/myaccount/form-login.php:54 2300 msgid "Full Name" 2301 msgstr "" 2302 2303 #: templates/woocommerce/myaccount/form-login.php:67 2304 msgid "Confirm Password" 2305 msgstr "" 2306 2307 #: templates/woocommerce/myaccount/form-login.php:73 2308 msgid "Sign Up & Verify" 2309 msgstr "" 2310 2311 #: templates/woocommerce/myaccount/form-login.php:78 2312 msgid "Already have an account?" 2313 msgstr "" 2314 2315 #: templates/woocommerce/myaccount/form-login.php:78 188 2316 msgid "Sign in" 189 2317 msgstr "" 190 2318 191 #: templates/woocommerce/myaccount/form-login.php:39 192 msgid "Don't have an account?" 193 msgstr "" 194 195 #: templates/woocommerce/myaccount/form-login.php:39 196 msgid "Sign up" 197 msgstr "" 198 199 #: templates/woocommerce/myaccount/form-login.php:51 200 msgid "Full Name" 201 msgstr "" 202 203 #: templates/woocommerce/myaccount/form-login.php:63 204 msgid "Confirm Password" 205 msgstr "" 206 207 #: templates/woocommerce/myaccount/form-login.php:77 208 msgid "Create Account" 209 msgstr "" 210 211 #: templates/woocommerce/myaccount/form-login.php:81 212 msgid "Back to sign in" 213 msgstr "" 2319 #: templates/woocommerce/myaccount/form-login.php:87 2320 msgid "Reset Password" 2321 msgstr "" 2322 2323 #: templates/woocommerce/myaccount/form-login.php:88 2324 msgid "Enter your email and we'll send you a 6-digit code." 2325 msgstr "" 2326 2327 #: templates/woocommerce/myaccount/form-login.php:92 2328 msgid "Registered Email" 2329 msgstr "" 2330 2331 #: templates/woocommerce/myaccount/form-login.php:97 2332 msgid "Send Verification Code" 2333 msgstr "" 2334 2335 #: templates/woocommerce/myaccount/form-login.php:102 2336 msgid "Back to Sign in" 2337 msgstr "" 2338 2339 #: templates/woocommerce/myaccount/form-login.php:109 2340 msgid "Set New Password" 2341 msgstr "" 2342 2343 #: templates/woocommerce/myaccount/form-login.php:110 2344 msgid "Choose a new password for your account." 2345 msgstr "" 2346 2347 #: templates/woocommerce/myaccount/form-login.php:115 2348 msgid "New Password" 2349 msgstr "" 2350 2351 #: templates/woocommerce/myaccount/form-login.php:119 2352 msgid "Confirm New Password" 2353 msgstr "" 2354 2355 #: templates/woocommerce/myaccount/form-login.php:124 2356 msgid "Update Password" 2357 msgstr "" -
naimur-email-otp-for-woocommerce/trunk/naimur-email-otp-for-woocommerce.php
r3780003 r3780739 3 3 * Plugin Name: Naimur Email OTP Verification for WooCommerce 4 4 * Plugin URI: https://naimurrahmannahid.com/naimur-otp-plugin/ 5 * Description: Verify WooCommerce customer emails with a 6-digit OTP for registration and password reset, with a modern, speed-optimized My Account UI.6 * Version: 2.1. 15 * Description: Verify WooCommerce customer emails with a 6-digit OTP for registration, password reset and (optionally) checkout, with editable email templates and a modern admin. 6 * Version: 2.1.2 7 7 * Requires at least: 6.0 8 8 * Tested up to: 7.1 9 9 * Requires PHP: 7.4 10 10 * Requires Plugins: woocommerce 11 * WC tested up to: 1 0.811 * WC tested up to: 11.2 12 12 * Author: Naimur Rahman Nahid 13 13 * Author URI: https://naimurrahmannahid.com/ … … 17 17 if ( ! defined( 'ABSPATH' ) ) { exit; } 18 18 19 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION', '2.1. 1');20 define('CEOVW_DB_VERSION', '2.1. 1');19 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION', '2.1.2'); 20 define('CEOVW_DB_VERSION', '2.1.2'); 21 21 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH', plugin_dir_path(__FILE__)); 22 22 define('NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL', plugin_dir_url(__FILE__)); 23 23 24 24 // Load all files exactly matching your folder structure 25 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-options.php'; 26 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-templates.php'; 25 27 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/helpers.php'; 26 28 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-ajax.php'; … … 31 33 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-settings.php'; 32 34 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-users.php'; 35 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/class-checkout.php'; 36 37 CEOVW_Checkout::init(); 38 if ( is_admin() ) { 39 require_once NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_PATH.'includes/admin/class-admin.php'; 40 CEOVW_Admin::init(); 41 } 42 43 // Declare compatibility with WooCommerce features this plugin was tested with 44 add_action( 'before_woocommerce_init', function() { 45 if ( class_exists( '\\Automattic\\WooCommerce\\Utilities\\FeaturesUtil' ) ) { 46 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); 47 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true ); 48 } 49 }); 33 50 34 51 // Activation Hooks for DB & Cron … … 69 86 // Enqueue Highly Optimized Assets 70 87 add_action('wp_enqueue_scripts', function() { 71 $override_enabled = (bool) get_option('naimur-email-otp-for-woocommerce_override_enabled', false);72 $my_page_id = (int) get_option('naimur-email-otp-for-woocommerce_myaccount_page_id', 0);88 $override_enabled = (bool) CEOVW_Options::get('modern_ui'); 89 $my_page_id = (int) CEOVW_Options::get('myaccount_page_id'); 73 90 74 91 $should_enqueue = false; … … 80 97 wp_enqueue_style('ceovw-modal-css', NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL.'assets/css/modal.css', [], NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION); 81 98 wp_enqueue_style('ceovw-corporate-ui', NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL.'assets/css/override-myaccount.css', [], NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION); 82 83 // Captcha Integration 84 $captcha_type = get_option('naimur-email-otp-for-woocommerce_captcha_type', 'none'); 85 if ( $captcha_type === 'recaptcha' && get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key') ) { 86 wp_enqueue_script('google-recaptcha','https://www.google.com/recaptcha/api.js',[],NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION,true); 87 wp_enqueue_script('ceovw-boot-app', NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_URL.'assets/js/boot.js', ['jquery'], NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION, true); 88 } elseif ( $captcha_type === 'turnstile' && get_option('naimur-email-otp-for-woocommerce_turnstile_site_key') ) { 89 wp_enqueue_script('cloudflare-turnstile','https://challenges.cloudflare.com/turnstile/v0/api.js',[],NAIMUR_EMAIL_OTP_FOR_WOOCOMMERCE_VERSION,true); 90 } 99 100 // CAPTCHA is not loaded: earlier versions displayed it without server-side verification (see Security & Rate Limits) 91 101 92 102 // Main App Logic … … 102 112 'error' => esc_html__('An error occurred. Please try again.', 'naimur-email-otp-for-woocommerce'), 103 113 'resend_in' => esc_html__('Resend in', 'naimur-email-otp-for-woocommerce'), 114 // Shown with .text() in JavaScript, so plain (unescaped) strings are safe here. 115 'required' => __('Please fill in all fields.', 'naimur-email-otp-for-woocommerce'), 116 'badEmail' => __('Please enter a valid email address.', 'naimur-email-otp-for-woocommerce'), 117 'mismatch' => __('The passwords do not match.', 'naimur-email-otp-for-woocommerce'), 118 'enterCode' => __('Please enter all 6 digits.', 'naimur-email-otp-for-woocommerce'), 119 'codeSent' => __('A new code has been sent.', 'naimur-email-otp-for-woocommerce'), 120 'verifyBtn' => __('Verify Code', 'naimur-email-otp-for-woocommerce'), 121 'updated' => __('Your password has been updated. Please sign in.', 'naimur-email-otp-for-woocommerce'), 104 122 ], 105 123 'settings' => [ 106 'captcha' => sanitize_text_field($captcha_type), 107 'site_key' => $captcha_type === 'recaptcha' ? get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key') : get_option('naimur-email-otp-for-woocommerce_turnstile_site_key'), 124 'resend' => [ 125 'register' => CEOVW_Options::resend_seconds('registration'), 126 'forgot' => CEOVW_Options::resend_seconds('forgot'), 127 ], 108 128 ] 109 129 ]); … … 112 132 // Settings Links 113 133 add_filter('plugin_action_links_'.plugin_basename(__FILE__), function($links){ 114 $links[] = '<a href="'.esc_url(admin_url('admin.php?page= naimur-email-otp-for-woocommerce-settings')).'">'.esc_html__('Settings', 'naimur-email-otp-for-woocommerce').'</a>';134 $links[] = '<a href="'.esc_url(admin_url('admin.php?page=ceovw-dashboard')).'">'.esc_html__('Settings', 'naimur-email-otp-for-woocommerce').'</a>'; 115 135 return $links; 116 136 }); -
naimur-email-otp-for-woocommerce/trunk/readme.txt
r3780003 r3780739 2 2 Contributors: naimurrahmannahid 3 3 Donate link: https://buymeacoffee.com/naimurrahmannahid 4 Tags: woocommerce, otp, email verification, c ustomer4 Tags: woocommerce, otp, email verification, checkout, customer 5 5 Requires at least: 6.0 6 6 Tested up to: 7.1 7 7 Requires PHP: 7.4 8 Stable tag: 2.1. 18 Stable tag: 2.1.2 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Verify WooCommerce customer emails with a 6-digit OTP during registration and secure password recovery.12 Verify WooCommerce customer emails with a 6-digit code at registration, password reset and (optionally) checkout. 13 13 14 14 == Description == 15 15 16 **Naimur Email OTP Verification for WooCommerce** confirms that a customer really owns their email address before an account is created, and uses the same one-time code (OTP) to protect password recovery.16 **Naimur Email OTP Verification for WooCommerce** confirms that a customer really owns their email address. A one-time code (OTP) is emailed and must be entered before the action completes. 17 17 18 18 The OTP is used for: … … 20 20 * New customer registration 21 21 * Password reset (Forgot Password) 22 23 The plugin does not add OTP to login or checkout. 22 * Checkout (optional, off by default): the order is placed only after the billing email is verified 23 24 The plugin does not add OTP to login. 24 25 25 26 = Key features = 26 27 27 28 * 6-digit email OTP for WooCommerce customer registration 28 * OTP-based Forgot Password flow with a single-use, short-lived reset authorization 29 * Modern, responsive My Account sign-in, registration and password recovery forms 30 * OTP entry with auto-advance, paste support and a resend button with countdown 31 * Three bundled email templates plus your own custom HTML template, with a live preview in the admin 32 * Email and activity logs in the admin (OTP codes are never shown; email addresses are masked) 33 * Server-side protection: OTP attempt limits, code expiry and rate limits for sending, verification and password reset 34 * Translation-ready, with developer filters such as `ceovw_otp_expiry`, `ceovw_rate_limit` and `ceovw_client_ip` 29 * OTP-based Forgot Password flow with a single-use, short-lived reset authorization (no automatic login after a reset) 30 * Optional checkout email verification for the Checkout block and the classic checkout, for guests and (optionally) logged-in customers. Verification is enforced on the server before the order is created, so no payment is taken from an unverified checkout 31 * Accessible verification popup at checkout: 6-digit input with paste support, countdown, resend with cooldown, change email, mobile bottom-sheet layout 32 * Three responsive email templates (registration, password reset, checkout) that use your store logo, name and brand colour 33 * HTML template editor with syntax highlighting, placeholders, a sandboxed live preview (desktop and mobile) and test emails 34 * "Naimur OTP" admin area: overview with setup checklist, one Verification page with Registration, Password Reset and Checkout tabs, email templates, security limits, branding, activity and diagnostics, searchable help and a settings finder 35 * Server-side protection: hashed codes, attempt limits, expiry, single use, per-IP and per-email rate limits, purpose and session binding 36 * Compatible with WooCommerce High-Performance Order Storage (HPOS) and the Cart and Checkout blocks 37 * Translation-ready, with developer filters such as `ceovw_otp_expiry`, `ceovw_checkout_auth_ttl`, `ceovw_rate_limit` and `ceovw_client_ip` 35 38 36 39 = How it works = 37 40 38 1. The customer fills in the registration form and receives a 6-digit code by email.39 2. The account is created only after the correct code is entered.40 3. For a forgotten password, the customer requests a code, verifies it, and then sets a new password. After the reset they sign in with the new password; they are not logged in automatically.41 1. Registration: the customer fills in the registration form and receives a 6-digit code by email. The account is created only after the correct code is entered. 42 2. Password reset: the customer requests a code, verifies it and sets a new password, then signs in with it. 43 3. Checkout (when enabled): after clicking Place order, a popup asks for the code sent to the billing email. Once verified, the order is placed automatically. Changing the email requires a new code. 41 44 42 45 = Documentation = 43 46 44 Full documentation for setup, usage and customization: https://naimurrahmannahid.com/naimur-otp-plugin/47 Built-in documentation is available under **Naimur OTP → Help & Documentation**. More guides: https://naimurrahmannahid.com/naimur-otp-plugin/ 45 48 46 49 Video tutorial: … … 48 51 [youtube https://youtu.be/AehMWNLxkVk] 49 52 50 == External services ==51 52 This plugin can optionally display a Google reCAPTCHA or Cloudflare Turnstile widget on the forms of its My Account UI. When enabled, the provider's script is loaded in the visitor's browser and the visitor's browser communicates directly with the provider. In version 2.1.1 the plugin's server does not send the CAPTCHA response to the provider for verification.53 54 **Google reCAPTCHA:**55 When reCAPTCHA is enabled in the plugin settings and a site key is configured, the visitor's browser loads the reCAPTCHA script from Google, and Google may receive:56 * The IP address of the user57 * Browser and usage information collected by the reCAPTCHA widget58 59 Service Provider:60 * Google reCAPTCHA – Provided by Google LLC61 * Terms of Service: https://www.google.com/recaptcha/terms/62 * Privacy Policy: https://policies.google.com/privacy/63 64 **Cloudflare Turnstile:**65 When Turnstile is enabled and a site key is configured, the visitor's browser loads the Turnstile script from Cloudflare. Similar to reCAPTCHA, IP and browser telemetry may be processed by Cloudflare.66 67 Service provider:68 * Cloudflare Turnstile – provided by Cloudflare, Inc.69 * Terms of Service: https://www.cloudflare.com/website-terms/70 * Privacy Policy: https://www.cloudflare.com/privacypolicy/71 72 53 == Source code == 73 54 … … 78 59 * `assets/src/js/main.js` 79 60 80 This file contains the original source code that can be viewed, studied and modified by developers.61 All other JavaScript and CSS files (`assets/js/checkout-otp.js`, `assets/admin/admin.js` and the stylesheets) are shipped unminified. 81 62 82 63 == Installation == 83 64 84 65 1. In your WordPress admin, go to **Plugins → Add New**, search for "Naimur Email OTP Verification for WooCommerce", then install and activate it. WooCommerce must be installed and active. 85 2. Go to **WooCommerce → Email Verification**. 86 3. Set the email subject and choose an email template (or write your own custom HTML using the `[naimur_email_otp_code]` placeholder). The live preview shows the result. 87 4. To use the modern forms, tick **Enable custom modern design** and select your WooCommerce My Account page, then save. 66 2. Open **Naimur OTP → Verification**. In Step 1, turn on the customer login and registration forms and select your My Account page. 67 3. Open **Naimur OTP → General Settings** and set your sender name, sender email and logo. 68 4. Review the emails under **Naimur OTP → Email Templates** and send yourself a test email. 69 5. Optional: turn on **Naimur OTP → Verification → Checkout OTP** after placing a test order yourself. 88 70 5. Make sure your site can send email. Using an SMTP plugin is recommended for reliable delivery. 89 71 … … 91 73 92 74 = Where is the OTP used? = 93 For new customer registration and for password reset (Forgot Password). The plugin does not add OTP to login or checkout. 94 95 = Can I customize the verification email? = 96 Yes. You can set the email subject, choose one of three bundled templates, or write your own HTML template. Use the `[naimur_email_otp_code]` placeholder where the code should appear. The settings page shows a live preview. 75 For new customer registration, for password reset and, if you enable it, at checkout. The plugin does not add OTP to login. 76 77 = Does checkout verification work with the Checkout block? = 78 Yes. It supports the Checkout block (Store API) and the classic `[woocommerce_checkout]` shortcode. The check runs on the server when the order is placed, so it cannot be skipped by disabling JavaScript or calling the API directly. 79 80 = Can a customer be charged before verifying? = 81 No. The order is not created until the billing email is verified, so payment is never requested for an unverified checkout. If a payment fails, the customer can retry payment for the same order and the same email without a new code. 82 83 = What about express payment buttons (Apple Pay, Google Pay)? = 84 Express buttons outside the checkout form skip the verification popup. While checkout verification is required, such orders are refused with the message "Please verify your email address to place your order." and the customer can complete the purchase through the checkout form. Turn checkout verification off if you rely on express-only payments. 85 86 = Can I customize the verification emails? = 87 Yes. Each email has its own subject, preheader and HTML. Use placeholders such as `{{otp_code}}`, `{{customer_name}}`, `{{site_name}}`, `{{expiry_minutes}}` and `{{support_email}}`. Scripts, forms, iframes and unsafe links are removed when you save, and PHP is never executed. 97 88 98 89 = How long is a code valid? = 99 Codes expire after 5 minutes and allow up to 5 verification attempts. Developers can change the expiry with the `ceovw_otp_expiry` filter.90 5 minutes by default, with 5 attempts. You can set expiry (1–30 minutes), resend delay and attempts separately for registration, password reset and checkout. 100 91 101 92 = Is the customer logged in after resetting their password? = 102 No. Since version 2.1.1 the customer is returnedto the sign-in form and signs in with the new password.93 No. The customer returns to the sign-in form and signs in with the new password. 103 94 104 95 = Does this plugin work with SMTP plugins? = 105 Yes. Emails are sent with WordPress's `wp_mail()`, so SMTP plugins that hook into `wp_mail()` (for example WP Mail SMTP, FluentSMTP or Post SMTP) handle delivery. 106 107 = Do reCAPTCHA and Cloudflare Turnstile protect the forms? =108 No t in version 2.1.1. The widgets can be displayed on the plugin's forms, but the CAPTCHA response is not verified on the server, so they do not currently block automated requests. OTP requests, OTP verification and password resets are protected by server-side rate limits instead. Server-side CAPTCHA verification is planned for a future release.96 Yes. Emails are sent with WordPress's `wp_mail()`, so SMTP plugins that hook into `wp_mail()` (for example WP Mail SMTP, FluentSMTP or Post SMTP) handle delivery. The sender name and address you set apply only to this plugin's emails. 97 98 = Is there CAPTCHA? = 99 No. Earlier versions could display reCAPTCHA or Turnstile without verifying it on the server, which gave no real protection, so it was removed in 2.1.2. The forms are protected by server-side rate limits and attempt limits. 109 100 110 101 = Does it slow down the site? = 111 The plugin's styles and scripts load only on the WooCommerce My Account page for visitors who are not logged in.102 No noticeable impact. The My Account scripts load only on the My Account page for visitors who are not logged in, the checkout script loads only on the checkout page when checkout verification is enabled, and admin assets load only on the plugin's own admin pages. 112 103 113 104 = My site is behind a proxy or CDN. Which IP address is used? = 114 105 For rate limiting and activity logs the plugin uses `REMOTE_ADDR` and does not trust forwarded headers. If your server sits behind a trusted proxy, return the real client IP through the `ceovw_client_ip` filter. 115 106 107 = What happens to my data if I delete the plugin? = 108 Pending codes, reset authorizations and logs are always removed. Settings and email templates are kept unless you turn on "Delete all plugin data" under General Settings. 109 116 110 == Screenshots == 117 111 118 1. Customer registration form in the modern My Account design. 119 2. Email OTP verification during customer registration. 120 3. Forgot Password: request a verification code by email. 121 4. Set a new password after the code is verified. 122 5. After a password reset the customer returns to the sign-in form (no automatic login). 123 6. Email subject and template selection with live preview. 124 7. Custom HTML email template with live preview. 125 8. Email and activity logs with masked email addresses and hidden OTP codes. 112 1. Overview with the status of each verification, email delivery and security health, a setup checklist and quick actions. 113 2. Checkout verification popup on the Checkout block. 114 3. Checkout verification popup on a phone. 115 4. Email template editor with syntax highlighting and live preview. 116 5. Checkout OTP tab: settings, how checkout verification works and compatibility. 117 6. Verification page with Registration, Password Reset and Checkout tabs. 118 7. Security settings: rate limits and always-on protections. 119 8. Checkout verification email. 120 9. Customer registration form on the My Account page. 121 10. Email OTP verification during customer registration. 122 11. Set a new password after the code is verified. 123 12. Activity & Diagnostics: test email, email log with masked addresses and system information. 126 124 127 125 == Changelog == 126 127 = 2.1.2 = 128 Added: 129 * Optional checkout email verification (off by default) for the Checkout block and classic checkout, for guests and logged-in customers. Enforced on the server before the order is created; verification is single-use and bound to the browser session, the exact email and the checkout purpose. 130 * Accessible checkout verification popup with paste support, countdown, resend cooldown, change email and a mobile layout. 131 * The registration and password reset code popup now matches the checkout popup: dialog semantics, Escape to close, focus kept inside, paste and one-time-code autofill, arrow and Backspace keys, and inline messages instead of browser alerts. 132 * New "Naimur OTP" admin menu with Overview, Verification (Registration, Password Reset and Checkout tabs), Email Templates, Security, Activity & Diagnostics, General Settings and Help & Documentation. 133 * Overview page with real status for each verification, email delivery and security health, a setup checklist and quick actions. 134 * Plain-language settings with help text, unsaved-changes warning, Discard changes, inline validation, a settings finder and contextual links to the documentation. 135 * Three new responsive email templates (registration, password reset, checkout) using your store logo and brand colour. 136 * HTML email template editor with syntax highlighting, placeholders, sandboxed live preview (desktop and mobile), reset and test email. 137 * Per-flow settings for code expiry, resend delay and attempts; adjustable rate limits within safe ranges. 138 * Sender name and email for this plugin's emails only, store logo, uninstall data retention option and activity log retention. 139 * Server-side resend cooldown for all flows. 140 * Declared compatibility with WooCommerce HPOS and the Cart and Checkout blocks. 141 Changed: 142 * Settings moved from WooCommerce → Email Verification to the new Naimur OTP menu. Existing settings are kept; old admin links redirect. 143 * Email HTML, security limits and general settings now require a site administrator (manage_options). Shop managers can still turn verification on or off and send test emails. 144 * A custom HTML template and custom subject from earlier versions are migrated to the registration and password reset templates. The bundled templates 1–3 are replaced by the new design. 145 * Password reset button now reads "Update Password" (the customer is not logged in automatically). 146 * When password reset or registration OTP is turned off, the standard WooCommerce links are shown instead. 147 Removed: 148 * reCAPTCHA and Cloudflare Turnstile widgets. They were never verified on the server. Saved keys are kept but unused, and no third-party scripts are loaded. 149 Security: 150 * All 2.1.1 protections retained: hashed codes, single-use reset authorization, atomic attempt counting, expiry, rate limits, cron cleanup, no automatic login after reset. 151 * Email HTML is filtered with a strict allowlist; placeholder values are escaped. 152 * Verification fails closed while a database upgrade is pending. 128 153 129 154 = 2.1.1 = … … 170 195 == Upgrade Notice == 171 196 197 = 2.1.2 = 198 Adds optional checkout email verification (off by default), a new Naimur OTP admin menu and editable responsive email templates. Settings are migrated automatically. reCAPTCHA/Turnstile display was removed. Requires WooCommerce. 199 172 200 = 2.1.1 = 173 201 Security release. Hardens password reset and OTP verification. Recommended for all users. After a password reset, customers now sign in with their new password. Pending password reset sessions started before the update must be restarted. -
naimur-email-otp-for-woocommerce/trunk/templates/modal.php
r3587442 r3780739 1 1 <?php defined('ABSPATH') || exit; ?> 2 2 3 <!-- OTP MODAL OVERLAY -->3 <!-- OTP MODAL OVERLAY (same visual language as the checkout popup) --> 4 4 <div id="ceovw-otp-modal" class="ceovw-modal-overlay" style="display:none;"> 5 <div class="ceovw-modal-content"> 6 <button class="ceovw-modal-close" aria-label="Close">×</button> 5 <div class="ceovw-modal-content" role="dialog" aria-modal="true" aria-labelledby="ceovw-otp-title" aria-describedby="ceovw-otp-desc"> 6 <button type="button" class="ceovw-modal-close" aria-label="<?php esc_attr_e('Close', 'naimur-email-otp-for-woocommerce'); ?>"><span aria-hidden="true">×</span></button> 7 <div class="ceovw-modal-icon" aria-hidden="true"><svg viewBox="0 0 48 48" width="34" height="34" fill="none"><rect x="6" y="11" width="36" height="26" rx="5" stroke="currentColor" stroke-width="2.5"/><path d="M8 15l16 11 16-11" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div> 7 8 <div class="ceovw-modal-header"> 8 <h3 ><?php esc_html_e('Verification Required', 'naimur-email-otp-for-woocommerce'); ?></h3>9 <p ><?php esc_html_e('Enter the 6-digit code sent to', 'naimur-email-otp-for-woocommerce'); ?> <strong id="ceovw-target-email"></strong></p>9 <h3 id="ceovw-otp-title"><?php esc_html_e('Check your email', 'naimur-email-otp-for-woocommerce'); ?></h3> 10 <p id="ceovw-otp-desc"><?php esc_html_e('Enter the 6-digit code sent to', 'naimur-email-otp-for-woocommerce'); ?> <strong id="ceovw-target-email"></strong></p> 10 11 </div> 11 <div class="ceovw-otp-inputs"> 12 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required autofocus> 13 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 14 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 15 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 16 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 17 <input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" class="ceovw-otp-box" required> 12 <div class="ceovw-otp-inputs" role="group" aria-label="<?php esc_attr_e('Verification code', 'naimur-email-otp-for-woocommerce'); ?>"> 13 <?php for ( $ceovw_i = 1; $ceovw_i <= 6; $ceovw_i++ ) : ?> 14 <input type="text" inputmode="numeric" pattern="[0-9]*" class="ceovw-otp-box" autocomplete="<?php echo 1 === $ceovw_i ? 'one-time-code' : 'off'; ?>" aria-label="<?php echo esc_attr( sprintf( /* translators: %d: digit position */ __( 'Digit %d of 6', 'naimur-email-otp-for-woocommerce' ), $ceovw_i ) ); ?>"> 15 <?php endfor; ?> 18 16 </div> 19 <div id="ceovw-otp-error" class="ceovw-error-msg" ></div>20 <button class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-verify">17 <div id="ceovw-otp-error" class="ceovw-error-msg" role="alert" aria-live="assertive"></div> 18 <button type="button" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-verify"> 21 19 <span class="btn-text"><?php esc_html_e('Verify Code', 'naimur-email-otp-for-woocommerce'); ?></span> 22 <span class="btn-loader" ></span>20 <span class="btn-loader" aria-hidden="true"></span> 23 21 </button> 24 22 <div class="ceovw-resend-wrapper"> 25 <button id="ceovw-btn-resend" class="ceovw-link" disabled><?php esc_html_e('Resend Code', 'naimur-email-otp-for-woocommerce'); ?></button> 26 <span id="ceovw-timer-text">(60s)</span> 23 <span class="ceovw-resend-q"><?php esc_html_e("Didn't get it?", 'naimur-email-otp-for-woocommerce'); ?></span> 24 <button type="button" id="ceovw-btn-resend" class="ceovw-link" disabled><?php esc_html_e('Resend Code', 'naimur-email-otp-for-woocommerce'); ?></button> 25 <span id="ceovw-timer-text"></span> 27 26 </div> 27 <p class="ceovw-modal-tip"><?php esc_html_e('Tip: you can paste the whole code into the first box. Check your spam folder if it has not arrived.', 'naimur-email-otp-for-woocommerce'); ?></p> 28 28 </div> 29 29 </div> -
naimur-email-otp-for-woocommerce/trunk/templates/woocommerce/myaccount/form-login.php
r3587442 r3780739 1 <?php 2 defined('ABSPATH') || exit; 1 <?php 2 defined('ABSPATH') || exit; 3 3 4 // ক্যাপচা টাইপ এবং সাইট কি (Site Key) ফেচ করা 5 $captcha_type = get_option('naimur-email-otp-for-woocommerce_captcha_type', 'none'); 6 $captcha_html = ''; 7 8 if ($captcha_type === 'recaptcha') { 9 $site_key = get_option('naimur-email-otp-for-woocommerce_recaptcha_site_key'); 10 if (!empty($site_key)) { 11 $captcha_html = '<div class="g-recaptcha" data-sitekey="' . esc_attr($site_key) . '" style="margin-bottom: 15px; display: flex; justify-content: center;"></div>'; 12 } 13 } elseif ($captcha_type === 'turnstile') { 14 $site_key = get_option('naimur-email-otp-for-woocommerce_turnstile_site_key'); 15 if (!empty($site_key)) { 16 $captcha_html = '<div class="cf-turnstile" data-sitekey="' . esc_attr($site_key) . '" style="margin-bottom: 15px; display: flex; justify-content: center;"></div>'; 17 } 18 } 4 $ceovw_reg_on = (bool) CEOVW_Options::get('registration_enabled'); 5 $ceovw_forgot_on = (bool) CEOVW_Options::get('forgot_enabled'); 19 6 ?> 20 7 21 <div id="ceovw-corporate-app" style="--ceovw-brand: <?php echo esc_attr( get_option('naimur-email-otp-for-woocommerce_brand_color', '#2563eb')); ?>;">8 <div id="ceovw-corporate-app" style="--ceovw-brand: <?php echo esc_attr(CEOVW_Options::get('brand_color')); ?>;"> 22 9 <div class="ceovw-app-card"> 23 10 24 11 <!-- 1. LOGIN VIEW --> 25 12 <div id="ceovw-view-login" class="ceovw-view-pane active"> 26 13 <div class="ceovw-header"> 27 14 <h2><?php esc_html_e('Welcome Back', 'naimur-email-otp-for-woocommerce'); ?></h2> 28 <p><?php esc_html_e(' Access your dashboard', 'naimur-email-otp-for-woocommerce'); ?></p>15 <p><?php esc_html_e('Sign in to your account', 'naimur-email-otp-for-woocommerce'); ?></p> 29 16 </div> 30 17 <form id="ceovw-form-login" method="post"> 31 18 <?php wp_nonce_field( 'woocommerce-login', 'woocommerce-login-nonce' ); ?> 32 19 <div class="ceovw-field-group"> 33 <label ><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label>34 <input type="email" name="username" required autocomplete="username" />20 <label for="ceovw-login-email"><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label> 21 <input type="email" id="ceovw-login-email" name="username" required autocomplete="username" /> 35 22 </div> 36 23 <div class="ceovw-field-group"> 37 <label ><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label>38 <input type="password" name="password" required autocomplete="current-password" />24 <label for="ceovw-login-pass"><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label> 25 <input type="password" id="ceovw-login-pass" name="password" required autocomplete="current-password" /> 39 26 </div> 40 41 <!-- ডাইনামিক ক্যাপচা রেন্ডার -->42 <?php echo $captcha_html; ?>43 27 44 28 <div class="ceovw-actions-row"> 45 29 <label class="ceovw-checkbox"><input type="checkbox" name="rememberme" value="forever" /> <?php esc_html_e('Remember me', 'naimur-email-otp-for-woocommerce'); ?></label> 46 < a href="#" data-target="forgot" class="ceovw-link ceovw-switch-view"><?php esc_html_e('Forgot password?', 'naimur-email-otp-for-woocommerce'); ?></a>30 <?php if ($ceovw_forgot_on) : ?><a href="#" data-target="forgot" class="ceovw-link ceovw-switch-view"><?php esc_html_e('Forgot password?', 'naimur-email-otp-for-woocommerce'); ?></a><?php else : ?><a href="<?php echo esc_url(wc_lostpassword_url()); ?>" class="ceovw-link"><?php esc_html_e('Forgot password?', 'naimur-email-otp-for-woocommerce'); ?></a><?php endif; ?> 47 31 </div> 32 <div class="ceovw-form-msg" role="status" aria-live="polite" hidden></div> 48 33 <button type="submit" name="login" class="ceovw-btn ceovw-btn-primary"> 49 34 <span class="btn-text"><?php esc_html_e('Sign In', 'naimur-email-otp-for-woocommerce'); ?></span> 50 <span class="btn-loader" ></span>35 <span class="btn-loader" aria-hidden="true"></span> 51 36 </button> 52 37 </form> 38 <?php if ($ceovw_reg_on) : ?> 53 39 <div class="ceovw-footer-nav"> 54 40 <p><?php esc_html_e("Don't have an account?", 'naimur-email-otp-for-woocommerce'); ?> <a href="#" data-target="register" class="ceovw-link ceovw-switch-view"><?php esc_html_e('Sign up', 'naimur-email-otp-for-woocommerce'); ?></a></p> 55 41 </div> 42 <?php endif; ?> 56 43 </div> 57 44 45 <?php if ($ceovw_reg_on) : ?> 58 46 <!-- 2. REGISTER VIEW --> 59 47 <div id="ceovw-view-register" class="ceovw-view-pane" style="display:none;"> 60 48 <div class="ceovw-header"> 61 49 <h2><?php esc_html_e('Create Account', 'naimur-email-otp-for-woocommerce'); ?></h2> 62 <p><?php esc_html_e( 'Join our platform today', 'naimur-email-otp-for-woocommerce'); ?></p>50 <p><?php esc_html_e("We'll email you a 6-digit code to confirm your address.", 'naimur-email-otp-for-woocommerce'); ?></p> 63 51 </div> 64 <form id="ceovw-form-register" method="post" >52 <form id="ceovw-form-register" method="post" novalidate> 65 53 <div class="ceovw-field-group"> 66 <label ><?php esc_html_e('Full Name', 'naimur-email-otp-for-woocommerce'); ?></label>67 <input type="text" name="ceovw_full_name" required/>54 <label for="ceovw-reg-name"><?php esc_html_e('Full Name', 'naimur-email-otp-for-woocommerce'); ?></label> 55 <input type="text" id="ceovw-reg-name" name="ceovw_full_name" required autocomplete="name" /> 68 56 </div> 69 57 <div class="ceovw-field-group"> 70 <label ><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label>71 <input type="email" name="email" required/>58 <label for="ceovw-reg-email"><?php esc_html_e('Email Address', 'naimur-email-otp-for-woocommerce'); ?></label> 59 <input type="email" id="ceovw-reg-email" name="email" required autocomplete="email" /> 72 60 </div> 73 61 <div class="ceovw-field-row"> 74 62 <div class="ceovw-field-group"> 75 <label ><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label>76 <input type="password" name="password" required/>63 <label for="ceovw-reg-pass"><?php esc_html_e('Password', 'naimur-email-otp-for-woocommerce'); ?></label> 64 <input type="password" id="ceovw-reg-pass" name="password" required autocomplete="new-password" /> 77 65 </div> 78 66 <div class="ceovw-field-group"> 79 <label ><?php esc_html_e('Confirm Password', 'naimur-email-otp-for-woocommerce'); ?></label>80 <input type="password" name="password_confirm" required/>67 <label for="ceovw-reg-pass2"><?php esc_html_e('Confirm Password', 'naimur-email-otp-for-woocommerce'); ?></label> 68 <input type="password" id="ceovw-reg-pass2" name="password_confirm" required autocomplete="new-password" /> 81 69 </div> 82 70 </div> 83 84 <!-- ডাইনামিক ক্যাপচা রেন্ডার --> 85 <?php echo $captcha_html; ?> 86 71 <div class="ceovw-form-msg" role="alert" aria-live="assertive" hidden></div> 87 72 <button type="submit" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-reg"> 88 73 <span class="btn-text"><?php esc_html_e('Sign Up & Verify', 'naimur-email-otp-for-woocommerce'); ?></span> 89 <span class="btn-loader" ></span>74 <span class="btn-loader" aria-hidden="true"></span> 90 75 </button> 91 76 </form> … … 94 79 </div> 95 80 </div> 81 <?php endif; ?> 96 82 83 <?php if ($ceovw_forgot_on) : ?> 97 84 <!-- 3. FORGOT PASSWORD VIEW --> 98 85 <div id="ceovw-view-forgot" class="ceovw-view-pane" style="display:none;"> 99 86 <div class="ceovw-header"> 100 87 <h2><?php esc_html_e('Reset Password', 'naimur-email-otp-for-woocommerce'); ?></h2> 101 <p><?php esc_html_e( 'Enter your email to receive a secure OTP', 'naimur-email-otp-for-woocommerce'); ?></p>88 <p><?php esc_html_e("Enter your email and we'll send you a 6-digit code.", 'naimur-email-otp-for-woocommerce'); ?></p> 102 89 </div> 103 <form id="ceovw-form-forgot" method="post" >90 <form id="ceovw-form-forgot" method="post" novalidate> 104 91 <div class="ceovw-field-group"> 105 <label ><?php esc_html_e('Registered Email', 'naimur-email-otp-for-woocommerce'); ?></label>106 <input type="email" name="reset_email" required/>92 <label for="ceovw-forgot-email"><?php esc_html_e('Registered Email', 'naimur-email-otp-for-woocommerce'); ?></label> 93 <input type="email" id="ceovw-forgot-email" name="reset_email" required autocomplete="email" /> 107 94 </div> 108 109 <!-- ডাইনামিক ক্যাপচা রেন্ডার --> 110 <?php echo $captcha_html; ?> 111 95 <div class="ceovw-form-msg" role="alert" aria-live="assertive" hidden></div> 112 96 <button type="submit" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-forgot"> 113 97 <span class="btn-text"><?php esc_html_e('Send Verification Code', 'naimur-email-otp-for-woocommerce'); ?></span> 114 <span class="btn-loader" ></span>98 <span class="btn-loader" aria-hidden="true"></span> 115 99 </button> 116 100 </form> … … 124 108 <div class="ceovw-header"> 125 109 <h2><?php esc_html_e('Set New Password', 'naimur-email-otp-for-woocommerce'); ?></h2> 126 <p><?php esc_html_e(' Secure your account with a new password', 'naimur-email-otp-for-woocommerce'); ?></p>110 <p><?php esc_html_e('Choose a new password for your account.', 'naimur-email-otp-for-woocommerce'); ?></p> 127 111 </div> 128 <form id="ceovw-form-newpass" method="post" >112 <form id="ceovw-form-newpass" method="post" novalidate> 129 113 <input type="hidden" name="verified_email" id="ceovw-verified-email" value=""> 130 114 <div class="ceovw-field-group"> 131 <label ><?php esc_html_e('New Password', 'naimur-email-otp-for-woocommerce'); ?></label>132 <input type="password" name="new_password" required/>115 <label for="ceovw-new-pass"><?php esc_html_e('New Password', 'naimur-email-otp-for-woocommerce'); ?></label> 116 <input type="password" id="ceovw-new-pass" name="new_password" required autocomplete="new-password" /> 133 117 </div> 134 118 <div class="ceovw-field-group"> 135 <label ><?php esc_html_e('Confirm New Password', 'naimur-email-otp-for-woocommerce'); ?></label>136 <input type="password" name="new_password_confirm" required/>119 <label for="ceovw-new-pass2"><?php esc_html_e('Confirm New Password', 'naimur-email-otp-for-woocommerce'); ?></label> 120 <input type="password" id="ceovw-new-pass2" name="new_password_confirm" required autocomplete="new-password" /> 137 121 </div> 122 <div class="ceovw-form-msg" role="alert" aria-live="assertive" hidden></div> 138 123 <button type="submit" class="ceovw-btn ceovw-btn-primary" id="ceovw-btn-setpass"> 139 <span class="btn-text"><?php esc_html_e('Update & Login', 'naimur-email-otp-for-woocommerce'); ?></span>140 <span class="btn-loader" ></span>124 <span class="btn-text"><?php esc_html_e('Update Password', 'naimur-email-otp-for-woocommerce'); ?></span> 125 <span class="btn-loader" aria-hidden="true"></span> 141 126 </button> 142 127 </form> 143 128 </div> 129 <?php endif; ?> 144 130 145 131 </div> -
naimur-email-otp-for-woocommerce/trunk/uninstall.php
r3780003 r3780739 16 16 delete_option('ceovw_db_version'); 17 17 delete_option('ceovw_upgrade_lock'); 18 delete_option('ceovw_last_cleanup'); 19 20 // Settings and email templates are kept unless the store owner chose to delete them (General Settings). 21 $ceovw_settings = get_option('ceovw_settings', array()); 22 if (is_array($ceovw_settings) && !empty($ceovw_settings['delete_data_on_uninstall'])) { 23 foreach (array('ceovw_settings', 'ceovw_email_templates', 'ceovw_test_email_sent_at') as $ceovw_option) { 24 delete_option($ceovw_option); 25 } 26 $ceovw_legacy = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('naimur-email-otp-for-woocommerce_') . '%')); 27 foreach ($ceovw_legacy as $ceovw_option) { 28 delete_option($ceovw_option); 29 } 30 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)