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 +365 -47 6.31 → trunk View file →
@@ -34,14 +34,15 @@
34 34
35 35 $page = $this->get_pagenum();
36 36 $per_page = $this->get_items_per_page( 'formidable_page_formidable_per_page' );
37 37
38 - $mode = self::get_param(
38 + $mode = self::get_param(
39 39 array(
40 40 'param' => 'mode',
41 41 'default' => 'list',
42 42 )
43 43 );
44 +
44 45 $orderby = self::get_param(
45 46 array(
46 47 'param' => 'orderby',
47 48 'default' => 'name',
@@ -123,8 +124,32 @@
123 124 /**
124 125 * @return void
125 126 */
126 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 +
127 152 if ( $this->status === 'trash' ) {
128 153 echo '<p>';
129 154 esc_html_e( 'No forms found in the trash.', 'formidable' );
130 155 // phpcs:disable Generic.WhiteSpace.ScopeIndent
@@ -223,23 +248,8 @@
223 248 return $links;
224 249 }
225 250
226 251 /**
227 - * @param string $which
228 - *
229 - * @return void
230 - */
231 - public function pagination( $which ) {
232 - global $mode;
233 -
234 - parent::pagination( $which );
235 -
236 - if ( 'top' === $which ) {
237 - $this->view_switcher( $mode );
238 - }
239 - }
240 -
241 - /**
242 252 * @param stdClass $item
243 253 * @param string $style
244 254 *
245 255 * @return string
@@ -253,20 +263,11 @@
253 263
254 264 $this->get_actions( $actions, $item, $edit_link );
255 265
256 266 $action_links = $this->row_actions( $actions );
267 + $checkbox = $this->get_row_checkbox( $item );
268 + $r = '<tr id="item-action-' . absint( $item->id ) . '"' . $style . '>';
257 269
258 - // Set up the checkbox ( because the user is editable, otherwise its empty )
259 - $checkbox = '<input type="checkbox" name="item-action[]" id="cb-item-action-' . absint( $item->id ) . '" value="' . esc_attr( $item->id ) . '" />';
260 - $checkbox_label_text = sprintf(
261 - // translators: Form title
262 - __( 'Select %s', 'formidable' ),
263 - ! empty( $item->name ) ? $item->name : FrmFormsHelper::get_no_title_text()
264 - );
265 -
266 - $checkbox .= '<label for="cb-item-action-' . absint( $item->id ) . '"><span class="screen-reader-text">' . esc_html( $checkbox_label_text ) . '</span></label>';
267 - $r = '<tr id="item-action-' . absint( $item->id ) . '"' . $style . '>';
268 -
269 270 list( $columns, $hidden ) = $this->get_column_info();
270 271
271 272 $format = 'Y/m/d';
272 273
@@ -278,9 +279,9 @@
278 279 $class = $column_name . ' column-' . $column_name . ( 'name' === $column_name ? ' post-title page-title column-title' : '' );
279 280 $style = '';
280 281
281 282 if ( in_array( $column_name, $hidden, true ) ) {
282 - $class .= ' frm_hidden';
283 + $class .= ' hidden';
283 284 }
284 285
285 286 if ( $column_name === 'name' ) {
286 287 $class .= ' column-primary';
@@ -285,11 +286,10 @@
285 286 if ( $column_name === 'name' ) {
286 287 $class .= ' column-primary';
287 288 }
288 289
289 - $class = 'class="' . esc_attr( $class ) . '"';
290 - $data_colname = ' data-colname="' . esc_attr( $column_display_name ) . '"';
291 - $attributes = $class . $style . $data_colname;
290 + $class = 'class="' . esc_attr( $class ) . '"';
291 + $attributes = $class . $style . $this->get_column_data_attr( $column_name, $column_display_name );
292 292
293 293 switch ( $column_name ) {
294 294 case 'cb':
295 295 $r .= '<th scope="row" class="check-column">' . $checkbox . '</th>';
@@ -306,21 +306,9 @@
306 306 $date = gmdate( $format, strtotime( $item->created_at ) );
307 307 $val = '<abbr title="' . esc_attr( gmdate( 'Y/m/d g:i:s A', strtotime( $item->created_at ) ) ) . '">' . $date . '</abbr>';
308 308 break;
309 309 case 'entries':
310 - if ( ! empty( $item->options['no_save'] ) ) {
311 - $val = FrmAppHelper::icon_by_class(
312 - 'frmfont frm_forbid_icon frm_bstooltip',
313 - array(
314 - 'title' => __( 'Saving entries is disabled for this form', 'formidable' ),
315 - 'echo' => false,
316 - )
317 - );
318 - } else {
319 - $text = FrmEntry::getRecordCount( $item->id );
320 - $val = current_user_can( 'frm_view_entries' ) ? '<a href="' . esc_url( admin_url( 'admin.php?page=formidable-entries&form=' . $item->id ) ) . '">' . $text . '</a>' : $text; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
321 - unset( $text );
322 - }
310 + $val = $this->get_entries_column_value( $item );
323 311 break;
324 312 default:
325 313 if ( method_exists( $this, 'column_' . $column_name ) ) {
326 314 $val = call_user_func( array( $this, 'column_' . $column_name ), $item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -338,12 +326,63 @@
338 326 return $r . '</tr>';
339 327 }
340 328
341 329 /**
330 + * @param string $column_name
331 + * @param string $column_display_name
332 + *
333 + * @return string
334 + */
335 + private function get_column_data_attr( $column_name, $column_display_name ) {
336 + if ( 'settings' === $column_name ) {
337 + return ' data-colname="' . esc_attr( trim( strip_tags( $column_display_name ) ) ) . '"';
338 + }
339 + return ' data-colname="' . esc_attr( $column_display_name ) . '"';
340 + }
341 +
342 + /**
343 + * @param object $item
344 + *
345 + * @return string
346 + */
347 + private function get_entries_column_value( $item ) {
348 + if ( ! empty( $item->options['no_save'] ) ) {
349 + return (string) FrmAppHelper::icon_by_class(
350 + 'frmfont frm_forbid_icon frm_bstooltip',
351 + array(
352 + 'title' => __( 'Saving entries is disabled for this form', 'formidable' ),
353 + 'echo' => false,
354 + )
355 + );
356 + }
357 +
358 + $text = intval( FrmEntry::getRecordCount( $item->id ) );
359 + return current_user_can( 'frm_view_entries' ) ? '<a href="' . esc_url( admin_url( 'admin.php?page=formidable-entries&form=' . $item->id ) ) . '">' . $text . '</a>' : (string) $text; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
360 + }
361 +
362 + /**
363 + * @param object $item
364 + *
365 + * @return string
366 + */
367 + private function get_row_checkbox( $item ) {
368 + // Set up the checkbox (because the user is editable, otherwise it's empty).
369 + $checkbox = '<input type="checkbox" name="item-action[]" id="cb-item-action-' . absint( $item->id ) . '" value="' . esc_attr( $item->id ) . '" />';
370 + $checkbox_label_text = sprintf(
371 + // translators: Form title
372 + __( 'Select %s', 'formidable' ),
373 + ! empty( $item->name ) ? $item->name : FrmFormsHelper::get_no_title_text()
374 + );
375 +
376 + return $checkbox . '<label for="cb-item-action-' . absint( $item->id ) . '"><span class="screen-reader-text">' . esc_html( $checkbox_label_text ) . '</span></label>';
377 + }
378 +
379 + /**
342 380 * Get the HTML for the Actions column in the form list.
343 381 * This includes multiple icons for triggering the embed modal, the visual styler, and an active landing page.
344 382 *
345 383 * @since 6.0
384 + * @deprecated 6.32 We moved these actions to other places. This column will show if there is a filter added to the hook.
346 385 *
347 386 * @param stdClass $form
348 387 *
349 388 * @return string
@@ -403,16 +442,34 @@
403 442 'title' => __( 'Link to list of all views for this form.', 'formidable' ),
404 443 'target' => '_blank',
405 444 );
406 445
446 + if ( class_exists( 'FrmViewsDisplay' ) ) {
447 + $view_ids = FrmViewsDisplay::get_display_ids_by_form( $form->id );
448 + $view_count = $view_ids ? count( $view_ids ) : 0;
449 + } else {
450 + $view_count = 0;
451 + }
452 +
407 453 // phpcs:disable Generic.WhiteSpace.ScopeIndent
408 454 return '<a ' . FrmAppHelper::array_to_html_params( $attributes ) . '>
409 - ' . FrmAppHelper::icon_by_class( 'frmfont frm_eye_icon', array( 'echo' => false ) ) .
455 + ' . intval( $view_count ) .
410 456 '</a>';
411 457 // phpcs:enable Generic.WhiteSpace.ScopeIndent
412 458 }
413 459
414 460 /**
461 + * Get the HTML for the Settings column in the form list.
462 + *
463 + * @since 6.32
464 + *
465 + * @return string
466 + */
467 + protected function column_settings() {
468 + return '&nbsp;';
469 + }
470 +
471 + /**
415 472 * @param array $actions
416 473 * @param object $item
417 474 * @param string $edit_link
418 475 *
@@ -434,10 +491,11 @@
434 491 $actions['frm_edit'] = '<a href="' . esc_url( $edit_link ) . '">' . esc_html__( 'Edit', 'formidable' ) . '</a>';
435 492 $actions['frm_settings'] = '<a href="' . esc_url( '?page=formidable&frm_action=settings&id=' . $item->id ) . '">' . esc_html__( 'Settings', 'formidable' ) . '</a>';
436 493 }
437 494
438 - $actions = array_merge( $actions, $new_actions );
439 - $actions['view'] = '<a href="' . esc_url( FrmFormsHelper::get_direct_link( $item->form_key, $item ) ) . '" target="_blank">' . esc_html__( 'Preview', 'formidable' ) . '</a>'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
495 + $actions = array_merge( $actions, $new_actions );
496 + $actions['embed'] = '<a href="#" class="frm-embed-form" role="button" aria-label="' . esc_attr__( 'Embed Form', 'formidable' ) . '">' . esc_html__( 'Embed', 'formidable' ) . '</a>'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
497 + $actions['view'] = '<a href="' . esc_url( FrmFormsHelper::get_direct_link( $item->form_key, $item ) ) . '" target="_blank">' . esc_html__( 'Preview', 'formidable' ) . '</a>'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
440 498 }
441 499
442 500 /**
443 501 * @param object $item
@@ -506,6 +564,266 @@
506 564 * @return string
507 565 */
508 566 protected function confirm_bulk_delete() {
509 567 return __( 'ALL selected forms and their entries will be permanently deleted. Want to proceed?', 'formidable' );
568 + }
569 +
570 + /**
571 + * @param stdClass $form
572 + *
573 + * @return string
574 + */
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 +
581 + $posts = $this->get_posts_contain_form( $form );
582 +
583 + if ( ! $posts ) {
584 + return '<span class="frm-forms-list-embeds-zero">0</span>';
585 + }
586 +
587 + return FrmAppHelper::clip(
588 + function () use ( $posts ) {
589 + ?>
590 + <a href="#" class="frm-forms-list-embeds-btn" data-posts="<?php echo esc_attr( wp_json_encode( $posts ) ); ?>">
591 + <?php
592 + FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown6_icon' );
593 + echo intval( count( $posts ) );
594 + ?>
595 + </a>
596 + <?php
597 + }
598 + );
599 + }
600 +
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 + /**
621 + * Gets posts or pages that contain the form shortcode.
622 + *
623 + * @since 6.32
624 + *
625 + * @param stdClass $form Form object.
626 + *
627 + * @return array
628 + */
629 + private function get_posts_contain_form( $form ) {
630 + $cached_posts = FrmFormEmbedsHelper::get_cached_posts();
631 +
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 );
635 + }
636 +
637 + if ( ! isset( $cached_posts[ $form->id ] ) || ! is_array( $cached_posts[ $form->id ] ) ) {
638 + return array();
639 + }
640 +
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;
664 + }
665 +
666 + $search_map[ $form_id ] = $this->get_search_strings_for_form( $form_id );
667 + $forms[ $form_id ] = $form_to_scan;
668 + }
669 +
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;
705 + }
706 + }
707 +
708 + return $forms;
709 + }
710 +
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' ) );
729 +
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;
733 + }
734 +
735 + return $filtered_posts;
736 + }
737 +
738 + /**
739 + * Gets search strings for a form inside a post.
740 + *
741 + * @since 6.32
742 + *
743 + * @param int $form_id Form ID.
744 + *
745 + * @return string[]
746 + */
747 + protected function get_search_strings_for_form( $form_id ) {
748 + return $this->get_base_search_strings_for_form( $form_id );
749 + }
750 +
751 + /**
752 + * Gets the base search strings for a form inside a post.
753 + *
754 + * @since 6.32
755 + *
756 + * @param int $form_id Form ID.
757 + *
758 + * @return string[]
759 + */
760 + protected function get_base_search_strings_for_form( $form_id ) {
761 + $strings = array(
762 + '<!-- wp:formidable/simple-form {"formId":"' . $form_id . '"',
763 + );
764 +
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 );
769 +
770 + if ( $form_key && $form_key !== (string) $form_id ) {
771 + $identifiers[] = $form_key;
772 + }
773 +
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 + }
781 + }
782 +
783 + return $strings;
784 + }
785 +
786 + /**
787 + * Maybe clear the embed posts transient when a post is inserted.
788 + *
789 + * @since 6.32
790 + *
791 + * @param int $post_id Post ID.
792 + * @param WP_Post $post Post object.
793 + * @param bool $update True when an existing post was updated rather than created.
794 + *
795 + * @return void
796 + */
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 + }
800 +
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 + }
815 +
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 );
510 828 }
511 829 }