PluginProbe
The Innovative Form Builder – IvyForms / 0.9
The Innovative Form Builder – IvyForms v0.9
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.9, at frontend/src/views/public/fields/RecaptchaFormField.vue

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