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
ivyforms / backend / src / Services / Shortcode / ShortcodeService.php

ShortcodeService.php in The Innovative Form Builder – IvyForms 1.5, at backend/src/Services/Shortcode/ShortcodeService.php

292 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
292