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

360 lines 10.2 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 '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);
237 }
238
239 /**
240 * Add script attribute for module type
241 *
242 * @param array<string, mixed> $attributes
243 *
244 * @return array<string, mixed>
245 */
246 public static function addScriptAttribute(array $attributes): array
247 {
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 ];
254
255 if (isset($attributes['id']) && in_array($attributes['id'], $scriptArray, true)) {
256 $attributes['type'] = 'module';
257 }
258
259 return $attributes;
260 }
261
262 /**
263 * Enqueue reCAPTCHA script if configured
264 *
265 * @param string $scriptId
266 * @throws ValidationException
267 */
268 public static function enqueueRecaptchaScript(string $scriptId): void
269 {
270 $securityService = self::getSecurityService();
271 $formFields = self::getAllFormFields();
272
273 // Get security configuration using our enhanced service
274 $securityConfig = $securityService->getFrontendSecurityConfig($formFields);
275
276 // Enqueue Google script if CAPTCHA is configured and has fields
277 if (self::shouldEnqueueCaptchaScript($securityConfig)) {
278 self::enqueueGoogleRecaptchaScript($securityConfig['captcha']);
279 }
280
281 wp_localize_script(
282 $scriptId,
283 'wpIvyRecaptchaConfig',
284 $securityConfig['captcha'] ?? []
285 );
286 }
287
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 }
299
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;
320 }
321
322 /**
323 * Check if CAPTCHA script should be enqueued
324 *
325 * @param array<string, mixed> $securityConfig
326 * @return bool
327 */
328 private static function shouldEnqueueCaptchaScript(array $securityConfig): bool
329 {
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 );
358 }
359 }
360