PluginProbe
The Innovative Form Builder – IvyForms / 0.4
The Innovative Form Builder – IvyForms v0.4
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 0.4, at backend/src/Services/Shortcode/ShortcodeService.php

429 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @copyright © TMS-Plugins. All rights reserved.
5 * @licence See LICENCE.md for license details.
6 */
7
8 namespace IvyForms\Services\Shortcode;
9
10 // phpcs:disable PSR1.Files.SideEffects
11 if (!defined('ABSPATH')) {
12 exit; // Exit if accessed directly
13 }
14
15 use IvyForms\Common\Exceptions\ValidationException;
16 use IvyForms\Factory\Field\FieldFactory;
17 use IvyForms\Routes\Routes;
18 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;
24
25 /**
26 * Class ShortcodeService
27 *
28 * @package IvyForms\Services\Shortcode
29 */
30 class ShortcodeService
31 {
32 public static int $counter = 0;
33 /**
34 * @var mixed[]
35 */
36 public static array $formList = [];
37
38 /**
39 * Shortcode handler
40 *
41 * @param mixed[] $atts
42 *
43 * @return string
44 */
45 public static function shortcodeHandler(array $atts): string
46 {
47 $atts = shortcode_atts(
48 [
49 'id' => '0'
50 ],
51 $atts
52 );
53
54 if (empty($atts['id'])) {
55 return '';
56 }
57
58 $formId = (int)$atts['id'];
59 $form = IvyFormsAPI::getForm($formId);
60
61 // Check if the form is published
62 if (is_wp_error($form) || !($form->isPublished())) {
63 return '';
64 }
65
66 $counter = self::$counter++;
67
68 self::getFormList($formId, $counter);
69
70 self::enqueueScripts();
71
72 ob_start();
73 include IVYFORMS_PATH . '/view/frontend/view.php';
74 $html = ob_get_contents();
75 ob_end_clean();
76
77 return $html;
78 }
79
80 /**
81 * Get form list
82 *
83 * @param int $formId
84 * @param int $counter
85 *
86 * @return void
87 */
88 public static function getFormList(int $formId, int $counter)
89 {
90 $form = IvyFormsAPI::getForm($formId);
91
92 $fields = IvyFormsAPI::getFields($form->getId());
93
94 $confirmations = IvyFormsAPI::getConfirmations($form->getId());
95
96 $fieldsMap = self::prepareFieldsMap($fields);
97
98 $confirmationsMap = [];
99
100 foreach ($confirmations as $confirmation) {
101 $confirmationsMap[] = is_object($confirmation) && method_exists($confirmation, 'toArray')
102 ? $confirmation->toArray()
103 : $confirmation;
104 }
105
106 $form->setFields($fieldsMap);
107
108 $formArr = $form->toArray();
109
110 self::$formList[$counter] = [
111 'form' => $formArr,
112 'fields' => $fieldsMap,
113 'id' => $form->getId(),
114 'counter' => $counter,
115 'nonce' => wp_create_nonce('ivyformsFrontSubmissionNonce_' . $form->getId()),
116 'confirmations' => $confirmationsMap,
117 ];
118 }
119
120 /**
121 * Prepare fields map for a form
122 *
123 * @param array<int, object> $fields
124 * @return array<int, array<string, mixed>>
125 */
126 private static function prepareFieldsMap(array $fields): array
127 {
128 $fieldsMap = [];
129 foreach ($fields as $field) {
130 $fieldArr = $field->toArray();
131 if (isset($fieldArr['type']) && $fieldArr['type'] === 'radio') {
132 $fieldOptions = IvyFormsAPI::getFieldOptions($fieldArr['id']);
133 $fieldArr['fieldOptions'] = [];
134 foreach ($fieldOptions as $option) {
135 if (is_object($option) && method_exists($option, 'toArray')) {
136 $fieldArr['fieldOptions'][] = $option->toArray();
137 }
138 }
139 }
140 $fieldsMap[] = $fieldArr;
141 }
142 return $fieldsMap;
143 }
144
145 /**
146 * Enqueue handler
147 */
148 public static function enqueueScripts(): void
149 {
150 $scriptId = IVYFORMS_DEV ? 'ivyforms_scripts_dev_vite' : 'ivyforms_script_index'; // @phpstan-ignore-line
151 $scriptSrc = IVYFORMS_DEV ? // @phpstan-ignore-line
152 'http://localhost:5173/src/assets/js/public/public.ts' :
153 IVYFORMS_URL . 'frontend/dist/public.js';
154
155 $styleSrc = IVYFORMS_DEV ? '' : IVYFORMS_URL . 'frontend/dist/index.css'; // @phpstan-ignore-line
156 $stylePublicSrc = IVYFORMS_DEV ? '' : IVYFORMS_URL . 'frontend/dist/public.css'; // @phpstan-ignore-line
157 $scriptVersion = IVYFORMS_DEV ? null : IVYFORMS_VERSION;// @phpstan-ignore-line
158
159 wp_enqueue_script(
160 $scriptId,
161 $scriptSrc,
162 [],
163 $scriptVersion,
164 true
165 );
166
167 // TODO: Improve this with merging styles
168 if ($styleSrc) { // @phpstan-ignore-line
169 wp_enqueue_style(
170 'ivyforms_style_index',
171 $styleSrc,
172 [],
173 $scriptVersion
174 );
175 wp_enqueue_style(
176 'ivyforms_style_public',
177 $stylePublicSrc,
178 [],
179 $scriptVersion
180 );
181
182 // TODO Create a helper service for adding styles
183 wp_add_inline_style(
184 'ivyforms_style_index',
185 /** @lang CSS */
186 '.ivyforms-skeleton {
187 width: 100%;
188 max-width: 720px;
189 margin: 0 auto;
190 }
191
192 .ivyforms-skeleton-title {
193 height: 28px;
194 width: 40%;
195 background: #dbdee3;
196 margin-bottom: 12px;
197 }
198
199 .ivyforms-skeleton-field {
200 margin-bottom: 16px;
201 }
202
203 .ivyforms-skeleton-label {
204 height: 22px;
205 width: 20%;
206 background: #dbdee3;
207 font-size: 14px;
208 border-radius: 4px;
209 margin-bottom: 4px;
210 }
211
212 .ivyforms-skeleton-input {
213 line-height: 22px;
214 font-size: 16px;
215 background: #dbdee3;
216 border-radius: 8px;
217 width: 100%;
218 }
219
220 .ivyforms-skeleton-email,
221 .ivyforms-skeleton-textarea,
222 .ivyforms-skeleton-text,
223 .ivyforms-skeleton-number,
224 .ivyforms-skeleton-phone {
225 height: 40px;
226 }
227
228 .ivyforms-skeleton-textarea {
229 height: 60px;
230 }
231
232 .ivyforms-skeleton-button {
233 height: 40px;
234 width: 100px;
235 background: #c0c5cb;
236 border-radius: 6px;
237 margin-top: 12px;
238 }
239
240 .ivyforms-skeleton-animate {
241 animation: pulse 1.5s infinite ease-in-out;
242 }
243
244 @keyframes pulse {
245 0%, 100% {
246 opacity: 1;
247 }
248 50% {
249 opacity: 0.4;
250 }
251 }
252
253 .ivyforms-preview {
254 width: 100%;
255 max-width: 720px;
256 margin: 0 auto;
257 background: #fff;
258 padding: 40px;
259 }
260
261 .ivyforms-preview h1 {
262 font-size: 50px !important;
263 font-weight: normal !important;
264 margin-top: 0 !important;
265 margin-left: 0 !important;
266 margin-bottom: 20px !important;
267 padding: 0 !important;
268 }'
269 );
270 }
271 self::enqueueRecaptchaScript($scriptId);
272
273 wp_localize_script(
274 $scriptId,
275 'wpIvyFormDataList',
276 self::$formList
277 );
278
279 wp_localize_script(
280 $scriptId,
281 'wpIvyLabels',
282 array_merge(
283 FrontendStrings::getFormRenderStrings(),
284 )
285 );
286
287 wp_localize_script(
288 $scriptId,
289 'wpIvyApiSettings',
290 [
291 'root' => esc_url_raw(rest_url()),
292 'nonce' => wp_create_nonce('wp_rest'),
293 'namespace' => Routes::$routeNamespace,
294 ]
295 );
296 wp_localize_script(
297 $scriptId,
298 'wpIvyDateFormat',
299 [
300 'dateFormat' => get_option('date_format'),
301 'timeFormat' => get_option('time_format'),
302 'dateTimeFormat' => get_option('date_format') . ' ' . get_option('time_format'),
303 'locale' => get_locale(),
304 ]
305 );
306 }
307
308 /**
309 * Add script attribute for module type
310 *
311 * @param array<string, mixed> $attributes
312 *
313 * @return array<string, mixed>
314 */
315 public static function addScriptAttribute(array $attributes): array
316 {
317 $scriptArray = [
318 'ivyforms_scripts_dev_main-js',
319 'ivyforms_scripts_dev_vite-js',
320 'ivyforms_script_index-js',
321 'ivyforms_scripts_public-js',
322 ];
323
324 if (isset($attributes['id']) && in_array($attributes['id'], $scriptArray, true)) {
325 $attributes['type'] = 'module';
326 }
327
328 return $attributes;
329 }
330
331 /**
332 * Enqueue reCAPTCHA script if configured
333 *
334 * @param string $scriptId
335 * @throws ValidationException
336 */
337 public static function enqueueRecaptchaScript(string $scriptId): void
338 {
339 $securityService = self::getSecurityService();
340 $formFields = self::getAllFormFields();
341
342 // Get security configuration using our enhanced service
343 $securityConfig = $securityService->getFrontendSecurityConfig($formFields);
344
345 // Enqueue Google script if CAPTCHA is configured and has fields
346 if (self::shouldEnqueueCaptchaScript($securityConfig)) {
347 self::enqueueGoogleRecaptchaScript($securityConfig['captcha']);
348 }
349
350 wp_localize_script(
351 $scriptId,
352 'wpIvyRecaptchaConfig',
353 $securityConfig['captcha'] ?? []
354 );
355 }
356
357 /**
358 * Get security service instance
359 *
360 * @return SecurityService
361 */
362 private static function getSecurityService(): SecurityService
363 {
364 $settingsService = new SettingsService();
365 $securityFactory = new SecurityServiceFactory($settingsService);
366 return new SecurityService($securityFactory);
367 }
368
369 /**
370 * Get all form fields from currently loaded forms
371 *
372 * @return array<object> Array of form field objects
373 * @throws ValidationException
374 */
375 private static function getAllFormFields(): array
376 {
377 $allFields = [];
378
379 foreach (self::$formList as $formData) {
380 foreach ($formData['fields'] as $fieldData) {
381 // Create a simple object to match the expected interface
382 $field = FieldFactory::create($fieldData);
383
384 $allFields[] = $field;
385 }
386 }
387
388 return $allFields;
389 }
390
391 /**
392 * Check if CAPTCHA script should be enqueued
393 *
394 * @param array<string, mixed> $securityConfig
395 * @return bool
396 */
397 private static function shouldEnqueueCaptchaScript(array $securityConfig): bool
398 {
399 return isset($securityConfig['captcha']['recaptcha']) && $securityConfig['captcha']['enabled'];
400 }
401
402 /**
403 * Enqueue Google reCAPTCHA script using security service
404 *
405 * @param array<string, mixed> $captchaConfig
406 */
407 private static function enqueueGoogleRecaptchaScript(array $captchaConfig): void
408 {
409 if (!isset($captchaConfig['recaptcha'])) {
410 return;
411 }
412
413 $recaptchaConfig = $captchaConfig['recaptcha'];
414 $scriptUrl = $recaptchaConfig['scriptUrl'] ?? '';
415
416 if (empty($scriptUrl)) {
417 return;
418 }
419
420 wp_enqueue_script(
421 'google-recaptcha',
422 $scriptUrl,
423 [],
424 null, // No version for external script
425 true // Load in footer
426 );
427 }
428 }
429