PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.7
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.7
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.7, at app/Services/Form/FormService.php

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