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 +161 -102 6.34 → 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
@@ -581,8 +572,13 @@
581 572 *
582 573 * @return string
583 574 */
584 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 +
585 581 $posts = $this->get_posts_contain_form( $form );
586 582
587 583 if ( ! $posts ) {
588 584 return '<span class="frm-forms-list-embeds-zero">0</span>';
@@ -602,8 +598,27 @@
602 598 );
603 599 }
604 600
605 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 + /**
606 621 * Gets posts or pages that contain the form shortcode.
607 622 *
608 623 * @since 6.32
609 624 *
@@ -611,52 +626,114 @@
611 626 *
612 627 * @return array
613 628 */
614 629 private function get_posts_contain_form( $form ) {
615 - $cached_posts = get_transient( self::$embed_posts_transient_name );
630 + $cached_posts = FrmFormEmbedsHelper::get_cached_posts();
616 631
617 - if ( isset( $cached_posts[ $form->id ] ) && is_array( $cached_posts[ $form->id ] ) ) {
618 - 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 );
619 635 }
620 636
621 - $posts = $this->query_posts_contain_form( $form );
622 -
623 - if ( ! is_array( $posts ) ) {
637 + if ( ! isset( $cached_posts[ $form->id ] ) || ! is_array( $cached_posts[ $form->id ] ) ) {
624 638 return array();
625 639 }
626 640
627 - foreach ( $posts as $post ) {
628 - if ( ! property_exists( $post, 'permalink' ) ) {
629 - $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;
630 664 }
631 665
632 - if ( ! property_exists( $post, 'edit_link' ) ) {
633 - $post->edit_link = get_edit_post_link( $post->ID );
634 - }
666 + $search_map[ $form_id ] = $this->get_search_strings_for_form( $form_id );
667 + $forms[ $form_id ] = $form_to_scan;
668 + }
635 669
636 - // Ensure post_name is not null or the string "null"
637 - if ( ! isset( $post->post_name ) ) {
638 - $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;
639 705 }
706 + }
640 707
641 - // Ensure post_title is not null or the string "null"
642 - if ( ! isset( $post->post_title ) ) {
643 - $post->post_title = '';
644 - }
708 + return $forms;
709 + }
645 710
646 - if ( '' === $post->post_title ) {
647 - $post->post_title = __( '(no title)', 'formidable' );
648 - }
649 - }//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' ) );
650 729
651 - if ( ! is_array( $cached_posts ) ) {
652 - $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;
653 733 }
654 734
655 - $cached_posts[ $form->id ] = $posts;
656 - set_transient( self::$embed_posts_transient_name, $cached_posts, DAY_IN_SECONDS );
657 -
658 - return $posts;
735 + return $filtered_posts;
659 736 }
660 737
661 738 /**
662 739 * Gets search strings for a form inside a post.
@@ -680,91 +757,73 @@
680 757 *
681 758 * @return string[]
682 759 */
683 760 protected function get_base_search_strings_for_form( $form_id ) {
684 - return array(
685 - '[formidable id=' . $form_id . ']',
686 - '[formidable id=' . $form_id . ' ',
687 - '[formidable id="' . $form_id . '"',
688 - "[formidable id='" . $form_id . "'",
761 + $strings = array(
689 762 '<!-- wp:formidable/simple-form {"formId":"' . $form_id . '"',
690 763 );
691 - }
692 764
693 - /**
694 - * Queries for posts that contain the form shortcode.
695 - *
696 - * @param stdClass $form Form object.
697 - *
698 - * @return array
699 - */
700 - private function query_posts_contain_form( $form ) {
701 - $form_id = $form->id;
702 - global $wpdb;
703 - $query_strings = $this->get_search_strings_for_form( $form_id );
704 - $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 );
705 769
706 - foreach ( $query_strings as $query_string ) {
707 - $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;
708 772 }
709 773
710 - $like_where = implode( ' OR ', $like_where );
711 - $where = "post_type IN ('post', 'page') AND ($like_where)";
712 -
713 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
714 - $posts = $wpdb->get_results( "SELECT ID,post_title,post_name FROM $wpdb->posts WHERE $where" );
715 -
716 - if ( ! is_array( $posts ) ) {
717 - 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 + }
718 781 }
719 782
720 - /**
721 - * @since 6.32
722 - *
723 - * @param stdClass[] $posts
724 - * @param array $args
725 - */
726 - $filtered_posts = apply_filters( 'frm_get_posts_contain_form', $posts, compact( 'form' ) );
727 -
728 - if ( ! is_array( $filtered_posts ) ) {
729 - _doing_it_wrong( 'frm_get_posts_contain_form', 'Filter should return an array.', '6.32' );
730 - return $posts;
731 - }
732 -
733 - return $filtered_posts;
783 + return $strings;
734 784 }
735 785
736 786 /**
737 - * Maybe clear the embed posts transient.
787 + * Maybe clear the embed posts transient when a post is inserted.
738 788 *
739 789 * @since 6.32
740 790 *
741 791 * @param int $post_id Post ID.
742 792 * @param WP_Post $post Post object.
793 + * @param bool $update True when an existing post was updated rather than created.
743 794 *
744 795 * @return void
745 796 */
746 - public static function maybe_clear_embed_posts_transient( $post_id, $post ) {
747 - if ( str_contains( $post->post_content, '[formidable ' ) || str_contains( $post->post_content, '<!-- wp:formidable/simple-form ' ) ) {
748 - // New post contains the form shortcode, so clear the embed posts transient.
749 - delete_transient( self::$embed_posts_transient_name );
750 - return;
751 - }
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 + }
752 800
753 - $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 + }
754 815
755 - if ( ! is_array( $cached_posts ) ) {
756 - return;
757 - }
758 -
759 - // If the new post data of a cached post doesn't contain the Formidable forms, clear the transient.
760 - foreach ( $cached_posts as $posts ) {
761 - foreach ( $posts as $post_data ) {
762 - if ( intval( $post_data->ID ) === intval( $post_id ) ) {
763 - // This post contains the form shortcode before updating, so clear the embed posts transient.
764 - delete_transient( self::$embed_posts_transient_name );
765 - return;
766 - }
767 - }
768 - }
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 );
769 828 }
770 829 }