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 +151 -142 0.1.2 → 1.5 View file →
@@ -1,20 +1,19 @@
1 1 <?php
2 2
3 3 /**
4 - * @copyright © TMS-Plugins. All rights reserved.
4 + * @copyright © Melograno Venture Studio. All rights reserved.
5 5 * @licence See LICENCE.md for license details.
6 6 */
7 7
8 8 namespace IvyForms\Services\Shortcode;
9 9
10 -// phpcs:disable PSR1.Files.SideEffects
11 -if (!defined('ABSPATH')) {
12 - exit; // Exit if accessed directly
13 -}
14 -
10 +use IvyForms\Common\Exceptions\ValidationException;
11 +use IvyForms\Common\Helpers\FieldHelper;
15 12 use IvyForms\Services\API\IvyFormsAPI;
16 -use IvyForms\Services\Translations\FrontendStrings;
13 +use IvyForms\Services\Form\Style\FormStyleService;
14 +use IvyForms\Services\Placeholder\PlaceholderLocalizationHelper;
15 +use IvyForms\Services\Shortcode\Helpers\ShortcodeHelper;
17 16
18 17 /**
19 18 * Class ShortcodeService
20 19 *
@@ -23,9 +22,11 @@
23 22 class ShortcodeService
24 23 {
25 24 public static int $counter = 0;
26 25 /**
27 - * @var mixed[]
26 + * Array keyed by unique render counters (string) -> payload array
27 + *
28 + * @var array<string, array<string, mixed>>
28 29 */
29 30 public static array $formList = [];
30 31
31 32 /**
@@ -33,14 +34,17 @@
33 34 *
34 35 * @param mixed[] $atts
35 36 *
36 37 * @return string
38 + * @throws ValidationException
37 39 */
38 40 public static function shortcodeHandler(array $atts): string
39 41 {
40 42 $atts = shortcode_atts(
41 43 [
42 - 'id' => '0'
44 + 'id' => '0',
45 + 'show_title' => null,
46 + 'show_description' => null,
43 47 ],
44 48 $atts
45 49 );
46 50
@@ -55,15 +59,38 @@
55 59 if (is_wp_error($form) || !($form->isPublished())) {
56 60 return '';
57 61 }
58 62
59 - $counter = self::$counter++;
63 + // Generate a stable unique identifier for this render
64 + $counter = $formId . '_' . uniqid();
60 65
61 - self::getFormList($formId, $counter);
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 + }
62 75
76 + self::getFormList($formId, $counter, $overrides);
77 +
63 78 self::enqueueScripts();
64 79
80 + // Generate form-specific CSS
81 + $formCSS = self::generateFormCSS($formId);
82 +
65 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 +
66 93 include IVYFORMS_PATH . '/view/frontend/view.php';
67 94 $html = ob_get_contents();
68 95 ob_end_clean();
69 96
@@ -73,13 +100,14 @@
73 100 /**
74 101 * Get form list
75 102 *
76 103 * @param int $formId
77 - * @param int $counter
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)
78 106 *
79 107 * @return void
80 108 */
81 - public static function getFormList(int $formId, int $counter)
109 + public static function getFormList(int $formId, string $counter, array $overrides = [])
82 110 {
83 111 $form = IvyFormsAPI::getForm($formId);
84 112
85 113 $fields = IvyFormsAPI::getFields($form->getId());
@@ -85,14 +113,10 @@
85 113 $fields = IvyFormsAPI::getFields($form->getId());
86 114
87 115 $confirmations = IvyFormsAPI::getConfirmations($form->getId());
88 116
89 - $fieldsMap = [];
117 + $fieldsMap = self::prepareFieldsMap($fields);
90 118
91 - foreach ($fields as $field) {
92 - $fieldsMap[] = $field->toArray();
93 - }
94 -
95 119 $confirmationsMap = [];
96 120
97 121 foreach ($confirmations as $confirmation) {
98 122 $confirmationsMap[] = is_object($confirmation) && method_exists($confirmation, 'toArray')
@@ -103,8 +127,16 @@
103 127 $form->setFields($fieldsMap);
104 128
105 129 $formArr = $form->toArray();
106 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 +
107 139 self::$formList[$counter] = [
108 140 'form' => $formArr,
109 141 'fields' => $fieldsMap,
110 142 'id' => $form->getId(),
@@ -114,169 +146,146 @@
114 146 ];
115 147 }
116 148
117 149 /**
118 - * Enqueue handler
150 + * Prepare fields map for a form
151 + *
152 + * @param array<int, object> $fields
153 + * @return array<int, array<string, mixed>>
119 154 */
120 - public static function enqueueScripts(): void
155 + private static function prepareFieldsMap(array $fields): array
121 156 {
122 - $scriptId = IVYFORMS_DEV ? 'ivyforms_scripts_dev_vite' : 'ivyforms_script_index'; // @phpstan-ignore-line
123 - $scriptSrc = IVYFORMS_DEV ? // @phpstan-ignore-line
124 - 'http://localhost:5173/src/assets/js/public/public.ts' :
125 - IVYFORMS_URL . 'frontend/dist/public.js';
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 + }
126 178
127 - $styleSrc = IVYFORMS_DEV ? '' : IVYFORMS_URL . 'frontend/dist/index.css';// @phpstan-ignore-line
128 - $scriptVersion = IVYFORMS_DEV ? null : IVYFORMS_VERSION;// @phpstan-ignore-line
129 -
130 - wp_enqueue_script(
131 - $scriptId,
132 - $scriptSrc,
133 - [],
134 - $scriptVersion,
135 - true
136 - );
137 -
138 - if ($styleSrc) { // @phpstan-ignore-line
139 - wp_enqueue_style(
140 - 'ivyforms_style_index',
141 - $styleSrc,
142 - [],
143 - $scriptVersion
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
144 193 );
194 + }
145 195
146 - // TODO Create a helper service for adding styles
147 - wp_add_inline_style(
148 - 'ivyforms_style_index',
149 - /** @lang CSS */
150 - '.ivyforms-skeleton {
151 - width: 100%;
152 - max-width: 720px;
153 - margin: 0 auto;
154 - }
196 + return $fieldArr;
197 + }
155 198
156 - .ivyforms-skeleton-title {
157 - height: 28px;
158 - width: 40%;
159 - background: #dbdee3;
160 - margin-bottom: 12px;
161 - }
199 + /**
200 + * Enqueue handler
201 + */
202 + public static function enqueueScripts(): void
203 + {
204 + ShortcodeHelper::registerFrontendAssets(self::$formList);
205 + }
162 206
163 - .ivyforms-skeleton-field {
164 - margin-bottom: 16px;
165 - }
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 + }
166 221
167 - .ivyforms-skeleton-label {
168 - height: 22px;
169 - width: 20%;
170 - background: #dbdee3;
171 - font-size: 14px;
172 - border-radius: 4px;
173 - margin-bottom: 4px;
174 - }
222 + $styleSettings = $formPayload['styleSettings'] ?? null;
175 223
176 - .ivyforms-skeleton-input {
177 - line-height: 22px;
178 - font-size: 16px;
179 - background: #dbdee3;
180 - border-radius: 8px;
181 - width: 100%;
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 + }
182 231 }
232 + }
183 233
184 - .ivyforms-skeleton-email,
185 - .ivyforms-skeleton-textarea,
186 - .ivyforms-skeleton-text,
187 - .ivyforms-skeleton-number,
188 - .ivyforms-skeleton-phone {
189 - height: 40px;
190 - }
234 + return '';
235 + }
191 236
192 - .ivyforms-skeleton-textarea {
193 - height: 60px;
194 - }
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);
195 247
196 - .ivyforms-skeleton-button {
197 - height: 40px;
198 - width: 100px;
199 - background: #c0c5cb;
200 - border-radius: 6px;
201 - margin-top: 12px;
202 - }
248 + $securityConfig = $securityService->getFrontendSecurityConfig($formFields);
203 249
204 - .ivyforms-skeleton-animate {
205 - animation: pulse 1.5s infinite ease-in-out;
250 + if ($securityConfig['captcha']['enabled'] ?? false) {
251 + if (isset($securityConfig['captcha']['recaptcha'])) {
252 + ShortcodeHelper::enqueueGoogleRecaptchaScript($securityConfig['captcha']);
206 253 }
207 -
208 - @keyframes pulse {
209 - 0%, 100% {
210 - opacity: 1;
211 - }
212 - 50% {
213 - opacity: 0.4;
214 - }
254 + if (isset($securityConfig['captcha']['turnstile'])) {
255 + ShortcodeHelper::enqueueTurnstileScript($securityConfig['captcha']);
215 256 }
216 -
217 - .ivyforms-preview {
218 - width: 100%;
219 - max-width: 720px;
220 - margin: 0 auto;
221 - background: #fff;
222 - padding: 40px;
257 + if (isset($securityConfig['captcha']['hcaptcha'])) {
258 + ShortcodeHelper::enqueueHCaptchaScript($securityConfig['captcha']);
223 259 }
224 -
225 - .ivyforms-preview h1 {
226 - font-size: 50px !important;
227 - font-weight: normal !important;
228 - margin-top: 0 !important;
229 - margin-left: 0 !important;
230 - margin-bottom: 20px !important;
231 - padding: 0 !important;
232 - }'
233 - );
234 260 }
235 261
236 262 wp_localize_script(
237 263 $scriptId,
238 - 'wpIvyFormDataList',
239 - self::$formList
264 + 'wpIvyRecaptchaConfig',
265 + $securityConfig['captcha'] ?? []
240 266 );
241 267
242 268 wp_localize_script(
243 269 $scriptId,
244 - 'wpIvyLabels',
245 - array_merge(
246 - FrontendStrings::getFormRenderStrings(),
247 - )
270 + 'wpIvyTurnstileConfig',
271 + $securityConfig['captcha'] ?? []
248 272 );
249 273
250 274 wp_localize_script(
251 275 $scriptId,
252 - 'wpIvyApiSettings',
253 - [
254 - 'root' => esc_url_raw(rest_url()),
255 - 'nonce' => wp_create_nonce('wp_rest')
256 - ]
276 + 'wpIvyHCaptchaConfig',
277 + $securityConfig['captcha'] ?? []
257 278 );
258 279 }
259 280
260 281 /**
261 - * Add script attribute for module type
282 + * Backwards-compatible wrapper for script attribute filter
262 283 *
263 284 * @param array<string, mixed> $attributes
264 - *
265 285 * @return array<string, mixed>
266 286 */
267 287 public static function addScriptAttribute(array $attributes): array
268 288 {
269 - $scriptArray = [
270 - 'ivyforms_scripts_dev_main-js',
271 - 'ivyforms_scripts_dev_vite-js',
272 - 'ivyforms_script_index-js',
273 - 'ivyforms_scripts_public-js',
274 - ];
275 -
276 - if (isset($attributes['id']) && in_array($attributes['id'], $scriptArray, true)) {
277 - $attributes['type'] = 'module';
278 - }
279 -
280 - return $attributes;
289 + return ShortcodeHelper::addScriptAttribute($attributes);
281 290 }
282 291 }