PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.12
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.12
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / Form / FormService.php

FormService.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.12, at app/Services/Form/FormService.php

780 lines 31.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Services\Form;
4
5 use Exception;
6 use FluentForm\App\Helpers\Helper;
7 use FluentForm\App\Models\Form;
8 use FluentForm\App\Models\FormMeta;
9 use FluentForm\Framework\Foundation\App;
10 use FluentForm\Framework\Support\Arr;
11 use FluentForm\App\Modules\Form\FormFieldsParser;
12 use FluentForm\App\Services\FluentConversational\Classes\Converter\Converter;
13
14 class FormService
15 {
16 /** @var \FluentForm\Framework\Foundation\Application */
17 protected $app;
18
19 /** @var \FluentForm\App\Models\Form|\FluentForm\Framework\Database\Query\Builder */
20 protected $model;
21
22 /** @var \FluentForm\App\Services\Form\Updater */
23 protected $updater;
24
25 /** @var \FluentForm\App\Services\Form\Duplicator */
26 protected $duplicator;
27
28 /** @var \FluentForm\App\Services\Form\Fields */
29 protected $fields;
30
31
32 public function __construct()
33 {
34 $this->model = new Form();
35 $this->fields = new Fields();
36 $this->app = App::getInstance();
37 $this->updater = new Updater();
38 $this->duplicator = new Duplicator();
39 }
40
41 /**
42 * Get the paginated forms matching search criteria.
43 *
44 * @param array $attributes
45 * @return array
46 */
47 public function get($attributes = [])
48 {
49 return fluentFormApi('forms')->forms([
50 'search' => Arr::get($attributes, 'search'),
51 'status' => Arr::get($attributes, 'status'),
52 'filter_by' => Arr::get($attributes, 'filter_by', 'all'),
53 'date_range' => Arr::get($attributes, 'date_range', []),
54 'sort_column' => Arr::get($attributes, 'sort_column', 'id'),
55 'sort_by' => Arr::get($attributes, 'sort_by', 'DESC'),
56 'per_page' => Arr::get($attributes, 'per_page', 10),
57 'page' => Arr::get($attributes, 'page', 1),
58 ]);
59 }
60
61 /**
62 * Store a form with its associated meta.
63 *
64 * @param array $attributes
65 * @return \FluentForm\App\Models\Form $form
66 * @throws Exception
67 */
68 public function store($attributes = [])
69 {
70 try {
71 $predefinedForm = Form::resolvePredefinedForm($attributes);
72
73 $data = Form::prepare($predefinedForm);
74
75 $form = $this->model->create($data);
76
77 $form->title = $form->title . ' (#' . $form->id . ')';
78
79 $form->save();
80
81 $formMeta = FormMeta::prepare($attributes, $predefinedForm);
82
83 FormMeta::store($form, $formMeta);
84
85 do_action_deprecated(
86 'fluentform_inserted_new_form',
87 [
88 $form->id,
89 $data
90 ],
91 FLUENTFORM_FRAMEWORK_UPGRADE,
92 'fluentform/inserted_new_form',
93 'Use fluentform/inserted_new_form instead of fluentform_inserted_new_form.'
94 );
95
96 do_action('fluentform/inserted_new_form', $form->id, $data);
97
98 return $form;
99 } catch (Exception $e) {
100 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- surfaced as JSON by the REST layer, never echoed as HTML
101 throw new Exception($e->getMessage());
102 }
103 }
104
105 /**
106 * Duplicate a form with its associated meta.
107 *
108 * @param array $attributes
109 * @return \FluentForm\App\Models\Form $form
110 * @throws Exception
111 */
112 public function duplicate($attributes = [])
113 {
114 $formId = Arr::get($attributes, 'form_id');
115
116 $existingForm = $this->model->with([
117 'formMeta' => function ($formMeta) {
118 return $formMeta->whereNotIn('meta_key', ['_total_views']);
119 },
120 ])->find($formId);
121
122 if (!$existingForm) {
123 throw new Exception(
124 esc_html__("The form couldn't be found.", 'fluentform')
125 );
126 }
127
128 $data = Form::prepare($existingForm->toArray());
129
130 $form = $this->model->create($data);
131
132 // Rename the form name here
133 $form->title = $form->title . ' (#' . $form->id . ')';
134 $form->save();
135
136 $this->duplicator->duplicateFormMeta($form, $existingForm);
137 $this->duplicator->maybeDuplicateFiles($form, $existingForm, $data);
138
139 do_action_deprecated(
140 'fluentform_form_duplicated',
141 [
142 $form->id
143 ],
144 FLUENTFORM_FRAMEWORK_UPGRADE,
145 'fluentform/form_duplicated',
146 'Use fluentform/form_duplicated instead of fluentform_form_duplicated.'
147 );
148 do_action('fluentform/form_duplicated', $form->id);
149
150 return $form;
151 }
152
153 public function find($id)
154 {
155 try {
156 return $this->model->with('formMeta')->findOrFail($id);
157 } catch (Exception $e) {
158 throw new Exception(
159 esc_html__("The form couldn't be found.", 'fluentform')
160 );
161 }
162 }
163
164 public function delete($id)
165 {
166 Form::remove($id);
167 }
168
169 /**
170 * Update a form with its relevant fields.
171 *
172 * @param array $attributes
173 * @return \FluentForm\App\Models\Form $form
174 * @throws Exception
175 */
176 public function update($attributes = [])
177 {
178 return $this->updater->update($attributes);
179 }
180
181 /**
182 * Duplicate a form with its associated meta.
183 *
184 * @param int $id
185 * @return \FluentForm\App\Models\Form $form
186 * @throws Exception
187 */
188 public function convert($id)
189 {
190 try {
191 $form = Form::with('conversationalMeta')->findOrFail($id);
192 } catch (Exception $e) {
193 throw new Exception(
194 esc_html__("The form couldn't be found.", 'fluentform')
195 );
196 }
197
198 $isConversationalForm = $form->conversationalMeta && 'yes' === $form->conversationalMeta->value;
199
200 if ($isConversationalForm) {
201 $conversationalMetaValue = 'no';
202 } else {
203 $form->fill([
204 'form_fields' => Converter::convertExistingForm($form),
205 ])->save();
206
207 $conversationalMetaValue = 'yes';
208 }
209
210 FormMeta::persist($form->id, 'is_conversion_form', $conversationalMetaValue);
211
212 return $form;
213 }
214
215 public function templates()
216 {
217 $forms = [
218 'Basic' => [],
219 ];
220
221 $predefinedForms = $this->model::findPredefinedForm();
222
223 foreach ($predefinedForms as $key => $item) {
224 if (!$item['category']) {
225 $item['category'] = 'Other';
226 }
227
228 if (!isset($forms[$item['category']])) {
229 $forms[$item['category']] = [];
230 }
231
232 $itemClass = 'item_' . str_replace([' ', '&', '/'], '_', strtolower($item['category']));
233
234 if (empty($item['screenshot'])) {
235 $itemClass .= ' item_no_image';
236 } else {
237 $itemClass .= ' item_has_image';
238 }
239
240 $forms[$item['category']][$key] = [
241 'class' => $itemClass,
242 'tags' => Arr::get($item, 'tag', ''),
243 'title' => Arr::get($item, 'title', ''),
244 'brief' => Arr::get($item, 'brief', ''),
245 'category' => Arr::get($item, 'category', ''),
246 'screenshot' => Arr::get($item, 'screenshot', ''),
247 'createable' => $item['createable'] ?? false,
248 'prev_link' => $item['prev_link'] ?? false,
249 'is_pro' => $item['is_pro'] ?? false,
250 'type' => Arr::get($item, 'type', 'form'),
251 ];
252 }
253 $dropDownForms = [
254 'post' => [
255 'title' => 'Post Form',
256 ],
257 ];
258 $dropDownForms = apply_filters_deprecated(
259 'fluentform-predefined-dropDown-forms',
260 [
261 $dropDownForms
262 ],
263 FLUENTFORM_FRAMEWORK_UPGRADE,
264 'fluentform/predefined_dropdown_forms',
265 'Use fluentform/predefined_dropdown_forms instead of fluentform-predefined-dropDown-forms.'
266 );
267
268 return [
269 'forms' => $forms,
270 'categories' => array_keys($forms),
271 'predefined_dropDown_forms' => apply_filters('fluentform/predefined_dropdown_forms', $dropDownForms),
272 ];
273 }
274
275 public function components($formId)
276 {
277 /**
278 * @var \FluentForm\App\Services\FormBuilder\Components
279 */
280 $components = $this->app->make('components');
281
282 do_action_deprecated(
283 'fluent_editor_init',
284 [
285 $components
286 ],
287 FLUENTFORM_FRAMEWORK_UPGRADE,
288 'fluentform/editor_init',
289 'Use fluentform/editor_init instead of fluent_editor_init.'
290 );
291
292 $this->app->doAction('fluentform/editor_init', $components);
293
294 $editorComponents = $components->sort()->toArray();
295
296 $editorComponents = apply_filters_deprecated(
297 'fluent_editor_components',
298 [
299 $editorComponents,
300 $formId
301 ],
302 FLUENTFORM_FRAMEWORK_UPGRADE,
303 'fluentform/editor_components',
304 'Use fluentform/editor_components instead of fluent_editor_components.'
305 );
306
307 return apply_filters('fluentform/editor_components', $editorComponents, $formId);
308 }
309
310 public function getDisabledComponents()
311 {
312 $isReCaptchaDisabled = !get_option('_fluentform_reCaptcha_keys_status', false);
313 $isHCaptchaDisabled = !get_option('_fluentform_hCaptcha_keys_status', false);
314 $isTurnstileDisabled = !get_option('_fluentform_turnstile_keys_status', false);
315
316 $disabled = [
317 'recaptcha' => [
318 'disabled' => $isReCaptchaDisabled,
319 'title' => __('reCaptcha', 'fluentform'),
320 'description' => __('Please enter a valid API key on Global Settings->Security->reCaptcha', 'fluentform'),
321 'hidePro' => true,
322 ],
323 'hcaptcha' => [
324 'disabled' => $isHCaptchaDisabled,
325 'title' => __('hCaptcha', 'fluentform'),
326 'description' => __('Please enter a valid API key on Global Settings->Security->hCaptcha', 'fluentform'),
327 'hidePro' => true,
328 ],
329 'turnstile' => [
330 'disabled' => $isTurnstileDisabled,
331 'title' => __('Turnstile', 'fluentform'),
332 'description' => __('Please enter a valid API key on Global Settings->Security->Turnstile', 'fluentform'),
333 'hidePro' => true,
334 ],
335 ];
336
337 if (!Helper::hasPro()) {
338 $disabled['input_image'] = [
339 'disabled' => true,
340 'title' => __('Image Upload', 'fluentform'),
341 'description' => __('Image Upload is not available with the free version. Please upgrade to pro to get all the advanced features.',
342 'fluentform'),
343 'image' => '',
344 'video' => 'https://www.youtube.com/embed/Yb3FSoZl9Zg',
345 ];
346 $disabled['input_file'] = [
347 'disabled' => true,
348 'title' => __('File Upload', 'fluentform'),
349 'description' => __('File Upload is not available with the free version. Please upgrade to pro to get all the advanced features.',
350 'fluentform'),
351 'image' => '',
352 'video' => 'https://www.youtube.com/embed/bXbTbNPM_4k',
353 ];
354 $disabled['shortcode'] = [
355 'disabled' => true,
356 'title' => __('Shortcode', 'fluentform'),
357 'description' => __('Shortcode is not available with the free version. Please upgrade to pro to get all the advanced features.',
358 'fluentform'),
359 'image' => '',
360 'video' => 'https://www.youtube.com/embed/op3mEQxX1MM',
361 ];
362 $disabled['action_hook'] = [
363 'disabled' => true,
364 'title' => __('Action Hook', 'fluentform'),
365 'description' => __('Action Hook is not available with the free version. Please upgrade to pro to get all the advanced features.',
366 'fluentform'),
367 'image' => fluentformMix('img/pro-fields/action-hook.png'),
368 'video' => '',
369 ];
370 $disabled['form_step'] = [
371 'disabled' => true,
372 'title' => __('Form Step', 'fluentform'),
373 'description' => __('Form Step is not available with the free version. Please upgrade to pro to get all the advanced features.',
374 'fluentform'),
375 'image' => '',
376 'video' => 'https://www.youtube.com/embed/VQTWnM6BbRU',
377 ];
378 $disabled['ratings'] = [
379 'disabled' => true,
380 'title' => __('Ratings', 'fluentform'),
381 'description' => __('Ratings is not available with the free version. Please upgrade to pro to get all the advanced features.',
382 'fluentform'),
383 'image' => '',
384 'video' => 'https://www.youtube.com/embed/YGdkNspMaEs',
385 ];
386 $disabled['tabular_grid'] = [
387 'disabled' => true,
388 'title' => __('Checkable Grid', 'fluentform'),
389 'description' => __('Checkable Grid is not available with the free version. Please upgrade to pro to get all the advanced features.',
390 'fluentform'),
391 'image' => '',
392 'video' => 'https://www.youtube.com/embed/ayI3TzXXANA',
393 ];
394 $disabled['chained_select'] = [
395 'disabled' => true,
396 'title' => __('Chained Select Field', 'fluentform'),
397 'description' => __('Chained Select Field is not available with the free version. Please upgrade to pro to get all the advanced features.',
398 'fluentform'),
399 'image' => fluentformMix('img/pro-fields/chained-select-field.png'),
400 'video' => '',
401 ];
402 $disabled['phone'] = [
403 'disabled' => true,
404 'title' => 'Phone Field',
405 'description' => __('Phone Field is not available with the free version. Please upgrade to pro to get all the advanced features.',
406 'fluentform'),
407 'image' => fluentformMix('img/pro-fields/phone-field.png'),
408 'video' => '',
409 ];
410 $disabled['rich_text_input'] = [
411 'disabled' => true,
412 'title' => __('Rich Text Input', 'fluentform'),
413 'description' => __('Rich Text Input is not available with the free version. Please upgrade to pro to get all the advanced features.',
414 'fluentform'),
415 'image' => fluentformMix('img/pro-fields/rich-text-input.png'),
416 'video' => '',
417 ];
418 $disabled['save_progress_button'] = [
419 'disabled' => true,
420 'title' => __('Save & Resume', 'fluentform'),
421 'description' => __('Save & Resume is not available with the free version. Please upgrade to pro to get all the advanced features.',
422 'fluentform'),
423 'image' => fluentformMix('img/pro-fields/save-progress-button.png'),
424 'video' => '',
425 ];
426 $disabled['cpt_selection'] = [
427 'disabled' => true,
428 'title' => __('Post/CPT Selection', 'fluentform'),
429 'description' => __('Post/CPT Selection is not available with the free version. Please upgrade to pro to get all the advanced features.',
430 'fluentform'),
431 'image' => fluentformMix('img/pro-fields/post-cpt-selection.png'),
432 'video' => '',
433 ];
434 $disabled['quiz_score'] = [
435 'disabled' => true,
436 'title' => __('Quiz Score', 'fluentform'),
437 'description' => __('Quiz Score is not available with the free version. Please upgrade to pro to get all the advanced features.',
438 'fluentform'),
439 'image' => '',
440 'video' => 'https://www.youtube.com/embed/bPjDXR0y_Oo',
441 ];
442 $disabled['net_promoter_score'] = [
443 'disabled' => true,
444 'title' => __('Net Promoter Score', 'fluentform'),
445 'description' => __('Net Promoter Score is not available with the free version. Please upgrade to pro to get all the advanced features.',
446 'fluentform'),
447 'image' => fluentformMix('img/pro-fields/net-promoter-score.png'),
448 'video' => '',
449 ];
450 $disabled['dynamic_field'] = [
451 'disabled' => true,
452 'title' => __('Dynamic Field', 'fluentform'),
453 'description' => __('Dynamic Field is not available with the free version. Please upgrade to pro to get all the advanced features.', 'fluentform'),
454 'image' => '',
455 'video' => 'https://www.youtube.com/embed/cx3N5y1ddOQ',
456 ];
457 $disabled['repeater_field'] = [
458 'disabled' => true,
459 'title' => __('Repeat Field', 'fluentform'),
460 'description' => __('Repeat Field is not available with the free version. Please upgrade to pro to get all the advanced features.',
461 'fluentform'),
462 'image' => '',
463 'video' => 'https://www.youtube.com/embed/BXo9Sk-OLnQ',
464 ];
465 $disabled['rangeslider'] = [
466 'disabled' => true,
467 'title' => __('Range Slider', 'fluentform'),
468 'description' => __('Range Slider is not available with the free version. Please upgrade to pro to get all the advanced features.',
469 'fluentform'),
470 'image' => '',
471 'video' => 'https://www.youtube.com/embed/RaY2VcPWk6I',
472 ];
473 $disabled['input_ranking'] = [
474 'disabled' => true,
475 'title' => __('Ranking Field', 'fluentform'),
476 'description' => __('Ranking Field is not available with the free version. Please upgrade to pro to get all the advanced features.', 'fluentform'),
477 'image' => '',
478 'video' => '',
479 ];
480 $disabled['color-picker'] = [
481 'disabled' => true,
482 'title' => __('Color Picker', 'fluentform'),
483 'description' => __('Color Picker is not available with the free version. Please upgrade to pro to get all the advanced features.',
484 'fluentform'),
485 'image' => fluentformMix('img/pro-fields/color-picker.png'),
486 'video' => '',
487 ];
488 $disabled['payment_coupon'] = [
489 'disabled' => true,
490 'title' => __('Coupon', 'fluentform'),
491 'description' => __('Coupon is not available with the free version. Please upgrade to pro to get all the advanced features.',
492 'fluentform'),
493 'image' => fluentformMix('img/pro-fields/coupon.png'),
494 'video' => '',
495 ];
496 $disabled['accordion'] = [
497 'disabled' => true,
498 'title' => __('Accordion/Tab', 'fluentform'),
499 'description' => __('Accordion/Tab is not available with the free version. Please upgrade to pro to get all the advanced features.', 'fluentform'),
500 'image' => fluentformMix('img/pro-fields/accordion-tab.png'),
501 'video' => '',
502 ];
503 }
504
505 $disabled = apply_filters_deprecated(
506 'fluentform_disabled_components',
507 [
508 $disabled
509 ],
510 FLUENTFORM_FRAMEWORK_UPGRADE,
511 'fluentform/disabled_components',
512 'Use fluentform/disabled_components instead of fluentform_disabled_components.'
513 );
514
515 return $this->app->applyFilters('fluentform/disabled_components', $disabled);
516 }
517
518 public function fields($id)
519 {
520 return $this->fields->get($id);
521 }
522
523 public function shortcodes($id)
524 {
525 return fluentFormGetAllEditorShortCodes($id);
526 }
527
528 public function pages()
529 {
530 return fluentformGetPages();
531 }
532
533 public function getInputsAndLabels($formId, $with = ['admin_label', 'raw'])
534 {
535 try {
536 $form = $this->model->findOrFail($formId);
537
538 $inputs = FormFieldsParser::getEntryInputs($form, $with);
539 $labels = FormFieldsParser::getAdminLabels($form, $inputs);
540
541 $labels = apply_filters_deprecated(
542 'fluentfoform_entry_lists_labels',
543 [
544 $labels,
545 $form
546 ],
547 FLUENTFORM_FRAMEWORK_UPGRADE,
548 'fluentform/entry_lists_labels',
549 'Use fluentform/entry_lists_labels instead of fluentfoform_entry_lists_labels.'
550 );
551 $labels = apply_filters('fluentform/entry_lists_labels', $labels, $form);
552
553 $labels = apply_filters_deprecated(
554 'fluentform_all_entry_labels',
555 [
556 $labels,
557 $formId
558 ],
559 FLUENTFORM_FRAMEWORK_UPGRADE,
560 'fluentform/all_entry_labels',
561 'Use fluentform/all_entry_labels instead of fluentform_all_entry_labels.'
562 );
563 $labels = apply_filters('fluentform/all_entry_labels', $labels, $formId);
564
565 if ($form->has_payment) {
566 $labels = apply_filters_deprecated(
567 'fluentform_all_entry_labels_with_payment',
568 [
569 $labels,
570 false,
571 $form
572 ],
573 FLUENTFORM_FRAMEWORK_UPGRADE,
574 'fluentform/all_entry_labels_with_payment',
575 'Use fluentform/all_entry_labels_with_payment instead of fluentform_all_entry_labels_with_payment.'
576 );
577
578 $labels = apply_filters('fluentform/all_entry_labels_with_payment', $labels, false, $form);
579 }
580
581 return [
582 'inputs' => $inputs,
583 'labels' => $labels,
584 ];
585 } catch (Exception $e) {
586 throw new Exception(
587 esc_html__("The form couldn't be found.", 'fluentform')
588 );
589 }
590 }
591
592 public function findShortCodePage($formId)
593 {
594 $excluded = ['attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_navigation', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_font_family', 'wp_font_face'];
595 $excluded = apply_filters('fluentform/find_shortcode_excluded_post_types', $excluded);
596 if (!is_array($excluded)) {
597 $excluded = [];
598 }
599
600 $publicTypes = get_post_types(['public' => true], 'names');
601 $builderTypes = get_post_types(['_builtin' => false], 'names');
602 $postTypes = array_values(array_diff(array_unique(array_merge($publicTypes, $builderTypes)), $excluded));
603
604 if (empty($postTypes)) {
605 return [
606 'locations' => [],
607 'status' => false,
608 ];
609 }
610
611 global $wpdb;
612 $placeholders = implode(', ', array_fill(0, count($postTypes), '%s'));
613 // "fluentfo" prefix matches both the [fluentform] shortcode/rendered HTML and the Gutenberg block "fluentfom/guten-block" (note the missing "r" in the block name).
614 $args = array_merge($postTypes, ['%fluentfo%']);
615 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholders is safe: generated from array_fill with %s format strings
616 $matchingIds = $wpdb->get_col($wpdb->prepare(
617 "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$placeholders}) AND post_status != 'trash' AND post_content LIKE %s",
618 $args
619 ));
620
621 // Page builders that store layout data in postmeta (Elementor popups, Bricks, Beaver Builder, etc.)
622 // are not reachable via post_content. Scan known builder meta keys so forms embedded inside those
623 // builders' templates and popups are also detected.
624 $builderMetaKeys = apply_filters('fluentform/find_shortcode_builder_meta_keys', [
625 '_elementor_data', // Elementor (templates, popups, pages)
626 '_fl_builder_data', // Beaver Builder
627 '_bricks_page_content_2', // Bricks Builder
628 ]);
629 if (!is_array($builderMetaKeys)) {
630 $builderMetaKeys = [];
631 }
632
633 if (!empty($builderMetaKeys)) {
634 $metaPlaceholders = implode(', ', array_fill(0, count($builderMetaKeys), '%s'));
635 $metaArgs = array_merge($postTypes, $builderMetaKeys, ['%fluentfo%']);
636 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholders generated from array_fill
637 $builderIds = $wpdb->get_col($wpdb->prepare(
638 "SELECT DISTINCT p.ID FROM {$wpdb->posts} p
639 INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
640 WHERE p.post_type IN ({$placeholders})
641 AND p.post_status != 'trash'
642 AND pm.meta_key IN ({$metaPlaceholders})
643 AND pm.meta_value LIKE %s",
644 $metaArgs
645 ));
646 $matchingIds = array_values(array_unique(array_merge($matchingIds, $builderIds)));
647 }
648
649 if (empty($matchingIds)) {
650 return [
651 'locations' => [],
652 'status' => false,
653 ];
654 }
655
656 $params = array(
657 'post_type' => $postTypes,
658 'post_status' => ['publish', 'draft', 'private', 'pending', 'future'],
659 'posts_per_page' => -1,
660 'post__in' => $matchingIds,
661 );
662
663 $params = apply_filters_deprecated(
664 'fluentform_find_shortcode_params',
665 [
666 $params
667 ],
668 FLUENTFORM_FRAMEWORK_UPGRADE,
669 'fluentform/find_shortcode_params',
670 'Use fluentform/find_shortcode_params instead of fluentform_find_shortcode_params.'
671 );
672 $params = apply_filters('fluentform/find_shortcode_params', $params);
673
674 $formLocations = [];
675 $posts = get_posts($params);
676 foreach ($posts as $post) {
677 $formIds = self::getShortCodeId($post->post_content);
678
679 foreach ($builderMetaKeys as $metaKey) {
680 $metaValue = get_post_meta($post->ID, $metaKey, true);
681 if (!is_string($metaValue) || '' === $metaValue) {
682 continue;
683 }
684 // Builder payloads (e.g. _elementor_data) are JSON with escaped quotes — normalize so the shortcode regex matches.
685 $metaValue = str_replace('\\"', '"', $metaValue);
686 $metaFormIds = self::getShortCodeId($metaValue);
687 if (!empty($metaFormIds)) {
688 $formIds = array_merge($formIds, $metaFormIds);
689 }
690 }
691
692 $formIds = array_unique($formIds);
693
694 if (!empty($formIds) && in_array($formId, $formIds)) {
695 $postType = get_post_type_object($post->post_type);
696 $editLink = get_edit_post_link($post->ID, 'raw');
697 if (!$editLink) {
698 $editLink = sprintf("%spost.php?post=%s&action=edit", admin_url(), $post->ID);
699 }
700 $formLocations[] = [
701 'id' => $post->ID,
702 'name' => $postType ? $postType->labels->singular_name : $post->post_type,
703 'title' => (empty($post->post_title) ? $post->ID : $post->post_title),
704 'edit_link' => $editLink,
705 ];
706 }
707 }
708 return [
709 'locations' => $formLocations,
710 'status' => !empty($formLocations),
711 ];
712 }
713
714 protected static function flattenBlocks($blocks)
715 {
716 $flat = [];
717 foreach ($blocks as $block) {
718 $flat[] = $block;
719 if (!empty($block['innerBlocks']) && is_array($block['innerBlocks'])) {
720 $flat = array_merge($flat, self::flattenBlocks($block['innerBlocks']));
721 }
722 }
723 return $flat;
724 }
725
726 public static function getShortCodeId($content, $shortcodeTag = 'fluentform')
727 {
728 $ids = [];
729 $selector = 'id';
730 $formId = '';
731 if (!function_exists('parse_blocks')) {
732 return $ids;
733 }
734 $parsedBlocks = self::flattenBlocks(parse_blocks($content));
735
736 foreach ($parsedBlocks as $block) {
737 if (!array_key_exists('blockName', $block) || !array_key_exists('attrs',
738 $block) || !array_key_exists('formId', $block['attrs'])) {
739 continue;
740 }
741 $hasBlock = strpos($block['blockName'], 'fluentfom/guten-block') === 0;
742 if (!$hasBlock) {
743 continue;
744 }
745 $ids[] = (int)$block['attrs']['formId'];
746 }
747 // Define the regex pattern with a placeholder for any number
748 $hasFormWidgets = false;
749 $pattern = '/<form data-form_id="(\d+)" id="fluentform_(\d+)" data-form_instance="ff_form_instance_(\d+)_(\d+)" method="POST" ><fieldset /';
750 // Perform the regex match
751 if (preg_match($pattern, $content, $matches)) {
752 $hasFormWidgets = isset($matches[0]);
753 $ids[] = isset($matches[1]) ? $matches[1] : '';
754 }
755
756 if (!has_shortcode($content, $shortcodeTag) && !$hasFormWidgets) {
757 return $ids;
758 }
759
760 preg_match_all('/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER);
761
762 if (empty($matches)) {
763 return $ids;
764 }
765
766 foreach ($matches as $shortcode) {
767 if (count($shortcode) >= 2 && $shortcodeTag === $shortcode[2]) {
768 $parsedCode = str_replace(['[', ']', '&#91;', '&#93;'], '', $shortcode[0]);
769
770 $result = shortcode_parse_atts($parsedCode);
771
772 if (!empty($result[$selector])) {
773 $ids[] = $result[$selector];
774 }
775 }
776 }
777 return $ids;
778 }
779 }
780