PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.14
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.14
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
← All changes | app/Services/Form/FormService.php +91 -15 6.2.26.2.14 View file →
@@ -96,9 +96,10 @@
96 96 do_action('fluentform/inserted_new_form', $form->id, $data);
97 97
98 98 return $form;
99 99 } catch (Exception $e) {
100 - throw new Exception(esc_html($e->getMessage()));
100 + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- surfaced as JSON by the REST layer, never echoed as HTML
101 + throw new Exception($e->getMessage());
101 102 }
102 103 }
103 104
104 105 /**
@@ -468,8 +469,15 @@
468 469 'fluentform'),
469 470 'image' => '',
470 471 'video' => 'https://www.youtube.com/embed/RaY2VcPWk6I',
471 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 + ];
472 480 $disabled['color-picker'] = [
473 481 'disabled' => true,
474 482 'title' => __('Color Picker', 'fluentform'),
475 483 'description' => __('Color Picker is not available with the free version. Please upgrade to pro to get all the advanced features.',
@@ -582,22 +590,29 @@
582 590 }
583 591
584 592 public function findShortCodePage($formId)
585 593 {
586 - $excluded = ['attachment'];
587 - $post_types = get_post_types(['show_in_menu' => true], 'objects', 'or');
588 - $postTypes = [];
589 - foreach ($post_types as $post_type) {
590 - $postTypeName = $post_type->name;
591 - if (in_array($postTypeName, $excluded)) {
592 - continue;
593 - }
594 - $postTypes[] = $postTypeName;
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 = [];
595 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 + }
596 610
597 611 global $wpdb;
598 612 $placeholders = implode(', ', array_fill(0, count($postTypes), '%s'));
599 - $args = array_merge($postTypes, ['%fluentform%']);
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%']);
600 615 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholders is safe: generated from array_fill with %s format strings
601 616 $matchingIds = $wpdb->get_col($wpdb->prepare(
602 617 "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$placeholders}) AND post_status != 'trash' AND post_content LIKE %s",
603 618 $args
@@ -602,8 +617,36 @@
602 617 "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$placeholders}) AND post_status != 'trash' AND post_content LIKE %s",
603 618 $args
604 619 ));
605 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 +
606 649 if (empty($matchingIds)) {
607 650 return [
608 651 'locations' => [],
609 652 'status' => false,
@@ -611,8 +654,9 @@
611 654 }
612 655
613 656 $params = array(
614 657 'post_type' => $postTypes,
658 + 'post_status' => ['publish', 'draft', 'private', 'pending', 'future'],
615 659 'posts_per_page' => -1,
616 660 'post__in' => $matchingIds,
617 661 );
618 662
@@ -630,15 +674,35 @@
630 674 $formLocations = [];
631 675 $posts = get_posts($params);
632 676 foreach ($posts as $post) {
633 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 +
634 694 if (!empty($formIds) && in_array($formId, $formIds)) {
635 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 + }
636 700 $formLocations[] = [
637 701 'id' => $post->ID,
638 - 'name' => $postType->labels->singular_name,
702 + 'name' => $postType ? $postType->labels->singular_name : $post->post_type,
639 703 'title' => (empty($post->post_title) ? $post->ID : $post->post_title),
640 - 'edit_link' => sprintf("%spost.php?post=%s&action=edit", admin_url(), $post->ID),
704 + 'edit_link' => $editLink,
641 705 ];
642 706 }
643 707 }
644 708 return [
@@ -646,8 +710,20 @@
646 710 'status' => !empty($formLocations),
647 711 ];
648 712 }
649 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 +
650 726 public static function getShortCodeId($content, $shortcodeTag = 'fluentform')
651 727 {
652 728 $ids = [];
653 729 $selector = 'id';
@@ -654,10 +730,10 @@
654 730 $formId = '';
655 731 if (!function_exists('parse_blocks')) {
656 732 return $ids;
657 733 }
658 - $parsedBlocks = parse_blocks($content);
659 -
734 + $parsedBlocks = self::flattenBlocks(parse_blocks($content));
735 +
660 736 foreach ($parsedBlocks as $block) {
661 737 if (!array_key_exists('blockName', $block) || !array_key_exists('attrs',
662 738 $block) || !array_key_exists('formId', $block['attrs'])) {
663 739 continue;