PluginProbe
The Innovative Form Builder – IvyForms / 0.5
The Innovative Form Builder – IvyForms v0.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / public / fields / RecaptchaFormField.vue

RecaptchaFormField.vue in The Innovative Form Builder – IvyForms 0.5, at frontend/src/views/public/fields/RecaptchaFormField.vue

210 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="ivyforms-recaptcha-field ivyforms-mx-0 ivyforms-my-20">
3 <!-- Show reCAPTCHA widget if configured -->
4 <Recaptcha
5 v-if="isRecaptchaConfigured"
6 ref="recaptchaWidgetRef"
7 v-model="recaptchaToken"
8 :config="recaptchaConfig"
9 @verify="onRecaptchaVerify"
10 @expire="onRecaptchaExpire"
11 @error="onRecaptchaError"
12 />
13
14 <!-- Show error message if present -->
15 <div v-if="recaptchaError">
16 <IvyAlert :description="recaptchaError" type="error" />
17 </div>
18
19 <!-- Show warning message if not configured -->
20 <div v-else-if="!isRecaptchaConfigured" class="ivyforms-recaptcha-not-configured">
21 <IvyAlert :description="getLabel('recaptcha_not_configured')" type="warning" />
22 </div>
23 </div>
24 </template>
25
26 <script setup lang="ts">
27 import { computed, onMounted, ref } from 'vue'
28 import { useLabels } from '@/composables/useLabels'
29 import Recaptcha from '@/views/_components/recaptcha/Recaptcha.vue'
30 import IvyAlert from '@/views/_components/alert/IvyAlert.vue'
31 import type { Field } from '@/types/field'
32 import type { RecaptchaConfig, WindowRecaptchaConfig } from '@/types/recaptcha/recaptcha-interface'
33 import type { RecaptchaType } from '@/types/recaptcha/recaptcha-type'
34
35 declare global {
36 interface Window {
37 wpIvyRecaptchaConfig?: WindowRecaptchaConfig | unknown[]
38 }
39 }
40
41 interface Props {
42 field: Field
43 modelValue?: string
44 error?: string
45 }
46
47 interface Emits {
48 (event: 'update:modelValue', value: string): void
49 }
50
51 const props = defineProps<Props>()
52 const emit = defineEmits<Emits>()
53
54 const { getLabel } = useLabels()
55 const recaptchaToken = ref('')
56 const recaptchaError = ref('')
57
58 // Check if reCAPTCHA is configured
59 const isRecaptchaConfigured = computed(() => {
60 // Check if global reCAPTCHA config exists and is configured
61 if (typeof window !== 'undefined' && window.wpIvyRecaptchaConfig) {
62 const config = window.wpIvyRecaptchaConfig as WindowRecaptchaConfig
63
64 // Handle both empty array case and proper object case
65 if (!config || Array.isArray(config) || typeof config !== 'object') {
66 return false
67 }
68
69 // Check if CAPTCHA is enabled
70 const isEnabled =
71 config.enabled === true ||
72 config.enabled === 'true' ||
73 config.enabled === '1' ||
74 config.enabled === 1
75
76 // Check if provider is recaptcha
77 const isRecaptchaProvider = config.provider === 'recaptcha'
78
79 // Check if recaptcha configuration exists and is configured
80 const hasRecaptchaConfig = config.recaptcha && typeof config.recaptcha === 'object'
81
82 if (!hasRecaptchaConfig) {
83 return false
84 }
85
86 const recaptchaConfig = config.recaptcha
87
88 // Check if properly configured - handle both boolean and string values
89 const isConfigured =
90 recaptchaConfig.configured === true ||
91 recaptchaConfig.configured === 'true' ||
92 recaptchaConfig.configured === '1' ||
93 recaptchaConfig.configured === 1
94
95 // Check if it has valid site key (the actual fix!)
96 const hasSiteKey =
97 recaptchaConfig.siteKey &&
98 typeof recaptchaConfig.siteKey === 'string' &&
99 recaptchaConfig.siteKey.trim() !== ''
100
101 return isEnabled && isRecaptchaProvider && isConfigured && hasSiteKey
102 }
103
104 return false
105 })
106
107 // reCAPTCHA configuration from global settings
108 const recaptchaConfig = computed((): RecaptchaConfig => {
109 if (typeof window !== 'undefined' && window.wpIvyRecaptchaConfig) {
110 const config = window.wpIvyRecaptchaConfig as WindowRecaptchaConfig
111 // Only process if config is a proper object (not array) and has recaptcha config
112 if (config && typeof config === 'object' && !Array.isArray(config) && config.recaptcha) {
113 const recaptchaData = config.recaptcha
114 return {
115 siteKey: recaptchaData.siteKey || '',
116 type: (recaptchaData.type || 'v2') as RecaptchaType,
117 theme: (recaptchaData.theme || 'light') as 'light' | 'dark',
118 language: recaptchaData.language || 'en',
119 }
120 }
121 }
122
123 return {
124 siteKey: '',
125 type: 'v2' as RecaptchaType,
126 theme: 'light' as const,
127 language: 'en',
128 }
129 })
130
131 // Handle reCAPTCHA verification
132 const onRecaptchaVerify = (token: string) => {
133 recaptchaToken.value = token
134 recaptchaError.value = '' // Clear any previous errors
135 emit('update:modelValue', token)
136 }
137
138 // Handle reCAPTCHA expiration
139 const onRecaptchaExpire = () => {
140 recaptchaToken.value = ''
141 recaptchaError.value = getLabel('recaptcha_expired')
142 emit('update:modelValue', '')
143 }
144
145 // Handle reCAPTCHA errors
146 const onRecaptchaError = (error: Error) => {
147 console.error('reCAPTCHA error:', error)
148 recaptchaToken.value = ''
149 recaptchaError.value = error.message
150 emit('update:modelValue', '')
151 }
152
153 // For v3, we need to execute the reCAPTCHA when the form is submitted
154 const recaptchaWidgetRef = ref<InstanceType<typeof Recaptcha>>()
155
156 // Expose method for parent components to trigger v3 execution
157 const executeRecaptcha = async (action = 'submit') => {
158 recaptchaError.value = ''
159
160 if (!recaptchaWidgetRef.value) {
161 const errorMsg = getLabel('recaptcha_widget_not_initialized_field')
162 recaptchaError.value = errorMsg
163 throw new Error(errorMsg)
164 }
165
166 if (recaptchaConfig.value.type === 'v3') {
167 try {
168 return await recaptchaWidgetRef.value.execute(action)
169 } catch (error) {
170 const errorMsg = getLabel('recaptcha_v3_failed')
171 console.error(errorMsg, error)
172 recaptchaError.value = errorMsg
173 throw error
174 }
175 } else if (recaptchaConfig.value.type === 'invisible') {
176 try {
177 return await recaptchaWidgetRef.value.execute()
178 } catch (error) {
179 const errorMsg = getLabel('recaptcha_invisible_failed')
180 console.error(errorMsg, error)
181 recaptchaError.value = errorMsg
182 throw error
183 }
184 }
185
186 // For v2 checkbox, check if we have a token
187 const currentToken = recaptchaToken.value
188 if (!currentToken) {
189 const errorMsg = getLabel('recaptcha_complete_verification')
190 recaptchaError.value = errorMsg
191 throw new Error(errorMsg)
192 }
193
194 return currentToken
195 }
196
197 // Expose the execute method for parent components
198 defineExpose({
199 executeRecaptcha,
200 })
201
202 // Initialize with current value
203 onMounted(() => {
204 if (props.modelValue) {
205 recaptchaToken.value = props.modelValue
206 recaptchaError.value = '' // Clear any errors when we have a valid value
207 }
208 })
209 </script>
210