PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.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
← All changes | backend/src/Services/Shortcode/ShortcodeService.php +291 -0 0.6.1 → 1.5 View file →
@@ -1,0 +1,291 @@
1 +<?php
2 +
3 +/**
4 + * @copyright © Melograno Venture Studio. All rights reserved.
5 + * @licence See LICENCE.md for license details.
6 + */
7 +
8 +namespace IvyForms\Services\Shortcode;
9 +
10 +use IvyForms\Common\Exceptions\ValidationException;
11 +use IvyForms\Common\Helpers\FieldHelper;
12 +use IvyForms\Services\API\IvyFormsAPI;
13 +use IvyForms\Services\Form\Style\FormStyleService;
14 +use IvyForms\Services\Placeholder\PlaceholderLocalizationHelper;
15 +use IvyForms\Services\Shortcode\Helpers\ShortcodeHelper;
16 +
17 +/**
18 + * Class ShortcodeService
19 + *
20 + * @package IvyForms\Services\Shortcode
21 + */
22 +class ShortcodeService
23 +{
24 + public static int $counter = 0;
25 + /**
26 + * Array keyed by unique render counters (string) -> payload array
27 + *
28 + * @var array<string, array<string, mixed>>
29 + */
30 + public static array $formList = [];
31 +
32 + /**
33 + * Shortcode handler
34 + *
35 + * @param mixed[] $atts
36 + *
37 + * @return string
38 + * @throws ValidationException
39 + */
40 + public static function shortcodeHandler(array $atts): string
41 + {
42 + $atts = shortcode_atts(
43 + [
44 + 'id' => '0',
45 + 'show_title' => null,
46 + 'show_description' => null,
47 + ],
48 + $atts
49 + );
50 +
51 + if (empty($atts['id'])) {
52 + return '';
53 + }
54 +
55 + $formId = (int)$atts['id'];
56 + $form = IvyFormsAPI::getForm($formId);
57 +
58 + // Check if the form is published
59 + if (is_wp_error($form) || !($form->isPublished())) {
60 + return '';
61 + }
62 +
63 + // Generate a stable unique identifier for this render
64 + $counter = $formId . '_' . uniqid();
65 +
66 + // Build overrides array for show_title and show_description
67 + $overrides = [];
68 + if ($atts['show_title'] !== null) {
69 + // Convert string 'true'/'false' or '1'/'0' to boolean
70 + $overrides['showTitle'] = filter_var($atts['show_title'], FILTER_VALIDATE_BOOLEAN);
71 + }
72 + if ($atts['show_description'] !== null) {
73 + $overrides['showDescription'] = filter_var($atts['show_description'], FILTER_VALIDATE_BOOLEAN);
74 + }
75 +
76 + self::getFormList($formId, $counter, $overrides);
77 +
78 + self::enqueueScripts();
79 +
80 + // Generate form-specific CSS
81 + $formCSS = self::generateFormCSS($formId);
82 +
83 + ob_start();
84 +
85 + // Output CSS inline before the form
86 + if (!empty($formCSS)) {
87 + $styleId = 'ivyforms-form-styles-' . sanitize_key(
88 + str_replace('.', '-', $counter)
89 + );
90 + echo '<style id="' . esc_attr($styleId) . '">' . "\n" . $formCSS . "\n" . '</style>' . "\n";
91 + }
92 +
93 + include IVYFORMS_PATH . '/view/frontend/view.php';
94 + $html = ob_get_contents();
95 + ob_end_clean();
96 +
97 + return $html;
98 + }
99 +
100 + /**
101 + * Get form list
102 + *
103 + * @param int $formId
104 + * @param string $counter Unique counter identifier for this render
105 + * @param array<string, mixed> $overrides Optional overrides for form settings (e.g., showTitle, showDescription)
106 + *
107 + * @return void
108 + */
109 + public static function getFormList(int $formId, string $counter, array $overrides = [])
110 + {
111 + $form = IvyFormsAPI::getForm($formId);
112 +
113 + $fields = IvyFormsAPI::getFields($form->getId());
114 +
115 + $confirmations = IvyFormsAPI::getConfirmations($form->getId());
116 +
117 + $fieldsMap = self::prepareFieldsMap($fields);
118 +
119 + $confirmationsMap = [];
120 +
121 + foreach ($confirmations as $confirmation) {
122 + $confirmationsMap[] = is_object($confirmation) && method_exists($confirmation, 'toArray')
123 + ? $confirmation->toArray()
124 + : $confirmation;
125 + }
126 +
127 + $form->setFields($fieldsMap);
128 +
129 + $formArr = $form->toArray();
130 +
131 + // Apply overrides for showTitle and showDescription (from Gutenberg block or shortcode)
132 + if (array_key_exists('showTitle', $overrides)) {
133 + $formArr['showTitle'] = $overrides['showTitle'];
134 + }
135 + if (array_key_exists('showDescription', $overrides)) {
136 + $formArr['showDescription'] = $overrides['showDescription'];
137 + }
138 +
139 + self::$formList[$counter] = [
140 + 'form' => $formArr,
141 + 'fields' => $fieldsMap,
142 + 'id' => $form->getId(),
143 + 'counter' => $counter,
144 + 'nonce' => wp_create_nonce('ivyformsFrontSubmissionNonce_' . $form->getId()),
145 + 'confirmations' => $confirmationsMap,
146 + ];
147 + }
148 +
149 + /**
150 + * Prepare fields map for a form
151 + *
152 + * @param array<int, object> $fields
153 + * @return array<int, array<string, mixed>>
154 + */
155 + private static function prepareFieldsMap(array $fields): array
156 + {
157 + $generalData = PlaceholderLocalizationHelper::getGeneralData();
158 + $fieldsMap = [];
159 + foreach ($fields as $field) {
160 + $fieldArr = $field->toArray();
161 + $fieldType = isset($fieldArr['type']) ? (string) $fieldArr['type'] : '';
162 + if (
163 + $fieldType !== ''
164 + && (FieldHelper::fieldTypeHasOptions($fieldType) || $fieldType === 'rating')
165 + ) {
166 + $fieldOptions = IvyFormsAPI::getFieldOptions($fieldArr['id']);
167 + $fieldArr['fieldOptions'] = [];
168 + foreach ($fieldOptions as $option) {
169 + if (is_object($option) && method_exists($option, 'toArray')) {
170 + $fieldArr['fieldOptions'][] = $option->toArray();
171 + }
172 + }
173 + }
174 + $fieldsMap[] = self::resolveFieldPlaceholders($fieldArr, $generalData);
175 + }
176 + return $fieldsMap;
177 + }
178 +
179 + /**
180 + * @param array<string, mixed> $fieldArr
181 + * @param array<string, string|int> $generalData
182 + * @return array<string, mixed>
183 + */
184 + private static function resolveFieldPlaceholders(array $fieldArr, array $generalData): array
185 + {
186 + foreach (['placeholder', 'defaultValue'] as $key) {
187 + if (!isset($fieldArr[$key]) || !is_string($fieldArr[$key])) {
188 + continue;
189 + }
190 + $fieldArr[$key] = PlaceholderLocalizationHelper::resolveFieldString(
191 + $fieldArr[$key],
192 + $generalData
193 + );
194 + }
195 +
196 + return $fieldArr;
197 + }
198 +
199 + /**
200 + * Enqueue handler
201 + */
202 + public static function enqueueScripts(): void
203 + {
204 + ShortcodeHelper::registerFrontendAssets(self::$formList);
205 + }
206 +
207 + /**
208 + * Generate CSS for a specific form
209 + *
210 + * @param int $formId
211 + * @return string
212 + */
213 + private static function generateFormCSS(int $formId): string
214 + {
215 + foreach (self::$formList as $formData) {
216 + if ($formData['id'] === $formId) {
217 + $formPayload = $formData['form'] ?? null;
218 + if (!is_array($formPayload)) {
219 + return '';
220 + }
221 +
222 + $styleSettings = $formPayload['styleSettings'] ?? null;
223 +
224 + if (!empty($styleSettings) && is_array($styleSettings)) {
225 + return FormStyleService::generateFormCSS(
226 + $formId,
227 + $styleSettings,
228 + isset($formPayload['formType']) ? (string)$formPayload['formType'] : null
229 + );
230 + }
231 + }
232 + }
233 +
234 + return '';
235 + }
236 +
237 + /**
238 + * Enqueue CAPTCHA scripts (reCAPTCHA, Turnstile, etc.) if configured
239 + *
240 + * @param string $scriptId
241 + */
242 + public static function enqueueRecaptchaScript(string $scriptId): void
243 + {
244 + // Delegate captcha discovery/enqueue to ShortcodeHelper while keeping BC wrapper here
245 + $securityService = ShortcodeHelper::getSecurityService();
246 + $formFields = ShortcodeHelper::getAllFormFields(self::$formList);
247 +
248 + $securityConfig = $securityService->getFrontendSecurityConfig($formFields);
249 +
250 + if ($securityConfig['captcha']['enabled'] ?? false) {
251 + if (isset($securityConfig['captcha']['recaptcha'])) {
252 + ShortcodeHelper::enqueueGoogleRecaptchaScript($securityConfig['captcha']);
253 + }
254 + if (isset($securityConfig['captcha']['turnstile'])) {
255 + ShortcodeHelper::enqueueTurnstileScript($securityConfig['captcha']);
256 + }
257 + if (isset($securityConfig['captcha']['hcaptcha'])) {
258 + ShortcodeHelper::enqueueHCaptchaScript($securityConfig['captcha']);
259 + }
260 + }
261 +
262 + wp_localize_script(
263 + $scriptId,
264 + 'wpIvyRecaptchaConfig',
265 + $securityConfig['captcha'] ?? []
266 + );
267 +
268 + wp_localize_script(
269 + $scriptId,
270 + 'wpIvyTurnstileConfig',
271 + $securityConfig['captcha'] ?? []
272 + );
273 +
274 + wp_localize_script(
275 + $scriptId,
276 + 'wpIvyHCaptchaConfig',
277 + $securityConfig['captcha'] ?? []
278 + );
279 + }
280 +
281 + /**
282 + * Backwards-compatible wrapper for script attribute filter
283 + *
284 + * @param array<string, mixed> $attributes
285 + * @return array<string, mixed>
286 + */
287 + public static function addScriptAttribute(array $attributes): array
288 + {
289 + return ShortcodeHelper::addScriptAttribute($attributes);
290 + }
291 +}