| @@ -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; |