PluginProbe
The Innovative Form Builder – IvyForms / 1.1
The Innovative Form Builder – IvyForms v1.1
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 / stores / useSettingsStore.ts

useSettingsStore.ts in The Innovative Form Builder – IvyForms 1.1, at frontend/src/stores/useSettingsStore.ts

774 lines 23.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { defineStore } from 'pinia'
2 import { ref, computed } from 'vue'
3 import { useApiClient } from '@/composables/useApiClient'
4 import { useLabels } from '@/composables/useLabels'
5 import IvyMessage from '@/views/_components/message/ivyMessage.ts'
6 import type { RecaptchaSettings } from '@/types/recaptcha/recaptcha-interface'
7 import type { RecaptchaType } from '@/types/recaptcha/recaptcha-type'
8 import type { TurnstileSettings } from '@/types/turnstile/turnstile-interface'
9 import type { TurnstileTheme } from '@/types/turnstile/turnstile-type'
10 import type { HCaptchaSettings } from '@/types/hcaptcha/hcaptcha-interface'
11 import type { HCaptchaType } from '@/types/hcaptcha/hcaptcha-type'
12 import type { AllSettings } from '@/types/settings'
13
14 /**
15 * Settings store for managing application settings
16 * This store handles reCAPTCHA settings and other security/general settings
17 */
18 export const useSettingsStore = defineStore('settingsStore', () => {
19 // State using refs (composition API style like useFormBuilder)
20
21 // All settings state
22 const allSettings = ref<AllSettings | null>(null)
23 const isLoading = ref(false)
24
25 // reCAPTCHA specific state (for backward compatibility)
26 const recaptchaType = ref<RecaptchaType>('v2')
27 const recaptchaSiteKey = ref('')
28 const recaptchaSecretKey = ref('')
29 const recaptchaLanguage = ref('')
30
31 // Turnstile specific state
32 const turnstileSiteKey = ref('')
33 const turnstileSecretKey = ref('')
34 const turnstileTheme = ref<TurnstileTheme>('auto')
35
36 // hCaptcha specific state
37 const hcaptchaType = ref<HCaptchaType>('checkbox')
38 const hcaptchaSiteKey = ref('')
39 const hcaptchaSecretKey = ref('')
40
41 // WCAG Compliance state
42 const wcagBackendCompliance = ref(false)
43
44 // Request deduplication - prevent multiple simultaneous calls
45 let loadPromise: Promise<boolean> | null = null
46
47 // Template favorites state
48 const favoriteTemplates = ref<string[]>([])
49
50 // Delete on uninstall state
51 const deleteOnUninstall = ref(false)
52
53 // MCP integration (Model Context Protocol for AI clients)
54 const mcpEnabled = ref(false)
55
56 const { getLabel } = useLabels()
57 const { request } = useApiClient()
58
59 // Computed values
60 const recaptchaSettings = computed(() => ({
61 type: recaptchaType.value,
62 siteKey: recaptchaSiteKey.value,
63 secretKey: recaptchaSecretKey.value,
64 language: recaptchaLanguage.value,
65 }))
66
67 const turnstileSettings = computed(() => ({
68 siteKey: turnstileSiteKey.value,
69 secretKey: turnstileSecretKey.value,
70 theme: turnstileTheme.value,
71 }))
72
73 const hcaptchaSettings = computed(() => ({
74 type: hcaptchaType.value,
75 siteKey: hcaptchaSiteKey.value,
76 secretKey: hcaptchaSecretKey.value,
77 }))
78
79 // Computed getters for all settings
80 const securitySettings = computed(() => allSettings.value?.security || null)
81 const generalSettings = computed(() => allSettings.value?.general || null)
82 const integrationsSettings = computed(() => allSettings.value?.integrations || null)
83
84 // Specific settings getters
85 const currentRecaptchaSettings = computed(() => allSettings.value?.security?.recaptcha || null)
86 const currentTurnstileSettings = computed(() => allSettings.value?.security?.turnstile || null)
87 const currentHCaptchaSettings = computed(() => allSettings.value?.security?.hcaptcha || null)
88
89 // Integration settings getters
90 const wpDataTablesIntegration = computed(
91 () => allSettings.value?.integrations?.wpdatatables || null,
92 )
93
94 const isRecaptchaConfigured = computed(() => {
95 // reCAPTCHA is configured only when both keys are present and valid
96 const siteKeyPresent = recaptchaSiteKey.value && recaptchaSiteKey.value.trim() !== ''
97 const secretKeyPresent = recaptchaSecretKey.value && recaptchaSecretKey.value.trim() !== ''
98
99 return (
100 siteKeyPresent &&
101 secretKeyPresent &&
102 validateCaptchaKeyFormat(recaptchaSiteKey.value) &&
103 validateCaptchaKeyFormat(recaptchaSecretKey.value)
104 )
105 })
106
107 const isTurnstileConfigured = computed(() => {
108 // Turnstile is configured only when both keys are present and valid
109 const siteKeyPresent = turnstileSiteKey.value && turnstileSiteKey.value.trim() !== ''
110 const secretKeyPresent = turnstileSecretKey.value && turnstileSecretKey.value.trim() !== ''
111
112 return (
113 siteKeyPresent &&
114 secretKeyPresent &&
115 validateCaptchaKeyFormat(turnstileSiteKey.value) && // Turnstile uses similar format
116 validateCaptchaKeyFormat(turnstileSecretKey.value)
117 )
118 })
119
120 const isHCaptchaConfigured = computed(() => {
121 // hCaptcha is configured only when both keys are present and valid
122 const siteKeyPresent = hcaptchaSiteKey.value && hcaptchaSiteKey.value.trim() !== ''
123 const secretKeyPresent = hcaptchaSecretKey.value && hcaptchaSecretKey.value.trim() !== ''
124
125 return (
126 siteKeyPresent &&
127 secretKeyPresent &&
128 validateCaptchaKeyFormat(hcaptchaSiteKey.value) && // hCaptcha uses similar format
129 validateCaptchaKeyFormat(hcaptchaSecretKey.value)
130 )
131 })
132
133 // Actions - following the same pattern as useFormBuilder
134 const setRecaptchaType = (type: RecaptchaType) => {
135 recaptchaType.value = type
136 }
137
138 const setRecaptchaSiteKey = (siteKey: string) => {
139 recaptchaSiteKey.value = siteKey
140 }
141
142 const setRecaptchaSecretKey = (secretKey: string) => {
143 recaptchaSecretKey.value = secretKey
144 }
145
146 const setRecaptchaLanguage = (language: string) => {
147 recaptchaLanguage.value = language
148 }
149
150 const setTurnstileSiteKey = (siteKey: string) => {
151 turnstileSiteKey.value = siteKey
152 }
153
154 const setTurnstileSecretKey = (secretKey: string) => {
155 turnstileSecretKey.value = secretKey
156 }
157
158 const setTurnstileTheme = (theme: TurnstileTheme) => {
159 turnstileTheme.value = theme
160 }
161
162 const setHCaptchaType = (type: HCaptchaType) => {
163 hcaptchaType.value = type
164 }
165
166 const setHCaptchaSiteKey = (siteKey: string) => {
167 hcaptchaSiteKey.value = siteKey
168 }
169
170 const setHCaptchaSecretKey = (secretKey: string) => {
171 hcaptchaSecretKey.value = secretKey
172 }
173
174 const setWcagBackendCompliance = (value: boolean) => {
175 wcagBackendCompliance.value = value
176 }
177
178 const setDeleteOnUninstall = (value: boolean) => {
179 deleteOnUninstall.value = value
180 }
181
182 const setMcpEnabled = (value: boolean) => {
183 mcpEnabled.value = value
184 }
185
186 const setRecaptchaSettings = (settings: RecaptchaSettings) => {
187 recaptchaType.value = settings.type
188 recaptchaSiteKey.value = settings.siteKey
189 recaptchaSecretKey.value = settings.secretKey
190 // Set defaults for new fields if not provided
191 recaptchaLanguage.value = settings.language || ''
192 }
193
194 const setTurnstileSettings = (settings: TurnstileSettings) => {
195 turnstileSiteKey.value = settings.siteKey
196 turnstileSecretKey.value = settings.secretKey
197 // Set defaults for new fields if not provided
198 turnstileTheme.value = settings.theme || 'auto'
199 }
200
201 const setHCaptchaSettings = (settings: HCaptchaSettings) => {
202 hcaptchaType.value = settings.type
203 hcaptchaSiteKey.value = settings.siteKey
204 hcaptchaSecretKey.value = settings.secretKey
205 }
206
207 // Load all settings for admin page - single API call for better performance
208 const loadAllSettings = async (): Promise<boolean> => {
209 // If there's already a request in progress, return it
210 if (loadPromise) {
211 return loadPromise
212 }
213
214 loadPromise = (async () => {
215 try {
216 isLoading.value = true
217 const { data, error, status } = await request('settings/all', {
218 method: 'GET',
219 headers: {
220 'Content-Type': 'application/json',
221 },
222 })
223
224 if (status === 200 && !error && data?.data?.data) {
225 allSettings.value = data.data.data
226
227 // Update individual reCAPTCHA refs for backward compatibility
228 const recaptcha = data.data.data.security?.recaptcha
229 if (recaptcha) {
230 recaptchaType.value = recaptcha.type || 'v2'
231 recaptchaSiteKey.value = recaptcha.siteKey || ''
232 recaptchaSecretKey.value = recaptcha.secretKey || ''
233 recaptchaLanguage.value = recaptcha.language || ''
234 }
235
236 // Update individual Turnstile refs
237 const turnstile = data.data.data.security?.turnstile
238 if (turnstile) {
239 turnstileSiteKey.value = turnstile.siteKey || ''
240 turnstileSecretKey.value = turnstile.secretKey || ''
241 turnstileTheme.value = turnstile.theme || 'auto'
242 }
243
244 // Update individual hCaptcha refs
245 const hcaptcha = data.data.data.security?.hcaptcha
246 if (hcaptcha) {
247 hcaptchaType.value = hcaptcha.type || 'checkbox'
248 hcaptchaSiteKey.value = hcaptcha.siteKey || ''
249 hcaptchaSecretKey.value = hcaptcha.secretKey || ''
250 }
251
252 // Update WCAG compliance setting
253 const general = data.data.data.general
254 if (general && general.wcagBackend !== undefined) {
255 wcagBackendCompliance.value = general.wcagBackend
256 }
257
258 // Update delete on uninstall setting
259 if (general && general.delete_on_uninstall !== undefined) {
260 deleteOnUninstall.value = general.delete_on_uninstall
261 }
262
263 if (general && general.mcp_enabled !== undefined) {
264 mcpEnabled.value = general.mcp_enabled
265 }
266
267 // Update favorite templates
268 if (general && general.favoriteTemplates) {
269 favoriteTemplates.value = general.favoriteTemplates
270 }
271
272 return true
273 } else {
274 IvyMessage({
275 message: getLabel('failed_to_load_settings'),
276 type: 'error',
277 })
278 return false
279 }
280 } catch (error) {
281 IvyMessage({
282 message: getLabel('error_loading_settings'),
283 type: 'error',
284 })
285 console.error(getLabel('error_loading_settings'), error)
286 return false
287 } finally {
288 isLoading.value = false
289 loadPromise = null
290 }
291 })()
292
293 return loadPromise
294 }
295
296 // Generic settings loader - can be used for any category/option combination
297 const loadSetting = async (category: string, option: string) => {
298 try {
299 isLoading.value = true
300 const { data, error, status } = await request(`setting/${category}/${option}`, {
301 method: 'GET',
302 headers: {
303 'Content-Type': 'application/json',
304 },
305 })
306
307 if (status === 200 && !error && data?.data?.value) {
308 return data.data.value
309 } else {
310 IvyMessage({
311 message: getLabel('failed_to_load_settings'),
312 type: 'error',
313 })
314 return null
315 }
316 } catch (err) {
317 console.error(getLabel('failed_to_load_settings'), err)
318 IvyMessage({
319 message: getLabel('failed_to_load_settings'),
320 type: 'error',
321 })
322 return null
323 } finally {
324 isLoading.value = false
325 }
326 }
327
328 // Generic settings saver - can be used for any category/option combination
329 const saveSetting = async (
330 category: string,
331 option: string,
332 value: unknown,
333 showMessage: boolean = true,
334 ): Promise<boolean> => {
335 try {
336 isLoading.value = true
337 const { data, error, status } = await request('setting/update', {
338 method: 'POST',
339 headers: {
340 'Content-Type': 'application/json',
341 },
342 data: {
343 settingsCategory: category,
344 settingsOption: option,
345 settingsValue: value,
346 },
347 })
348
349 if (status === 200 && !error) {
350 if (showMessage) {
351 IvyMessage({
352 message: getLabel('settings_saved'),
353 type: 'success',
354 })
355 }
356 return true
357 } else {
358 if (showMessage) {
359 IvyMessage({
360 message: getLabel('failed_to_save_settings'),
361 type: 'error',
362 })
363 }
364 console.error(getLabel('failed_to_save_settings'), data?.message)
365 return false
366 }
367 } catch (error) {
368 if (showMessage) {
369 IvyMessage({
370 message: getLabel('error_saving_settings'),
371 type: 'error',
372 })
373 }
374 console.error(getLabel('error_saving_settings'), error)
375 return false
376 } finally {
377 isLoading.value = false
378 }
379 }
380
381 // Update specific setting in allSettings state (for real-time updates)
382 const updateSettingInState = (category: keyof AllSettings, option: string, value: unknown) => {
383 if (allSettings.value) {
384 // Create a deep copy to trigger reactivity
385 const updatedSettings = { ...allSettings.value }
386 if (updatedSettings[category]) {
387 ;(updatedSettings[category] as Record<string, unknown>)[option] = value
388 allSettings.value = updatedSettings
389 }
390 }
391 }
392
393 // Load reCAPTCHA settings - following the async pattern from useFormBuilder
394 const loadRecaptchaSettings = async () => {
395 const settings = await loadSetting('security', 'recaptcha')
396
397 if (settings) {
398 recaptchaType.value = settings.type || 'v2'
399 recaptchaSiteKey.value = settings.siteKey || ''
400 recaptchaSecretKey.value = settings.secretKey || ''
401 recaptchaLanguage.value = settings.language || ''
402 }
403 }
404
405 // Save reCAPTCHA settings - following the async pattern from useFormBuilder
406 const saveRecaptchaSettings = async (): Promise<boolean> => {
407 // Basic frontend validation - only validate non-empty keys
408 const siteKeyValue = recaptchaSiteKey.value.trim()
409 const secretKeyValue = recaptchaSecretKey.value.trim()
410
411 // Validate both keys at once if they are provided
412 if (
413 (siteKeyValue && !validateCaptchaKeyFormat(siteKeyValue)) ||
414 (secretKeyValue && !validateCaptchaKeyFormat(secretKeyValue))
415 ) {
416 const errorMessage =
417 siteKeyValue && !validateCaptchaKeyFormat(siteKeyValue)
418 ? getLabel('site_key_invalid')
419 : getLabel('secret_key_invalid')
420
421 IvyMessage({
422 message: errorMessage,
423 type: 'error',
424 })
425 return false
426 }
427
428 const settingsValue = {
429 type: recaptchaType.value,
430 siteKey: recaptchaSiteKey.value,
431 secretKey: recaptchaSecretKey.value,
432 language: recaptchaLanguage.value,
433 }
434
435 const success = await saveSetting('security', 'recaptcha', settingsValue)
436
437 if (success) {
438 // Update the allSettings state with the new values
439 updateSettingInState('security', 'recaptcha', settingsValue)
440 }
441
442 return success
443 }
444
445 // Load Turnstile settings - following the async pattern from useFormBuilder
446 const loadTurnstileSettings = async () => {
447 const settings = await loadSetting('security', 'turnstile')
448
449 if (settings) {
450 turnstileSiteKey.value = settings.siteKey || ''
451 turnstileSecretKey.value = settings.secretKey || ''
452 turnstileTheme.value = settings.theme || 'auto'
453 }
454 }
455
456 // Load hCaptcha settings - following the async pattern from useFormBuilder
457 const loadHCaptchaSettings = async () => {
458 const settings = await loadSetting('security', 'hcaptcha')
459
460 if (settings) {
461 hcaptchaType.value = settings.type || 'checkbox'
462 hcaptchaSiteKey.value = settings.siteKey || ''
463 hcaptchaSecretKey.value = settings.secretKey || ''
464 }
465 }
466
467 // Save Turnstile settings - following the async pattern from useFormBuilder
468 const saveTurnstileSettings = async (): Promise<boolean> => {
469 // Basic frontend validation - only validate non-empty keys
470 const siteKeyValue = turnstileSiteKey.value.trim()
471 const secretKeyValue = turnstileSecretKey.value.trim()
472
473 // Validate both keys at once if they are provided
474 if (
475 (siteKeyValue && !validateCaptchaKeyFormat(siteKeyValue)) ||
476 (secretKeyValue && !validateCaptchaKeyFormat(secretKeyValue))
477 ) {
478 const errorMessage =
479 siteKeyValue && !validateCaptchaKeyFormat(siteKeyValue)
480 ? getLabel('site_key_invalid')
481 : getLabel('secret_key_invalid')
482
483 IvyMessage({
484 message: errorMessage,
485 type: 'error',
486 })
487 return false
488 }
489
490 const settingsValue = {
491 siteKey: turnstileSiteKey.value,
492 secretKey: turnstileSecretKey.value,
493 theme: turnstileTheme.value,
494 }
495
496 const success = await saveSetting('security', 'turnstile', settingsValue)
497
498 if (success) {
499 // Update the allSettings state with the new values
500 updateSettingInState('security', 'turnstile', settingsValue)
501 }
502
503 return success
504 }
505
506 // Save hCaptcha settings - following the async pattern from useFormBuilder
507 const saveHCaptchaSettings = async (): Promise<boolean> => {
508 // Basic frontend validation - only validate non-empty keys
509 const siteKeyValue = hcaptchaSiteKey.value.trim()
510 const secretKeyValue = hcaptchaSecretKey.value.trim()
511
512 // Validate both keys at once if they are provided
513 if (
514 (siteKeyValue && !validateCaptchaKeyFormat(siteKeyValue)) ||
515 (secretKeyValue && !validateCaptchaKeyFormat(secretKeyValue))
516 ) {
517 const errorMessage =
518 siteKeyValue && !validateCaptchaKeyFormat(siteKeyValue)
519 ? getLabel('site_key_invalid')
520 : getLabel('secret_key_invalid')
521
522 IvyMessage({
523 message: errorMessage,
524 type: 'error',
525 })
526 return false
527 }
528
529 const settingsValue = {
530 type: hcaptchaType.value,
531 siteKey: hcaptchaSiteKey.value,
532 secretKey: hcaptchaSecretKey.value,
533 }
534
535 const success = await saveSetting('security', 'hcaptcha', settingsValue)
536
537 if (success) {
538 // Update the allSettings state with the new values
539 updateSettingInState('security', 'hcaptcha', settingsValue)
540 }
541
542 return success
543 }
544
545 // Save general settings
546 const saveGeneralSettings = async (): Promise<boolean> => {
547 const wcagSuccess = await saveSetting(
548 'general',
549 'wcagBackend',
550 wcagBackendCompliance.value,
551 false,
552 )
553 const mcpSuccess = await saveSetting('general', 'mcp_enabled', mcpEnabled.value, false)
554 const deleteSuccess = await saveSetting(
555 'general',
556 'delete_on_uninstall',
557 deleteOnUninstall.value,
558 )
559
560 const success = wcagSuccess && mcpSuccess && deleteSuccess
561
562 if (success) {
563 // Update the allSettings state with the new values
564 updateSettingInState('general', 'wcagBackend', wcagBackendCompliance.value)
565 updateSettingInState('general', 'mcp_enabled', mcpEnabled.value)
566 updateSettingInState('general', 'delete_on_uninstall', deleteOnUninstall.value)
567 }
568
569 return success
570 }
571
572 // Basic captcha key format validation
573 const validateCaptchaKeyFormat = (key: string): boolean => {
574 // Allow empty keys (for clearing/resetting settings)
575 if (!key || key.trim() === '') {
576 return true
577 }
578
579 const trimmedKey = key.trim()
580
581 // captcha keys are typically 40 characters long, but can vary
582 if (trimmedKey.length < 20 || trimmedKey.length > 100) {
583 return false
584 }
585
586 // Basic format validation - should contain alphanumeric characters and some special chars
587 return /^[A-Za-z0-9_-]+$/.test(trimmedKey)
588 }
589
590 // Integration settings methods
591 /**
592 * Get integration settings dynamically by name
593 */
594 const getIntegrationSettings = (integrationName: string) => {
595 return allSettings.value?.integrations?.[integrationName] || null
596 }
597
598 /**
599 * Check if integration is enabled
600 */
601 const isIntegrationEnabled = (integrationName: string): boolean => {
602 return allSettings.value?.integrations?.[integrationName]?.enabled || false
603 }
604
605 /**
606 * Update integration enabled status (simple toggle)
607 */
608 const updateIntegrationEnabled = async (
609 integrationName: string,
610 enabled: boolean,
611 ): Promise<boolean> => {
612 const integrationValue = { enabled }
613 const success = await saveSetting('integrations', integrationName, integrationValue)
614
615 if (success) {
616 updateSettingInState('integrations', integrationName, integrationValue)
617 }
618
619 return success
620 }
621
622 /**
623 * Update integration settings (for complex integrations with multiple settings)
624 */
625 const updateIntegrationSettings = async (
626 integrationName: string,
627 settings: Record<string, unknown>,
628 ): Promise<boolean> => {
629 const success = await saveSetting('integrations', integrationName, settings)
630
631 if (success) {
632 updateSettingInState('integrations', integrationName, settings)
633 }
634
635 return success
636 }
637
638 /**
639 * Toggle a template as favorite
640 */
641 const toggleFavoriteTemplate = async (templateId: string): Promise<boolean> => {
642 const index = favoriteTemplates.value.indexOf(templateId)
643 if (index > -1) {
644 // Remove from favorites
645 favoriteTemplates.value = favoriteTemplates.value.filter((id) => id !== templateId)
646 } else {
647 // Add to favorites
648 favoriteTemplates.value = [...favoriteTemplates.value, templateId]
649 }
650 // Save to backend
651 const success = await saveSetting('general', 'favoriteTemplates', favoriteTemplates.value)
652 if (success) {
653 updateSettingInState('general', 'favoriteTemplates', favoriteTemplates.value)
654 }
655 return success
656 }
657
658 /**
659 * Check if a template is favorited
660 */
661 const isTemplateFavorited = (templateId: string): boolean => {
662 return favoriteTemplates.value.includes(templateId)
663 }
664
665 /**
666 * Load favorite templates
667 */
668 const loadFavoriteTemplates = async () => {
669 // If allSettings is already loaded, use it
670 if (allSettings.value?.general?.favoriteTemplates) {
671 favoriteTemplates.value = allSettings.value.general.favoriteTemplates as string[]
672 return
673 }
674
675 // Otherwise, load from individual endpoint
676 const favorites = await loadSetting('general', 'favoriteTemplates')
677 if (favorites && Array.isArray(favorites)) {
678 favoriteTemplates.value = favorites as string[]
679 } else if (favorites === null) {
680 // If it returns null, try loading all settings
681 await loadAllSettings()
682 if (allSettings.value?.general?.favoriteTemplates) {
683 favoriteTemplates.value = allSettings.value.general.favoriteTemplates as string[]
684 }
685 }
686 }
687
688 return {
689 // State
690 recaptchaType,
691 recaptchaSiteKey,
692 recaptchaSecretKey,
693 recaptchaLanguage,
694 turnstileSiteKey,
695 turnstileSecretKey,
696 turnstileTheme,
697 hcaptchaType,
698 hcaptchaSiteKey,
699 hcaptchaSecretKey,
700 wcagBackendCompliance,
701 favoriteTemplates,
702 deleteOnUninstall,
703 mcpEnabled,
704 isLoading,
705
706 // All Settings State
707 allSettings,
708
709 // Computed
710 recaptchaSettings,
711 turnstileSettings,
712 hcaptchaSettings,
713 isRecaptchaConfigured,
714 isTurnstileConfigured,
715 isHCaptchaConfigured,
716
717 // All Settings Computed
718 securitySettings,
719 generalSettings,
720 integrationsSettings,
721 currentRecaptchaSettings,
722 currentTurnstileSettings,
723 currentHCaptchaSettings,
724 wpDataTablesIntegration,
725
726 // Actions
727 setRecaptchaType,
728 setRecaptchaSiteKey,
729 setRecaptchaSecretKey,
730 setRecaptchaLanguage,
731 setTurnstileSiteKey,
732 setTurnstileSecretKey,
733 setTurnstileTheme,
734 setHCaptchaType,
735 setHCaptchaSiteKey,
736 setHCaptchaSecretKey,
737 setWcagBackendCompliance,
738 setDeleteOnUninstall,
739 setMcpEnabled,
740 setRecaptchaSettings,
741 setTurnstileSettings,
742 setHCaptchaSettings,
743 loadRecaptchaSettings,
744 saveRecaptchaSettings,
745 loadTurnstileSettings,
746 saveTurnstileSettings,
747 loadHCaptchaSettings,
748 saveHCaptchaSettings,
749 saveGeneralSettings,
750
751 // All Settings API
752 loadAllSettings,
753 updateSettingInState,
754
755 // Generic Settings API
756 loadSetting,
757 saveSetting,
758
759 // Integration Settings API
760 updateIntegrationEnabled,
761 getIntegrationSettings,
762 isIntegrationEnabled,
763 updateIntegrationSettings,
764
765 // Template Favorites API
766 toggleFavoriteTemplate,
767 isTemplateFavorited,
768 loadFavoriteTemplates,
769
770 // Validation utilities
771 validateCaptchaKeyFormat,
772 }
773 })
774