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

369 lines 10.5 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 // 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 * @throws ValidationException
45 */
46 public static function shortcodeHandler(array $atts): string
47 {
48 $atts = shortcode_atts(
49 [
50 'id' => '0'
51 ],
52 $atts
53 );
54
55 if (empty($atts['id'])) {
56 return '';
57 }
58
59 $formId = (int)$atts['id'];
60 $form = IvyFormsAPI::getForm($formId);
61
62 // Check if the form is published
63 if (is_wp_error($form) || !($form->isPublished())) {
64 return '';
65 }
66
67 $counter = self::$counter++;
68
69 self::getFormList($formId, $counter);
70
71 self::enqueueScripts();
72
73 ob_start();
74 include IVYFORMS_PATH . '/view/frontend/view.php';
75 $html = ob_get_contents();
76 ob_end_clean();
77
78 return $html;
79 }
80
81 /**
82 * Get form list
83 *
84 * @param int $formId
85 * @param int $counter
86 *
87 * @return void
88 */
89 public static function getFormList(int $formId, int $counter)
90 {
91 $form = IvyFormsAPI::getForm($formId);
92
93 $fields = IvyFormsAPI::getFields($form->getId());
94
95 $confirmations = IvyFormsAPI::getConfirmations($form->getId());
96
97 $fieldsMap = self::prepareFieldsMap($fields);
98
99 $confirmationsMap = [];
100
101 foreach ($confirmations as $confirmation) {
102 $confirmationsMap[] = is_object($confirmation) && method_exists($confirmation, 'toArray')
103 ? $confirmation->toArray()
104 : $confirmation;
105 }
106
107 $form->setFields($fieldsMap);
108
109 $formArr = $form->toArray();
110
111 self::$formList[$counter] = [
112 'form' => $formArr,
113 'fields' => $fieldsMap,
114 'id' => $form->getId(),
115 'counter' => $counter,
116 'nonce' => wp_create_nonce('ivyformsFrontSubmissionNonce_' . $form->getId()),
117 'confirmations' => $confirmationsMap,
118 ];
119 }
120
121 /**
122 * Prepare fields map for a form
123 *
124 * @param array<int, object> $fields
125 * @return array<int, array<string, mixed>>
126 */
127 private static function prepareFieldsMap(array $fields): array
128 {
129 $fieldsMap = [];
130 foreach ($fields as $field) {
131 $fieldArr = $field->toArray();
132 if (
133 isset($fieldArr['type']) && in_array(
134 $fieldArr['type'],
135 ['radio', 'checkbox', 'select', 'multi-select'],
136 true
137 )
138 ) {
139 $fieldOptions = IvyFormsAPI::getFieldOptions($fieldArr['id']);
140 $fieldArr['fieldOptions'] = [];
141 foreach ($fieldOptions as $option) {
142 if (is_object($option) && method_exists($option, 'toArray')) {
143 $fieldArr['fieldOptions'][] = $option->toArray();
144 }
145 }
146 }
147 $fieldsMap[] = $fieldArr;
148 }
149 return $fieldsMap;
150 }
151
152 /**
153 * Enqueue handler
154 * @throws ValidationException
155 */
156 public static function enqueueScripts(): void
157 {
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()
184 );
185 }
186 self::enqueueRecaptchaScript($scriptId);
187
188 wp_localize_script(
189 $scriptId,
190 'wpIvyFormDataList',
191 self::$formList
192 );
193
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 'wpIvyUrls',
212 [
213 'pluginURL' => IVYFORMS_URL,
214 'siteURL' => esc_url_raw(site_url()),
215 ]
216 );
217
218 wp_localize_script(
219 $scriptId,
220 'wpIvyApiSettings',
221 [
222 'root' => esc_url_raw(rest_url()),
223 'nonce' => wp_create_nonce('wp_rest'),
224 'namespace' => Routes::$routeNamespace,
225 ]
226 );
227 wp_localize_script(
228 $scriptId,
229 'wpIvyDateFormat',
230 [
231 'dateFormat' => get_option('date_format'),
232 'timeFormat' => get_option('time_format'),
233 'dateTimeFormat' => get_option('date_format') . ' ' . get_option('time_format'),
234 'locale' => get_locale(),
235 ]
236 );
237
238 /**
239 * Allow external plugins/components to enqueue additional scripts/styles.
240 * @since 0.1.0
241 *
242 * Args:
243 * - string $scriptId The main script handle for the frontend (public) script
244 */
245 do_action('ivyforms/shortcode/enqueue_scripts', $scriptId);
246 }
247
248 /**
249 * Add script attribute for module type
250 *
251 * @param array<string, mixed> $attributes
252 *
253 * @return array<string, mixed>
254 */
255 public static function addScriptAttribute(array $attributes): array
256 {
257 $scriptArray = [
258 'ivyforms_scripts_dev_main-js',
259 'ivyforms_scripts_dev_vite-js',
260 'ivyforms_script_index-js',
261 'ivyforms_scripts_public-js',
262 ];
263
264 if (isset($attributes['id']) && in_array($attributes['id'], $scriptArray, true)) {
265 $attributes['type'] = 'module';
266 }
267
268 return $attributes;
269 }
270
271 /**
272 * Enqueue reCAPTCHA script if configured
273 *
274 * @param string $scriptId
275 * @throws ValidationException
276 */
277 public static function enqueueRecaptchaScript(string $scriptId): void
278 {
279 $securityService = self::getSecurityService();
280 $formFields = self::getAllFormFields();
281
282 // Get security configuration using our enhanced service
283 $securityConfig = $securityService->getFrontendSecurityConfig($formFields);
284
285 // Enqueue Google script if CAPTCHA is configured and has fields
286 if (self::shouldEnqueueCaptchaScript($securityConfig)) {
287 self::enqueueGoogleRecaptchaScript($securityConfig['captcha']);
288 }
289
290 wp_localize_script(
291 $scriptId,
292 'wpIvyRecaptchaConfig',
293 $securityConfig['captcha'] ?? []
294 );
295 }
296
297 /**
298 * Get security service instance
299 *
300 * @return SecurityService
301 */
302 private static function getSecurityService(): SecurityService
303 {
304 $settingsService = new SettingsService();
305 $securityFactory = new SecurityServiceFactory($settingsService);
306 return new SecurityService($securityFactory);
307 }
308
309 /**
310 * Get all form fields from currently loaded forms
311 *
312 * @return array<object> Array of form field objects
313 * @throws ValidationException
314 */
315 private static function getAllFormFields(): array
316 {
317 $allFields = [];
318
319 foreach (self::$formList as $formData) {
320 foreach ($formData['fields'] as $fieldData) {
321 // Create a simple object to match the expected interface
322 $field = FieldFactory::create($fieldData);
323
324 $allFields[] = $field;
325 }
326 }
327
328 return $allFields;
329 }
330
331 /**
332 * Check if CAPTCHA script should be enqueued
333 *
334 * @param array<string, mixed> $securityConfig
335 * @return bool
336 */
337 private static function shouldEnqueueCaptchaScript(array $securityConfig): bool
338 {
339 return isset($securityConfig['captcha']['recaptcha']) && $securityConfig['captcha']['enabled'];
340 }
341
342 /**
343 * Enqueue Google reCAPTCHA script using security service
344 *
345 * @param array<string, mixed> $captchaConfig
346 */
347 private static function enqueueGoogleRecaptchaScript(array $captchaConfig): void
348 {
349 if (!isset($captchaConfig['recaptcha'])) {
350 return;
351 }
352
353 $recaptchaConfig = $captchaConfig['recaptcha'];
354 $scriptUrl = $recaptchaConfig['scriptUrl'] ?? '';
355
356 if (empty($scriptUrl)) {
357 return;
358 }
359
360 wp_enqueue_script(
361 'google-recaptcha',
362 $scriptUrl,
363 [],
364 null, // No version for external script
365 true // Load in footer
366 );
367 }
368 }
369