| @@ -13,8 +13,43 @@ | ||
| 13 | 13 | add_action( 'wp_ajax_forumax_search_data_fetch', 'forumax_search_data_fetch' ); |
| 14 | 14 | add_action( 'wp_ajax_nopriv_forumax_search_data_fetch', 'forumax_search_data_fetch' ); |
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | + * Build the ancestor "breadcrumb" trail for a search result, mirroring the | |
| 18 | + * EazyDocs search UI (e.g. Forum › Topic for a reply). | |
| 19 | + * | |
| 20 | + * @param int $post_id Post ID. | |
| 21 | + * @param string $type Post type of the result. | |
| 22 | + * @return array List of ancestor titles, ordered from top-most down. | |
| 23 | + */ | |
| 24 | +function forumax_search_result_breadcrumb( $post_id, $type ) { | |
| 25 | + $crumbs = array(); | |
| 26 | + | |
| 27 | + if ( 'reply' === $type && function_exists( 'bbp_get_reply_forum_id' ) ) { | |
| 28 | + $forum_id = bbp_get_reply_forum_id( $post_id ); | |
| 29 | + $topic_id = bbp_get_reply_topic_id( $post_id ); | |
| 30 | + if ( $forum_id ) { | |
| 31 | + $crumbs[] = get_the_title( $forum_id ); | |
| 32 | + } | |
| 33 | + if ( $topic_id ) { | |
| 34 | + $crumbs[] = get_the_title( $topic_id ); | |
| 35 | + } | |
| 36 | + } elseif ( 'topic' === $type && function_exists( 'bbp_get_topic_forum_id' ) ) { | |
| 37 | + $forum_id = bbp_get_topic_forum_id( $post_id ); | |
| 38 | + if ( $forum_id ) { | |
| 39 | + $crumbs[] = get_the_title( $forum_id ); | |
| 40 | + } | |
| 41 | + } else { | |
| 42 | + // Generic hierarchy fallback (forums with parents, docs, pages, etc.). | |
| 43 | + foreach ( array_reverse( get_post_ancestors( $post_id ) ) as $ancestor_id ) { | |
| 44 | + $crumbs[] = get_the_title( $ancestor_id ); | |
| 45 | + } | |
| 46 | + } | |
| 47 | + | |
| 48 | + return array_values( array_filter( $crumbs ) ); | |
| 49 | +} | |
| 50 | + | |
| 51 | +/** | |
| 17 | 52 | * Fetch search data. |
| 18 | 53 | */ |
| 19 | 54 | function forumax_search_data_fetch() { |
| 20 | 55 | |
| @@ -20,31 +55,65 @@ | ||
| 20 | 55 | |
| 21 | 56 | // Check nonce. |
| 22 | 57 | check_ajax_referer( 'forumax-nonce', 'nonce' ); |
| 23 | 58 | |
| 24 | - $opt = get_option( 'bbpc_opt' ); | |
| 25 | - $is_ajax_search_tab = $opt['is_ajax_search_tab'] ?? ''; | |
| 26 | - | |
| 27 | 59 | // Sanitize keyword. |
| 28 | - $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field( $_POST['keyword'] ) : ''; | |
| 29 | - $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : ''; | |
| 60 | + $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['keyword'] ) ) : ''; | |
| 61 | + $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : ''; | |
| 30 | 62 | |
| 31 | - // Determine which post types to query. | |
| 32 | - if ( ! empty( $post_type ) ) { | |
| 63 | + // Determine which post types to query. An explicit single post type narrows | |
| 64 | + // the search; 'all' (or no value) searches the whole forum: forums, topics | |
| 65 | + // and replies, so content matches from any of them are returned. | |
| 66 | + if ( ! empty( $post_type ) && 'all' !== $post_type ) { | |
| 33 | 67 | $query_post_types = array( $post_type ); |
| 34 | 68 | } elseif ( class_exists( 'bbPress' ) ) { |
| 35 | - $query_post_types = array( 'forum', 'topic' ); | |
| 69 | + $query_post_types = array( 'forum', 'topic', 'reply' ); | |
| 36 | 70 | } else { |
| 37 | 71 | $query_post_types = array( 'post' ); |
| 38 | 72 | } |
| 39 | 73 | |
| 74 | + // Determine which statuses to include. bbPress stores closed topics with the | |
| 75 | + // 'closed' post status (and uses 'private'/'hidden' for restricted forums), | |
| 76 | + // none of which are returned by WP_Query's default 'publish'-only behavior. | |
| 77 | + // Include the publicly viewable bbPress statuses so closed topics still match. | |
| 78 | + if ( class_exists( 'bbPress' ) ) { | |
| 79 | + $query_post_status = array( 'publish', 'closed', 'private', 'hidden' ); | |
| 80 | + } else { | |
| 81 | + $query_post_status = array( 'publish' ); | |
| 82 | + } | |
| 83 | + | |
| 84 | + // Force the search to match BOTH the title and the content. This overrides | |
| 85 | + // any other plugin/theme `posts_search` filter that may otherwise restrict | |
| 86 | + // matching to titles only, so content keywords return results too. | |
| 87 | + $bbpc_search_clause = static function ( $search, $wp_query ) use ( $keyword ) { | |
| 88 | + global $wpdb; | |
| 89 | + | |
| 90 | + if ( '' === $keyword || ! $wp_query->get( 'bbpc_ajax_search' ) ) { | |
| 91 | + return $search; | |
| 92 | + } | |
| 93 | + | |
| 94 | + $like = '%' . $wpdb->esc_like( $keyword ) . '%'; | |
| 95 | + | |
| 96 | + return $wpdb->prepare( | |
| 97 | + " AND ( {$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s ) ", | |
| 98 | + $like, | |
| 99 | + $like | |
| 100 | + ); | |
| 101 | + }; | |
| 102 | + | |
| 103 | + add_filter( 'posts_search', $bbpc_search_clause, 999, 2 ); | |
| 104 | + | |
| 40 | 105 | // Main results query. |
| 41 | 106 | $results = new \WP_Query( array( |
| 42 | - 'post_type' => $query_post_types, | |
| 43 | - 's' => $keyword, | |
| 44 | - 'posts_per_page' => 10, | |
| 107 | + 'post_type' => $query_post_types, | |
| 108 | + 'post_status' => $query_post_status, | |
| 109 | + 's' => $keyword, | |
| 110 | + 'posts_per_page' => 10, | |
| 111 | + 'bbpc_ajax_search' => true, | |
| 45 | 112 | ) ); |
| 46 | 113 | |
| 114 | + remove_filter( 'posts_search', $bbpc_search_clause, 999 ); | |
| 115 | + | |
| 47 | 116 | $results_count = $results->found_posts; |
| 48 | 117 | |
| 49 | 118 | if ( $results_count > 0 ) { |
| 50 | 119 | $nsoresult = 'data-result='; |
| @@ -68,11 +137,12 @@ | ||
| 68 | 137 | if ( ! isset( $grouped[ $type ] ) ) { |
| 69 | 138 | $grouped[ $type ] = array(); |
| 70 | 139 | } |
| 71 | 140 | $grouped[ $type ][] = array( |
| 72 | - 'id' => get_the_ID(), | |
| 73 | - 'title' => get_the_title(), | |
| 74 | - 'url' => get_the_permalink(), | |
| 141 | + 'id' => get_the_ID(), | |
| 142 | + 'title' => get_the_title(), | |
| 143 | + 'url' => get_the_permalink(), | |
| 144 | + 'breadcrumb' => forumax_search_result_breadcrumb( get_the_ID(), $type ), | |
| 75 | 145 | ); |
| 76 | 146 | endwhile; |
| 77 | 147 | wp_reset_postdata(); |
| 78 | 148 | |
| @@ -79,96 +149,58 @@ | ||
| 79 | 149 | // Render grouped results. |
| 80 | 150 | $type_labels = array( |
| 81 | 151 | 'forum' => __( 'Forums', 'forumax' ), |
| 82 | 152 | 'topic' => __( 'Topics', 'forumax' ), |
| 153 | + 'reply' => __( 'Replies', 'forumax' ), | |
| 83 | 154 | 'docs' => __( 'Documentation', 'forumax' ), |
| 84 | 155 | 'post' => __( 'Posts', 'forumax' ), |
| 85 | 156 | ); |
| 86 | 157 | |
| 158 | + // Document icon shared by every result row. | |
| 159 | + $result_icon = '<svg class="bbpc-result-icon" width="16" height="16" viewBox="0 0 17 17" fill="currentColor" role="img" aria-hidden="true"><path d="M14.72,0H2.28A2.28,2.28,0,0,0,0,2.28V14.72A2.28,2.28,0,0,0,2.28,17H14.72A2.28,2.28,0,0,0,17,14.72V2.28A2.28,2.28,0,0,0,14.72,0ZM2.28,1H14.72A1.28,1.28,0,0,1,16,2.28V5.33H1V2.28A1.28,1.28,0,0,1,2.28,1ZM1,14.72V6.33H5.33V16H2.28A1.28,1.28,0,0,1,1,14.72ZM14.72,16H6.33V6.33H16v8.39A1.28,1.28,0,0,1,14.72,16Z"></path></svg>'; | |
| 160 | + | |
| 161 | + // Result type tabs (All / Forums / Topics / Replies), mirroring EazyDocs. | |
| 162 | + ?> | |
| 163 | + <div class="bbpc-result-tabs"> | |
| 164 | + <button type="button" class="bbpc-tab active" data-tab="all"><?php esc_html_e( 'All', 'forumax' ); ?></button> | |
| 165 | + <?php foreach ( $grouped as $type => $posts ) : | |
| 166 | + $tab_label = isset( $type_labels[ $type ] ) ? $type_labels[ $type ] : ucfirst( $type ); | |
| 167 | + ?> | |
| 168 | + <button type="button" class="bbpc-tab" data-tab="<?php echo esc_attr( $type ); ?>"><?php echo esc_html( $tab_label ); ?></button> | |
| 169 | + <?php endforeach; ?> | |
| 170 | + </div> | |
| 171 | + <?php | |
| 172 | + | |
| 87 | 173 | foreach ( $grouped as $type => $posts ) : |
| 88 | 174 | $label = isset( $type_labels[ $type ] ) ? $type_labels[ $type ] : ucfirst( $type ); |
| 89 | - echo '<h2 class="bbpc-search-title">' . esc_html( $label ) . '</h2>'; | |
| 90 | - foreach ( $posts as $post_item ) : | |
| 91 | - ?> | |
| 92 | - <div class="search-result-item forum" onclick="document.location='<?php echo esc_url( $post_item['url'] ); ?>'"> | |
| 93 | - <a href="<?php echo esc_url( $post_item['url'] ); ?>"> | |
| 94 | - <?php echo esc_html( $post_item['title'] ); ?> | |
| 95 | - </a> | |
| 96 | - </div> | |
| 97 | - <?php | |
| 98 | - endforeach; | |
| 175 | + ?> | |
| 176 | + <div class="bbpc-result-group" data-type="<?php echo esc_attr( $type ); ?>"> | |
| 177 | + <div class="bbpc-result-group-label"><?php echo esc_html( $label ); ?></div> | |
| 178 | + <?php foreach ( $posts as $post_item ) : ?> | |
| 179 | + <div class="search-result-item" data-url="<?php echo esc_url( $post_item['url'] ); ?>" onclick="document.location='<?php echo esc_url( $post_item['url'] ); ?>'"> | |
| 180 | + <a href="<?php echo esc_url( $post_item['url'] ); ?>" class="bbpc-result-link"> | |
| 181 | + <?php echo $result_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static markup. ?> | |
| 182 | + <span class="bbpc-result-body"> | |
| 183 | + <span class="bbpc-result-title"><?php echo esc_html( $post_item['title'] ); ?></span> | |
| 184 | + <span class="bbpc-result-type"><?php echo esc_html( $label ); ?></span> | |
| 185 | + </span> | |
| 186 | + </a> | |
| 187 | + <?php if ( ! empty( $post_item['breadcrumb'] ) ) : ?> | |
| 188 | + <ol class="bbpc-result-breadcrumb"> | |
| 189 | + <?php foreach ( $post_item['breadcrumb'] as $crumb ) : ?> | |
| 190 | + <li class="bbpc-breadcrumb-item"><?php echo esc_html( $crumb ); ?></li> | |
| 191 | + <?php endforeach; ?> | |
| 192 | + </ol> | |
| 193 | + <?php endif; ?> | |
| 194 | + </div> | |
| 195 | + <?php endforeach; ?> | |
| 196 | + </div> | |
| 197 | + <?php | |
| 99 | 198 | endforeach; |
| 100 | 199 | endif; |
| 101 | 200 | |
| 102 | 201 | echo '</div>'; |
| 103 | - echo '<h5 class="not-found-text">' . esc_html__( 'Not Found Result!', 'forumax' ) . '</h5>'; | |
| 104 | 202 | echo '</div>'; |
| 105 | - wp_die(); | |
| 106 | -} | |
| 107 | - | |
| 108 | -/** | |
| 109 | - * Forum Search results | |
| 110 | - */ | |
| 111 | -add_action( 'wp_ajax_forumax_search_data_forum', 'forumax_search_data_forum' ); | |
| 112 | -add_action( 'wp_ajax_nopriv_forumax_search_data_forum', 'forumax_search_data_forum' ); | |
| 113 | - | |
| 114 | -/** | |
| 115 | - * Search data forum. | |
| 116 | - */ | |
| 117 | -function forumax_search_data_forum() { | |
| 118 | - | |
| 119 | - // Check nonce | |
| 120 | - check_ajax_referer( 'forumax-nonce', 'nonce' ); | |
| 121 | - | |
| 122 | - // Sanitize keyword | |
| 123 | - $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field( $_POST['keyword'] ) : ''; | |
| 124 | - | |
| 125 | - // Forums query | |
| 126 | - if ( class_exists( 'bbPress' ) ) : | |
| 127 | - | |
| 128 | - echo '<div class="search-results-tab show" id="search-forum-results">'; | |
| 129 | - | |
| 130 | - $forum = new WP_Query( [ | |
| 131 | - 'post_type' => 'forum', | |
| 132 | - 's' => $keyword, | |
| 133 | - 'posts_per_page' => 5, | |
| 134 | - ] ); | |
| 135 | - | |
| 136 | - if ( $forum->have_posts() ) : | |
| 137 | - echo '<h2 class="bbpc-search-title">' . esc_html__( 'Forums', 'forumax' ) . '</h2>'; | |
| 138 | - while ( $forum->have_posts() ) : $forum->the_post(); | |
| 139 | - ?> | |
| 140 | - <div class="search-result-item forum" onclick="document.location='<?php echo esc_url( get_the_permalink( get_the_ID() ) ); ?>'"> | |
| 141 | - <a href="<?php echo esc_url( get_the_permalink( get_the_ID() ) ); ?>"> | |
| 142 | - <?php the_title(); ?> | |
| 143 | - </a> | |
| 144 | - </div> | |
| 145 | - <?php | |
| 146 | - endwhile; | |
| 147 | - wp_reset_postdata(); | |
| 148 | - endif; | |
| 149 | - | |
| 150 | - $topics = new WP_Query( [ | |
| 151 | - 'post_type' => 'topic', | |
| 152 | - 's' => $keyword, | |
| 153 | - 'posts_per_page' => 5, | |
| 154 | - ] ); | |
| 155 | - | |
| 156 | - if ( $topics->have_posts() ) : | |
| 157 | - echo '<h2 class="bbpc-search-title">' . esc_html__( 'Topics', 'forumax' ) . '</h2>'; | |
| 158 | - while ( $topics->have_posts() ) : $topics->the_post(); | |
| 159 | - ?> | |
| 160 | - <div class="search-result-item forum" onclick="document.location='<?php echo esc_url( get_the_permalink( get_the_ID() ) ); ?>'"> | |
| 161 | - <a href="<?php echo esc_url( get_the_permalink( get_the_ID() ) ); ?>"> | |
| 162 | - <?php the_title(); ?> | |
| 163 | - </a> | |
| 164 | - </div> | |
| 165 | - <?php | |
| 166 | - endwhile; | |
| 167 | - wp_reset_postdata(); | |
| 168 | - endif; | |
| 169 | - echo '</div>'; | |
| 170 | - endif; | |
| 171 | 203 | wp_die(); |
| 172 | 204 | } |
| 173 | 205 | |
| 174 | 206 | /** |