PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | classes/helpers/FrmFormsListHelper.php +185 -102 6.32.1 → trunk View file →
@@ -5,19 +5,10 @@
5 5
6 6 class FrmFormsListHelper extends FrmListHelper {
7 7
8 8 /**
9 - * The transient name that stores data for which posts a form is embedded in.
10 - *
11 - * @since 6.32
12 - *
13 9 * @var string
14 10 */
15 - private static $embed_posts_transient_name = 'frm_posts_contain_form';
16 -
17 - /**
18 - * @var string
19 - */
20 11 public $status = '';
21 12
22 13 public $total_items = 0;
23 14
@@ -133,8 +124,32 @@
133 124 /**
134 125 * @return void
135 126 */
136 127 public function no_items() {
128 + $s = self::get_param(
129 + array(
130 + 'param' => 's',
131 + 'sanitize' => 'sanitize_text_field',
132 + )
133 + );
134 +
135 + if ( $s !== '' ) {
136 + $current_url = set_url_scheme(
137 + 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' )
138 + );
139 + $clear_url = remove_query_arg( 's', $current_url );
140 +
141 + echo '<p>';
142 + printf(
143 + /* translators: %1$s: Start link HTML, %2$s: End link HTML */
144 + esc_html__( 'No forms match your search. %1$sClear search%2$s', 'formidable' ),
145 + '<a href="' . esc_url( $clear_url ) . '">',
146 + '</a>'
147 + );
148 + echo '</p>';
149 + return;
150 + }
151 +
137 152 if ( $this->status === 'trash' ) {
138 153 echo '<p>';
139 154 esc_html_e( 'No forms found in the trash.', 'formidable' );
140 155 // phpcs:disable Generic.WhiteSpace.ScopeIndent
@@ -557,8 +572,13 @@
557 572 *
558 573 * @return string
559 574 */
560 575 public function column_embeds( $form ) {
576 + if ( $this->column_is_hidden( 'embeds' ) ) {
577 + // Locating embeds means scanning post_content, so skip it when the column is hidden.
578 + return '';
579 + }
580 +
561 581 $posts = $this->get_posts_contain_form( $form );
562 582
563 583 if ( ! $posts ) {
564 584 return '<span class="frm-forms-list-embeds-zero">0</span>';
@@ -578,8 +598,27 @@
578 598 );
579 599 }
580 600
581 601 /**
602 + * Checks if a column is hidden with Screen Options.
603 + *
604 + * @since 6.35
605 + *
606 + * @param string $column_name Column name.
607 + *
608 + * @return bool
609 + */
610 + private function column_is_hidden( $column_name ) {
611 + $column_info = $this->get_column_info();
612 +
613 + if ( ! isset( $column_info[1] ) || ! is_array( $column_info[1] ) ) {
614 + return false;
615 + }
616 +
617 + return in_array( $column_name, $column_info[1], true );
618 + }
619 +
620 + /**
582 621 * Gets posts or pages that contain the form shortcode.
583 622 *
584 623 * @since 6.32
585 624 *
@@ -587,52 +626,114 @@
587 626 *
588 627 * @return array
589 628 */
590 629 private function get_posts_contain_form( $form ) {
591 - $cached_posts = get_transient( self::$embed_posts_transient_name );
630 + $cached_posts = FrmFormEmbedsHelper::get_cached_posts();
592 631
593 - if ( isset( $cached_posts[ $form->id ] ) && is_array( $cached_posts[ $form->id ] ) ) {
594 - return $cached_posts[ $form->id ];
632 + if ( ! isset( $cached_posts[ $form->id ] ) || ! is_array( $cached_posts[ $form->id ] ) ) {
633 + // A single scan covers every form listed on this page, not just this one.
634 + $cached_posts = $this->fill_embed_posts_cache( $form );
595 635 }
596 636
597 - $posts = $this->query_posts_contain_form( $form );
598 -
599 - if ( ! is_array( $posts ) ) {
637 + if ( ! isset( $cached_posts[ $form->id ] ) || ! is_array( $cached_posts[ $form->id ] ) ) {
600 638 return array();
601 639 }
602 640
603 - foreach ( $posts as $post ) {
604 - if ( ! property_exists( $post, 'permalink' ) ) {
605 - $post->permalink = get_permalink( $post->ID );
641 + // Links and title fallbacks are derived at render time, not stored. get_edit_post_link()
642 + // depends on the current user, so caching it in a shared transient would hand one user's
643 + // edit link to another, and a permalink cached now goes stale on any slug change.
644 + return FrmFormEmbedsHelper::prepare_posts( $cached_posts[ $form->id ] );
645 + }
646 +
647 + /**
648 + * Scans for embeds once and caches the result for every form listed on the current page.
649 + *
650 + * @since 6.35
651 + *
652 + * @param stdClass $form The form whose column is currently rendering.
653 + *
654 + * @return array Embed posts keyed by form ID.
655 + */
656 + private function fill_embed_posts_cache( $form ) {
657 + $cached_posts = FrmFormEmbedsHelper::get_cached_posts();
658 + $search_map = array();
659 + $forms = array();
660 +
661 + foreach ( $this->get_forms_to_scan( $form ) as $form_id => $form_to_scan ) {
662 + if ( isset( $cached_posts[ $form_id ] ) && is_array( $cached_posts[ $form_id ] ) ) {
663 + continue;
606 664 }
607 665
608 - if ( ! property_exists( $post, 'edit_link' ) ) {
609 - $post->edit_link = get_edit_post_link( $post->ID );
610 - }
666 + $search_map[ $form_id ] = $this->get_search_strings_for_form( $form_id );
667 + $forms[ $form_id ] = $form_to_scan;
668 + }
611 669
612 - // Ensure post_name is not null or the string "null"
613 - if ( ! isset( $post->post_name ) ) {
614 - $post->post_name = '';
670 + if ( ! $forms ) {
671 + return $cached_posts;
672 + }
673 +
674 + $matched = FrmFormEmbedsHelper::match_candidate_posts( $search_map );
675 +
676 + foreach ( $forms as $form_id => $form_to_scan ) {
677 + $posts = $this->filter_embed_posts( $matched[ $form_id ], $form_to_scan );
678 + $cached_posts[ $form_id ] = FrmFormEmbedsHelper::slim_posts( $posts );
679 + }
680 +
681 + FrmFormEmbedsHelper::save_cached_posts( $cached_posts );
682 +
683 + return $cached_posts;
684 + }
685 +
686 + /**
687 + * Gets every form rendered on the current list page, keyed by form ID.
688 + *
689 + * @since 6.35
690 + *
691 + * @param stdClass $form The form whose column is currently rendering.
692 + *
693 + * @return array
694 + */
695 + private function get_forms_to_scan( $form ) {
696 + $forms = array( $form->id => $form );
697 +
698 + if ( ! is_array( $this->items ) ) {
699 + return $forms;
700 + }
701 +
702 + foreach ( $this->items as $item ) {
703 + if ( is_object( $item ) && isset( $item->id ) ) {
704 + $forms[ $item->id ] = $item;
615 705 }
706 + }
616 707
617 - // Ensure post_title is not null or the string "null"
618 - if ( ! isset( $post->post_title ) ) {
619 - $post->post_title = '';
620 - }
708 + return $forms;
709 + }
621 710
622 - if ( '' === $post->post_title ) {
623 - $post->post_title = __( '(no title)', 'formidable' );
624 - }
625 - }//end foreach
711 + /**
712 + * Applies the frm_get_posts_contain_form filter to a form's embed posts.
713 + *
714 + * @since 6.35
715 + *
716 + * @param array $posts Posts that embed the form.
717 + * @param stdClass $form Form object.
718 + *
719 + * @return array
720 + */
721 + private function filter_embed_posts( $posts, $form ) {
722 + /**
723 + * @since 6.32
724 + *
725 + * @param stdClass[] $posts
726 + * @param array $args
727 + */
728 + $filtered_posts = apply_filters( 'frm_get_posts_contain_form', $posts, compact( 'form' ) );
626 729
627 - if ( ! is_array( $cached_posts ) ) {
628 - $cached_posts = array();
730 + if ( ! is_array( $filtered_posts ) ) {
731 + _doing_it_wrong( 'frm_get_posts_contain_form', 'Filter should return an array.', '6.32' );
732 + return $posts;
629 733 }
630 734
631 - $cached_posts[ $form->id ] = $posts;
632 - set_transient( self::$embed_posts_transient_name, $cached_posts, DAY_IN_SECONDS );
633 -
634 - return $posts;
735 + return $filtered_posts;
635 736 }
636 737
637 738 /**
638 739 * Gets search strings for a form inside a post.
@@ -656,91 +757,73 @@
656 757 *
657 758 * @return string[]
658 759 */
659 760 protected function get_base_search_strings_for_form( $form_id ) {
660 - return array(
661 - '[formidable id=' . $form_id . ']',
662 - '[formidable id=' . $form_id . ' ',
663 - '[formidable id="' . $form_id . '"',
664 - "[formidable id='" . $form_id . "'",
761 + $strings = array(
665 762 '<!-- wp:formidable/simple-form {"formId":"' . $form_id . '"',
666 763 );
667 - }
668 764
669 - /**
670 - * Queries for posts that contain the form shortcode.
671 - *
672 - * @param stdClass $form Form object.
673 - *
674 - * @return array
675 - */
676 - private function query_posts_contain_form( $form ) {
677 - $form_id = $form->id;
678 - global $wpdb;
679 - $query_strings = $this->get_search_strings_for_form( $form_id );
680 - $like_where = array();
765 + // The shortcode renders a form from its key just as happily as from its ID, in either
766 + // attribute, so all four spellings have to be searched or those pages never count.
767 + $identifiers = array( $form_id );
768 + $form_key = FrmForm::get_key_by_id( $form_id );
681 769
682 - foreach ( $query_strings as $query_string ) {
683 - $like_where[] = $wpdb->remove_placeholder_escape( $wpdb->prepare( 'post_content LIKE %s', '%' . $query_string . '%' ) );
770 + if ( $form_key && $form_key !== (string) $form_id ) {
771 + $identifiers[] = $form_key;
684 772 }
685 773
686 - $like_where = implode( ' OR ', $like_where );
687 - $where = "post_type IN ('post', 'page') AND ($like_where)";
688 -
689 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
690 - $posts = $wpdb->get_results( "SELECT ID,post_title,post_name FROM $wpdb->posts WHERE $where" );
691 -
692 - if ( ! is_array( $posts ) ) {
693 - return array();
774 + foreach ( $identifiers as $identifier ) {
775 + foreach ( array( 'id', 'key' ) as $att ) {
776 + $strings[] = '[formidable ' . $att . '=' . $identifier . ']';
777 + $strings[] = '[formidable ' . $att . '=' . $identifier . ' ';
778 + $strings[] = '[formidable ' . $att . '="' . $identifier . '"';
779 + $strings[] = '[formidable ' . $att . "='" . $identifier . "'";
780 + }
694 781 }
695 782
696 - /**
697 - * @since 6.32
698 - *
699 - * @param stdClass[] $posts
700 - * @param array $args
701 - */
702 - $filtered_posts = apply_filters( 'frm_get_posts_contain_form', $posts, compact( 'form' ) );
703 -
704 - if ( ! is_array( $filtered_posts ) ) {
705 - _doing_it_wrong( 'frm_get_posts_contain_form', 'Filter should return an array.', '6.32' );
706 - return $posts;
707 - }
708 -
709 - return $filtered_posts;
783 + return $strings;
710 784 }
711 785
712 786 /**
713 - * Maybe clear the embed posts transient.
787 + * Maybe clear the embed posts transient when a post is inserted.
714 788 *
715 789 * @since 6.32
716 790 *
717 791 * @param int $post_id Post ID.
718 792 * @param WP_Post $post Post object.
793 + * @param bool $update True when an existing post was updated rather than created.
719 794 *
720 795 * @return void
721 796 */
722 - public static function maybe_clear_embed_posts_transient( $post_id, $post ) {
723 - if ( str_contains( $post->post_content, '[formidable ' ) || str_contains( $post->post_content, '<!-- wp:formidable/simple-form ' ) ) {
724 - // New post contains the form shortcode, so clear the embed posts transient.
725 - delete_transient( self::$embed_posts_transient_name );
726 - return;
727 - }
797 + public static function maybe_clear_embed_posts_transient( $post_id, $post, $update = false ) {
798 + FrmFormEmbedsHelper::maybe_clear_on_insert( $post_id, $post, $update );
799 + }
728 800
729 - $cached_posts = get_transient( self::$embed_posts_transient_name );
801 + /**
802 + * Maybe clear the embed posts transient when a post is updated.
803 + *
804 + * @since 6.35
805 + *
806 + * @param int $post_id Post ID.
807 + * @param WP_Post $post_after Post object after the update.
808 + * @param WP_Post $post_before Post object before the update.
809 + *
810 + * @return void
811 + */
812 + public static function maybe_clear_embed_posts_transient_on_update( $post_id, $post_after, $post_before ) {
813 + FrmFormEmbedsHelper::maybe_clear_on_update( $post_id, $post_after, $post_before );
814 + }
730 815
731 - if ( ! is_array( $cached_posts ) ) {
732 - return;
733 - }
734 -
735 - // If the new post data of a cached post doesn't contain the Formidable forms, clear the transient.
736 - foreach ( $cached_posts as $posts ) {
737 - foreach ( $posts as $post_data ) {
738 - if ( intval( $post_data->ID ) === intval( $post_id ) ) {
739 - // This post contains the form shortcode before updating, so clear the embed posts transient.
740 - delete_transient( self::$embed_posts_transient_name );
741 - return;
742 - }
743 - }
744 - }
816 + /**
817 + * Maybe clear the embed posts transient when a post is trashed, untrashed or deleted.
818 + *
819 + * @since 6.35
820 + *
821 + * @param int $post_id Post ID.
822 + * @param WP_Post|null $post Post object, when the hook provides one.
823 + *
824 + * @return void
825 + */
826 + public static function clear_embed_posts_transient_for_post( $post_id, $post = null ) {
827 + FrmFormEmbedsHelper::maybe_clear_for_post( $post_id, $post );
745 828 }
746 829 }