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

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