PluginProbe
Post Grid Gutenberg Blocks – PostX / 5.0.41
Post Grid Gutenberg Blocks – PostX v5.0.41
5.0.41 5.0.39 5.0.38 5.0.37 5.0.36 5.0.35 5.0.34 5.0.33 5.0.32 5.0.31 5.0.30 5.0.29 5.0.28 5.0.27 5.0.26 5.0.25 5.0.23 5.0.24 5.0.22 5.0.21 5.0.20 5.0.19 5.0.18 5.0.17 2.1.1 All 238 releases
← All changes | classes/REST_API.php +109 -31 5.0.275.0.41 View file →
@@ -20,8 +20,34 @@
20 20 */
21 21 class REST_API {
22 22
23 23 /**
24 + * Upper bound for the search endpoint's postPerPage. Matches the max of the
25 + * Search block's "Show Initial Post" slider, so no UI value is ever capped.
26 + * This is the same max range of the slider in gutenburg editor.
27 + * 100 is enough for search bar result.
28 + *
29 + * @since v.5.0.35
30 + */
31 + const SEARCH_POSTS_PER_PAGE_MAX = 100;
32 +
33 + /**
34 + * Longest accepted search term. Longer strings only widen the LIKE pattern
35 + * scanned against every row; no real query needs more.
36 + *
37 + * @since v.5.0.35
38 + */
39 + const SEARCH_TEXT_MAX_LENGTH = 200;
40 +
41 + /**
42 + * Upper bound for excerptLimit. Also keeps $num_words + 1 inside integer range
43 + * in wp_trim_words(), which otherwise overflows to float on a huge value.
44 + *
45 + * @since v.5.0.35
46 + */
47 + const SEARCH_EXCERPT_WORDS_MAX = 400;
48 +
49 + /**
24 50 * Setup class.
25 51 *
26 52 * @since v.1.0.0
27 53 */
@@ -633,28 +659,61 @@
633 659 $post = $server->get_params();
634 660 $searchText = isset( $post['searchText'] ) ? ultimate_post()->ultp_rest_sanitize_params( $post['searchText'] ) : '';
635 661 $paged = isset( $post['paged'] ) ? ultimate_post()->ultp_rest_sanitize_params( $post['paged'] ) : '';
636 662 $postPerPage = isset( $post['postPerPage'] ) ? ultimate_post()->ultp_rest_sanitize_params( $post['postPerPage'] ) : '';
637 - $query_args = array(
663 +
664 + // Public route: cap posts_per_page so -1 can never mean "unlimited".
665 + // 0 survives the cap, and WP_Query resolves it to the posts_per_page option.
666 + $postPerPage = min( absint( $postPerPage ), self::SEARCH_POSTS_PER_PAGE_MAX );
667 + // is_scalar first: searchText arrives as an array if the caller sends one, and
668 + // casting that to string is an "Array to string conversion" warning.
669 + $searchText = is_scalar( $searchText ) ? mb_substr( (string) $searchText, 0, self::SEARCH_TEXT_MAX_LENGTH ) : '';
670 +
671 + $query_args = array(
638 672 's' => $searchText,
639 673 'paged' => $paged,
640 - 'compare' => 'LIKE',
641 674 'orderby' => 'relevance',
642 675 'posts_per_page' => $postPerPage,
643 676 );
644 677 if ( isset( $post['exclude'] ) && is_array( $post['exclude'] ) && count( $post['exclude'] ) > 0 ) {
645 - $post['exclude'] = ultimate_post()->ultp_rest_sanitize_params( $post['exclude'] );
646 - $post_exclude = array();
647 - foreach ( $post['exclude'] as $data ) {
648 - $post_exclude[ $data['title'] ] = $data['title'];
678 + $all_types = get_post_types( array( 'public' => true ), 'names' );
679 + $post_exclude = array();
680 +
681 + // Bound before sanitising: this route is public, and sanitising an array
682 + // whose length the caller picks is itself the attack. Excluding more post
683 + // types than exist is meaningless, so nothing valid is dropped.
684 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Length is bounded here only; every element is sanitized below before use.
685 + $exclude_input = array_slice( $post['exclude'], 0, count( $all_types ) );
686 +
687 + foreach ( $exclude_input as $data ) {
688 + // A flat array of strings would index a string here and warn.
689 + if ( ! is_array( $data ) || ! isset( $data['title'] ) || ! is_scalar( $data['title'] ) ) {
690 + continue;
691 + }
692 + $exclude_type = sanitize_text_field( $data['title'] );
693 + $post_exclude[ $exclude_type ] = $exclude_type;
649 694 }
650 - $all_types = get_post_types( array( 'public' => true ), 'names' );
651 - $post_type = array_diff_key( $all_types, $post_exclude );
652 - $query_args['post_type'] = $post_type;
695 +
696 + $query_args['post_type'] = array_diff_key( $all_types, $post_exclude );
653 697 }
654 698 $output = '';
655 699 $query_result = new \WP_Query( $query_args );
656 700
701 + /**
702 + * Resolved once here rather than guarded at each use, since the request may
703 + * omit any of them. The loose == is deliberate: the two callers disagree on
704 + * type -- the editor sends real booleans, the frontend sends 1/0 parsed out
705 + * of data attributes -- and both must keep working.
706 + */
707 + $show_image = isset( $post['image'] ) && $post['image'] == 1;
708 + $show_category = isset( $post['category'] ) && $post['category'] == 1;
709 + $show_author = isset( $post['author'] ) && $post['author'] == 1;
710 + $show_date = isset( $post['date'] ) && $post['date'] == 1;
711 + $show_excerpt = isset( $post['excerpt'] ) && $post['excerpt'] == 1;
712 + $excerpt_limit = isset( $post['excerptLimit'] )
713 + ? min( absint( $post['excerptLimit'] ), self::SEARCH_EXCERPT_WORDS_MAX )
714 + : 55;
715 +
657 716 if ( $query_result->have_posts() ) {
658 717 while ( $query_result->have_posts() ) {
659 718 $query_result->the_post();
660 719 $post_id = get_the_ID();
@@ -660,40 +719,59 @@
660 719 $post_id = get_the_ID();
661 720 $title = get_the_title();
662 721
663 722 $output .= '<div class="ultp-search-result__item">';
664 - if ( $post['image'] == 1 && has_post_thumbnail() ) {
665 - $thumb_id = get_post_thumbnail_id( $post_id );
666 - $output .= '<img class="ultp-searchresult-image" src=' . wp_get_attachment_image_src( $thumb_id, 'thumbnail', false )[0] . ' alt="' . $title . '"/>';
723 +
724 + if ( $show_image && has_post_thumbnail() ) {
725 + // has_post_thumbnail() only proves the _thumbnail_id meta exists.
726 + // If the attachment behind it was deleted, src() returns false.
727 + $thumb = wp_get_attachment_image_src( get_post_thumbnail_id( $post_id ), 'thumbnail', false );
728 + if ( ! empty( $thumb[0] ) ) {
729 + $output .= '<img class="ultp-searchresult-image" src="' . esc_url( $thumb[0] ) . '" alt="' . esc_attr( $title ) . '"/>';
730 + }
667 731 }
668 - $output .= '<div class="ultp-searchresult-content">';
669 - $output .= '<div class="ultp-rescontent-meta">';
670 - // Category.
671 - $post_cat = get_the_terms( $post_id, 'category' );
672 - if ( $post['category'] == 1 && $post_cat && count( $post_cat ) ) {
732 +
733 + $output .= '<div class="ultp-searchresult-content">';
734 + $output .= '<div class="ultp-rescontent-meta">';
735 +
736 + // Category.
737 + $post_cat = get_the_terms( $post_id, 'category' );
738 + if ( $show_category && $post_cat && ! is_wp_error( $post_cat ) ) {
673 739 $output .= '<div class="ultp-searchresult-category">';
674 740 foreach ( $post_cat as $cat ) {
675 - $output .= '<a href="' . get_term_link( $cat->term_id ) . '">' . $cat->name . '</a>';
741 + $term_link = get_term_link( $cat->term_id );
742 + if ( is_wp_error( $term_link ) ) {
743 + continue;
744 + }
745 + $output .= '<a href="' . esc_url( $term_link ) . '">' . esc_html( $cat->name ) . '</a>';
676 746 }
677 - $output .= '</div>';
747 + $output .= '</div>';
678 748 }
679 - // Author.
680 - if ( $post['author'] == 1 ) {
749 +
750 + // Author.
751 + if ( $show_author ) {
681 752 $user_id = get_the_author_meta( 'ID' );
682 - $output .= '<a href="' . get_author_posts_url( $user_id ) . '" class="ultp-searchresult-author">' . get_the_author_meta( 'display_name' ) . '</a>';
753 + $output .= '<a href="' . esc_url( get_author_posts_url( $user_id ) ) . '" class="ultp-searchresult-author">' . esc_html( get_the_author_meta( 'display_name' ) ) . '</a>';
683 754 }
684 - // Date.
685 - if ( $post['date'] == 1 ) {
686 - $output .= '<div class="ultp-searchresult-publishdate">' . get_the_date( 'F j, Y' ) . '</div>';
755 +
756 + // Date.
757 + if ( $show_date ) {
758 + $output .= '<div class="ultp-searchresult-publishdate">' . esc_html( get_the_date( 'F j, Y' ) ) . '</div>';
687 759 }
688 - $output .= '</div>';
689 - $output .= '<a href="' . get_permalink() . '" class="ultp-searchresult-title">' . $title . '</a>';
690 - if ( $post['excerpt'] == 1 ) {
691 - $output .= '<div class="ultp-searchresult-excerpt">' . wp_trim_words( get_the_excerpt(), isset( $post['excerptLimit'] ) ? ultimate_post()->ultp_rest_sanitize_params( $post['excerptLimit'] ) : 55 ) . '</div>';
760 +
761 + $output .= '</div>';
762 + $output .= '<a href="' . esc_url( get_permalink() ) . '" class="ultp-searchresult-title">' . esc_html( $title ) . '</a>';
763 +
764 + if ( $show_excerpt ) {
765 + // wp_trim_words() strips tags, so what comes back is plain text.
766 + $output .= '<div class="ultp-searchresult-excerpt">' . esc_html( wp_trim_words( get_the_excerpt(), $excerpt_limit ) ) . '</div>';
692 767 }
693 - $output .= '</div>';
694 - $output .= '</div>';
768 +
769 + $output .= '</div>';
770 + $output .= '</div>';
695 771 }
772 + // the_post() overwrote the global $post; hand it back.
773 + wp_reset_postdata();
696 774 }
697 775
698 776 return array(
699 777 'post_data' => $output,