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 +120 -188 0.6.1.1 → 1.5 View file →
@@ -6,22 +6,14 @@
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 -
15 10 use IvyForms\Common\Exceptions\ValidationException;
16 -use IvyForms\Factory\Field\FieldFactory;
17 -use IvyForms\Routes\Routes;
11 +use IvyForms\Common\Helpers\FieldHelper;
18 12 use IvyForms\Services\API\IvyFormsAPI;
19 -use IvyForms\Services\Settings\SettingsService;
20 -use IvyForms\Services\Translations\FrontendStrings;
21 -use IvyForms\Factory\Security\SecurityServiceFactory;
22 -use IvyForms\Services\Security\SecurityService;
23 -use IvyForms\Repository\FieldOptions\FieldOptionsRepository;
13 +use IvyForms\Services\Form\Style\FormStyleService;
14 +use IvyForms\Services\Placeholder\PlaceholderLocalizationHelper;
15 +use IvyForms\Services\Shortcode\Helpers\ShortcodeHelper;
24 16
25 17 /**
26 18 * Class ShortcodeService
27 19 *
@@ -30,9 +22,11 @@
30 22 class ShortcodeService
31 23 {
32 24 public static int $counter = 0;
33 25 /**
34 - * @var mixed[]
26 + * Array keyed by unique render counters (string) -> payload array
27 + *
28 + * @var array<string, array<string, mixed>>
35 29 */
36 30 public static array $formList = [];
37 31
38 32 /**
@@ -46,9 +40,11 @@
46 40 public static function shortcodeHandler(array $atts): string
47 41 {
48 42 $atts = shortcode_atts(
49 43 [
50 - 'id' => '0'
44 + 'id' => '0',
45 + 'show_title' => null,
46 + 'show_description' => null,
51 47 ],
52 48 $atts
53 49 );
54 50
@@ -63,15 +59,38 @@
63 59 if (is_wp_error($form) || !($form->isPublished())) {
64 60 return '';
65 61 }
66 62
67 - $counter = self::$counter++;
63 + // Generate a stable unique identifier for this render
64 + $counter = $formId . '_' . uniqid();
68 65
69 - 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 + }
70 75
76 + self::getFormList($formId, $counter, $overrides);
77 +
71 78 self::enqueueScripts();
72 79
80 + // Generate form-specific CSS
81 + $formCSS = self::generateFormCSS($formId);
82 +
73 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 +
74 93 include IVYFORMS_PATH . '/view/frontend/view.php';
75 94 $html = ob_get_contents();
76 95 ob_end_clean();
77 96
@@ -81,13 +100,14 @@
81 100 /**
82 101 * Get form list
83 102 *
84 103 * @param int $formId
85 - * @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)
86 106 *
87 107 * @return void
88 108 */
89 - public static function getFormList(int $formId, int $counter)
109 + public static function getFormList(int $formId, string $counter, array $overrides = [])
90 110 {
91 111 $form = IvyFormsAPI::getForm($formId);
92 112
93 113 $fields = IvyFormsAPI::getFields($form->getId());
@@ -107,8 +127,16 @@
107 127 $form->setFields($fieldsMap);
108 128
109 129 $formArr = $form->toArray();
110 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 +
111 139 self::$formList[$counter] = [
112 140 'form' => $formArr,
113 141 'fields' => $fieldsMap,
114 142 'id' => $form->getId(),
@@ -125,17 +153,16 @@
125 153 * @return array<int, array<string, mixed>>
126 154 */
127 155 private static function prepareFieldsMap(array $fields): array
128 156 {
157 + $generalData = PlaceholderLocalizationHelper::getGeneralData();
129 158 $fieldsMap = [];
130 159 foreach ($fields as $field) {
131 160 $fieldArr = $field->toArray();
161 + $fieldType = isset($fieldArr['type']) ? (string) $fieldArr['type'] : '';
132 162 if (
133 - isset($fieldArr['type']) && in_array(
134 - $fieldArr['type'],
135 - ['radio', 'checkbox', 'select', 'multi-select'],
136 - true
137 - )
163 + $fieldType !== ''
164 + && (FieldHelper::fieldTypeHasOptions($fieldType) || $fieldType === 'rating')
138 165 ) {
139 166 $fieldOptions = IvyFormsAPI::getFieldOptions($fieldArr['id']);
140 167 $fieldArr['fieldOptions'] = [];
141 168 foreach ($fieldOptions as $option) {
@@ -143,140 +170,94 @@
143 170 $fieldArr['fieldOptions'][] = $option->toArray();
144 171 }
145 172 }
146 173 }
147 - $fieldsMap[] = $fieldArr;
174 + $fieldsMap[] = self::resolveFieldPlaceholders($fieldArr, $generalData);
148 175 }
149 176 return $fieldsMap;
150 177 }
151 178
152 179 /**
153 - * Enqueue handler
154 - * @throws ValidationException
180 + * @param array<string, mixed> $fieldArr
181 + * @param array<string, string|int> $generalData
182 + * @return array<string, mixed>
155 183 */
156 - public static function enqueueScripts(): void
184 + private static function resolveFieldPlaceholders(array $fieldArr, array $generalData): array
157 185 {
158 - $scriptId = IVYFORMS_DEV ? 'ivyforms_scripts_dev_vite' : 'ivyforms_script_index'; // @phpstan-ignore-line
159 - $scriptSrc = IVYFORMS_DEV ? // @phpstan-ignore-line
160 - 'http://localhost:5173/src/assets/js/public/public.ts' :
161 - IVYFORMS_URL . 'frontend/dist/public.js';
162 -
163 - $styleSrc = IVYFORMS_DEV ? '' : IVYFORMS_URL . 'frontend/dist/index.css'; // @phpstan-ignore-line
164 - $stylePublicSrc = IVYFORMS_DEV ? '' : IVYFORMS_URL . 'frontend/dist/public.css'; // @phpstan-ignore-line
165 - $scriptVersion = IVYFORMS_DEV ? null : IVYFORMS_VERSION;// @phpstan-ignore-line
166 -
167 - wp_enqueue_script(
168 - $scriptId,
169 - $scriptSrc,
170 - [],
171 - $scriptVersion,
172 - true
173 - );
174 -
175 - // TODO: Improve this with merging styles
176 - if ($styleSrc) { // @phpstan-ignore-line
177 - wp_enqueue_style('ivyforms_style_index', $styleSrc, [], $scriptVersion);
178 - wp_enqueue_style('ivyforms_style_public', $stylePublicSrc, [], $scriptVersion);
179 -
180 - // Use helper for inline styles
181 - wp_add_inline_style(
182 - 'ivyforms_style_index',
183 - InlineStyleHelper::getFrontendInlineCss()
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
184 193 );
185 194 }
186 - self::enqueueRecaptchaScript($scriptId);
187 195
188 - wp_localize_script(
189 - $scriptId,
190 - 'wpIvyFormDataList',
191 - self::$formList
192 - );
196 + return $fieldArr;
197 + }
193 198
194 - $frontendLabels = array_merge(
195 - FrontendStrings::getFormRenderStrings()
196 - );
197 -
198 - /**
199 - * Filter frontend labels before localizing
200 - * Allows Pro version to merge additional strings
201 - *
202 - * @since 0.1.0
203 - * @param array $frontendLabels Array of frontend label strings
204 - */
205 - $frontendLabels = apply_filters('ivyforms/shortcode/frontend_labels', $frontendLabels);
206 -
207 - wp_localize_script($scriptId, 'wpIvyLabels', $frontendLabels);
208 -
209 - wp_localize_script(
210 - $scriptId,
211 - 'wpIvyApiSettings',
212 - [
213 - 'root' => esc_url_raw(rest_url()),
214 - 'nonce' => wp_create_nonce('wp_rest'),
215 - 'namespace' => Routes::$routeNamespace,
216 - ]
217 - );
218 - wp_localize_script(
219 - $scriptId,
220 - 'wpIvyDateFormat',
221 - [
222 - 'dateFormat' => get_option('date_format'),
223 - 'timeFormat' => get_option('time_format'),
224 - 'dateTimeFormat' => get_option('date_format') . ' ' . get_option('time_format'),
225 - 'locale' => get_locale(),
226 - ]
227 - );
228 -
229 - /**
230 - * Allow external plugins/components to enqueue additional scripts/styles.
231 - * @since 0.1.0
232 - *
233 - * Args:
234 - * - string $scriptId The main script handle for the frontend (public) script
235 - */
236 - do_action('ivyforms/shortcode/enqueue_scripts', $scriptId);
199 + /**
200 + * Enqueue handler
201 + */
202 + public static function enqueueScripts(): void
203 + {
204 + ShortcodeHelper::registerFrontendAssets(self::$formList);
237 205 }
238 206
239 207 /**
240 - * Add script attribute for module type
208 + * Generate CSS for a specific form
241 209 *
242 - * @param array<string, mixed> $attributes
243 - *
244 - * @return array<string, mixed>
210 + * @param int $formId
211 + * @return string
245 212 */
246 - public static function addScriptAttribute(array $attributes): array
213 + private static function generateFormCSS(int $formId): string
247 214 {
248 - $scriptArray = [
249 - 'ivyforms_scripts_dev_main-js',
250 - 'ivyforms_scripts_dev_vite-js',
251 - 'ivyforms_script_index-js',
252 - 'ivyforms_scripts_public-js',
253 - ];
215 + foreach (self::$formList as $formData) {
216 + if ($formData['id'] === $formId) {
217 + $formPayload = $formData['form'] ?? null;
218 + if (!is_array($formPayload)) {
219 + return '';
220 + }
254 221
255 - if (isset($attributes['id']) && in_array($attributes['id'], $scriptArray, true)) {
256 - $attributes['type'] = 'module';
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 + }
257 232 }
258 233
259 - return $attributes;
234 + return '';
260 235 }
261 236
262 237 /**
263 - * Enqueue reCAPTCHA script if configured
238 + * Enqueue CAPTCHA scripts (reCAPTCHA, Turnstile, etc.) if configured
264 239 *
265 240 * @param string $scriptId
266 - * @throws ValidationException
267 241 */
268 242 public static function enqueueRecaptchaScript(string $scriptId): void
269 243 {
270 - $securityService = self::getSecurityService();
271 - $formFields = self::getAllFormFields();
244 + // Delegate captcha discovery/enqueue to ShortcodeHelper while keeping BC wrapper here
245 + $securityService = ShortcodeHelper::getSecurityService();
246 + $formFields = ShortcodeHelper::getAllFormFields(self::$formList);
272 247
273 - // Get security configuration using our enhanced service
274 248 $securityConfig = $securityService->getFrontendSecurityConfig($formFields);
275 249
276 - // Enqueue Google script if CAPTCHA is configured and has fields
277 - if (self::shouldEnqueueCaptchaScript($securityConfig)) {
278 - self::enqueueGoogleRecaptchaScript($securityConfig['captcha']);
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 + }
279 260 }
280 261
281 262 wp_localize_script(
282 263 $scriptId,
@@ -282,78 +263,29 @@
282 263 $scriptId,
283 264 'wpIvyRecaptchaConfig',
284 265 $securityConfig['captcha'] ?? []
285 266 );
286 - }
287 267
288 - /**
289 - * Get security service instance
290 - *
291 - * @return SecurityService
292 - */
293 - private static function getSecurityService(): SecurityService
294 - {
295 - $settingsService = new SettingsService();
296 - $securityFactory = new SecurityServiceFactory($settingsService);
297 - return new SecurityService($securityFactory);
298 - }
268 + wp_localize_script(
269 + $scriptId,
270 + 'wpIvyTurnstileConfig',
271 + $securityConfig['captcha'] ?? []
272 + );
299 273
300 - /**
301 - * Get all form fields from currently loaded forms
302 - *
303 - * @return array<object> Array of form field objects
304 - * @throws ValidationException
305 - */
306 - private static function getAllFormFields(): array
307 - {
308 - $allFields = [];
309 -
310 - foreach (self::$formList as $formData) {
311 - foreach ($formData['fields'] as $fieldData) {
312 - // Create a simple object to match the expected interface
313 - $field = FieldFactory::create($fieldData);
314 -
315 - $allFields[] = $field;
316 - }
317 - }
318 -
319 - return $allFields;
274 + wp_localize_script(
275 + $scriptId,
276 + 'wpIvyHCaptchaConfig',
277 + $securityConfig['captcha'] ?? []
278 + );
320 279 }
321 280
322 281 /**
323 - * Check if CAPTCHA script should be enqueued
282 + * Backwards-compatible wrapper for script attribute filter
324 283 *
325 - * @param array<string, mixed> $securityConfig
326 - * @return bool
284 + * @param array<string, mixed> $attributes
285 + * @return array<string, mixed>
327 286 */
328 - private static function shouldEnqueueCaptchaScript(array $securityConfig): bool
287 + public static function addScriptAttribute(array $attributes): array
329 288 {
330 - return isset($securityConfig['captcha']['recaptcha']) && $securityConfig['captcha']['enabled'];
331 - }
332 -
333 - /**
334 - * Enqueue Google reCAPTCHA script using security service
335 - *
336 - * @param array<string, mixed> $captchaConfig
337 - */
338 - private static function enqueueGoogleRecaptchaScript(array $captchaConfig): void
339 - {
340 - if (!isset($captchaConfig['recaptcha'])) {
341 - return;
342 - }
343 -
344 - $recaptchaConfig = $captchaConfig['recaptcha'];
345 - $scriptUrl = $recaptchaConfig['scriptUrl'] ?? '';
346 -
347 - if (empty($scriptUrl)) {
348 - return;
349 - }
350 -
351 - wp_enqueue_script(
352 - 'google-recaptcha',
353 - $scriptUrl,
354 - [],
355 - null, // No version for external script
356 - true // Load in footer
357 - );
289 + return ShortcodeHelper::addScriptAttribute($attributes);
358 290 }
359 291 }