Skip to:
Content

bbPress.org

Changeset 8132


Ignore:
Timestamp:
10/24/2026 12:30:28 AM (5 hours ago)
Author:
johnjamesjacoby
Message:

Search: Exclude password-protected content.

Exclude password-protected forums, topics, replies, and protected forum descendants from search results. Keep direct password-cookie access and authorized administration workflows unchanged.

Cache protected forum IDs, follow canonical parents, and retain existing visibility filters without changing parent-topic status rules.

In trunk, for 2.7.

Location:
trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/CHANGELOG.md

    r8131 r8132  
    5656### Security
    5757
     58- Prevented password-protected forum content from influencing search results.
    5859- Required permission to manage both forums before moving a topic between them.
    5960- Kept password-protected topic and reply content out of BuddyPress activity.
  • trunk/src/includes/common/functions.php

    r8096 r8132  
    121121                " AND ( {$bbp_db->posts}.post_type <> %s OR EXISTS ( SELECT 1 FROM {$bbp_db->posts} AS bbp_parent_topic WHERE bbp_parent_topic.ID = {$bbp_db->posts}.post_parent AND bbp_parent_topic.post_type = %s AND bbp_parent_topic.post_status IN ({$placeholders}) ) )",
    122122                array_merge( array( bbp_get_reply_post_type(), bbp_get_topic_post_type() ), $statuses )
     123        );
     124
     125        return $where;
     126}
     127
     128/**
     129 * Exclude password-protected bbPress matches from search queries.
     130 *
     131 * Password-protected content stays out of public search results even after its
     132 * password has been entered. Direct requests continue to use WordPress's post
     133 * password cookie normally.
     134 *
     135 * @since 2.6.21 bbPress (r8133)
     136 *
     137 * @param string   $where       SQL WHERE clause.
     138 * @param WP_Query $posts_query Posts query.
     139 * @return string SQL WHERE clause.
     140 */
     141function bbp_password_protected_search_where( $where = '', $posts_query = null ) {
     142        if ( ! $posts_query instanceof WP_Query || ! $posts_query->get( '_bbp_password_protected_search' ) ) {
     143                return $where;
     144        }
     145
     146        $bbp_db = bbp_db();
     147        $where .= $bbp_db->prepare(
     148                " AND ( {$bbp_db->posts}.post_type NOT IN ( %s, %s, %s ) OR CHAR_LENGTH( {$bbp_db->posts}.post_password ) = 0 )",
     149                bbp_get_forum_post_type(),
     150                bbp_get_topic_post_type(),
     151                bbp_get_reply_post_type()
     152        );
     153
     154        // Metadata is a fallback, not a replacement for canonical forum ancestry.
     155        $forum_ids       = wp_parse_id_list( $posts_query->get( '_bbp_password_protected_forum_ids' ) );
     156        $topic_forum_sql = '';
     157        if ( ! empty( $forum_ids ) ) {
     158                $placeholders = implode( ', ', array_fill( 0, count( $forum_ids ), '%d' ) );
     159                $where       .= $bbp_db->prepare(
     160                        " AND ( {$bbp_db->posts}.post_type <> %s OR {$bbp_db->posts}.ID NOT IN ({$placeholders}) )
     161                        AND ( {$bbp_db->posts}.post_type <> %s OR {$bbp_db->posts}.post_parent NOT IN ({$placeholders}) )",
     162                        array_merge( array( bbp_get_forum_post_type() ), $forum_ids, array( bbp_get_topic_post_type() ), $forum_ids )
     163                );
     164                $topic_forum_sql = $bbp_db->prepare( " AND bbp_password_topic.post_parent NOT IN ({$placeholders})", $forum_ids );
     165        }
     166
     167        // Follow reply ancestry without changing existing parent-topic status rules.
     168        $where .= $bbp_db->prepare(
     169                " AND ( {$bbp_db->posts}.post_type <> %s OR EXISTS ( SELECT 1 FROM {$bbp_db->posts} AS bbp_password_topic WHERE bbp_password_topic.ID = {$bbp_db->posts}.post_parent AND bbp_password_topic.post_type = %s AND CHAR_LENGTH( bbp_password_topic.post_password ) = 0{$topic_forum_sql} ) )",
     170                bbp_get_reply_post_type(),
     171                bbp_get_topic_post_type()
    123172        );
    124173
  • trunk/src/includes/core/cache.php

    r7856 r8132  
    129129 *
    130130 * Recurses through parent posts and updates the bbpress_posts last_changed
    131  * cache value when the forum root is reached. Runs on clean_post_cache.
     131 * cache value when the forum root or an invalid parent is reached. Runs on
     132 * clean_post_cache.
    132133 *
    133134 * @since 2.1.0 bbPress (r4041)
    134135 * @since 2.6.0 bbPress (r6053) Introduced the `$post_id` parameter.
     136 * @since 2.6.21 bbPress (r8133) Invalidate queries when a bbPress parent is missing.
    135137 *
    136138 * @param int     $post_id ID supplied by clean_post_cache. The post object supplies the ID used here.
    … …  
    161163        do_action( 'bbp_clean_post_cache', $post->ID, $post );
    162164
    163         // Invalidate parent caches
    164         if ( ! empty( $post->post_parent ) ) {
    165                 clean_post_cache( $post->post_parent );
    166 
    167         // Only bump `last_changed` when forum-root is reached
    168         } else {
     165        // Invalidate existing parent caches, including non-bbPress parents.
     166        $parent = ! empty( $post->post_parent ) ? get_post( $post->post_parent ) : null;
     167        if ( ! empty( $parent ) ) {
     168                clean_post_cache( $parent );
     169        }
     170
     171        // A missing or non-bbPress parent cannot propagate invalidation to a root.
     172        if ( empty( $parent ) || ! in_array( $parent->post_type, $post_types, true ) ) {
    169173                wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
    170174        }
  • trunk/src/includes/core/filters.php

    r8129 r8132  
    9191add_action( 'posts_pre_query',                 'bbp_posts_pre_query',             10, 2 );
    9292add_filter( 'posts_where',                     'bbp_public_topic_replies_where',  11, 2 );
     93add_filter( 'posts_where',                     'bbp_password_protected_search_where', 12, 2 );
    9394
    9495// User Creation
  • trunk/src/includes/forums/functions.php

    r8126 r8132  
    26512651
    26522652/**
     2653 * Return password-protected forum IDs and all of their descendants.
     2654 *
     2655 * @since 2.6.21 bbPress (r8133)
     2656 *
     2657 * @return int[] Password-protected forum IDs and descendant forum IDs.
     2658 */
     2659function bbp_get_password_protected_forum_ids() {
     2660        $last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
     2661        $cache_key    = 'bbp_password_protected_forum_ids:' . $last_changed;
     2662        $forum_ids    = wp_cache_get( $cache_key, 'bbpress_posts' );
     2663
     2664        if ( false === $forum_ids ) {
     2665                // Length checks preserve whitespace-only passwords with padded collations.
     2666                $bbp_db    = bbp_db();
     2667                $query     = $bbp_db->prepare(
     2668                        "SELECT ID FROM {$bbp_db->posts} WHERE post_type = %s AND CHAR_LENGTH( post_password ) > 0",
     2669                        bbp_get_forum_post_type()
     2670                );
     2671                $forum_ids = wp_parse_id_list( $bbp_db->get_col( $query ) );
     2672
     2673                if ( ! empty( $forum_ids ) ) {
     2674                        $forum_ids = bbp_forum_query_all_descendant_ids( $forum_ids );
     2675                }
     2676
     2677                wp_cache_set( $cache_key, $forum_ids, 'bbpress_posts' );
     2678        }
     2679
     2680        return (array) apply_filters( 'bbp_get_password_protected_forum_ids', $forum_ids );
     2681}
     2682
     2683/**
    26532684 * Returns the forum IDs that should be excluded from various views & queries,
    26542685 * based on the current user's capabilities.
    … …  
    28412872 * @since 2.3.0 bbPress (r4808)
    28422873 * @since 2.6.19 bbPress (r7691) Exclude restricted forum IDs from post__in queries.
     2874 * @since 2.6.21 bbPress (r8133) Exclude password-restricted content from searches.
    28432875 *
    28442876 * @global string $pagenow The filename of the current screen.
    … …  
    28602892        }
    28612893
    2862         // Preserve the main post list query without exempting admin-ajax queries
    2863         if ( is_admin() && ! wp_doing_ajax() && isset( $_REQUEST['post_status'] ) && isset( $GLOBALS['pagenow'] ) && ( 'edit.php' === $GLOBALS['pagenow'] ) && $posts_query->is_main_query() && current_user_can( 'moderate' ) ) {
     2894        // Preserve the existing explicit-status admin exemption.
     2895        $is_admin_list = is_admin() && ! wp_doing_ajax() && isset( $GLOBALS['pagenow'] ) && ( 'edit.php' === $GLOBALS['pagenow'] ) && $posts_query->is_main_query() && current_user_can( 'moderate' );
     2896        if ( $is_admin_list && isset( $_REQUEST['post_status'] ) ) {
    28642897                return;
    28652898        }
    … …  
    28942927        // Mark this query as normalized
    28952928        $posts_query->set( '_bbp_forum_visibility_normalized', true );
     2929
     2930        // Singular requests can still contain a search term that WordPress uses in SQL.
     2931        $password_search = $posts_query->is_search() || ( '' !== (string) $posts_query->get( 's' ) );
     2932
     2933        // Preserve admin lists and suggestions without skipping forum restrictions.
     2934        if ( $is_admin_list || ( $posts_query->get( 'suppress_filters' ) && wp_doing_ajax() && current_user_can( 'moderate' ) ) ) {
     2935                $password_search = false;
     2936        }
    28962937
    28972938        // Separate non-bbPress post types from supported bbPress post types.
    … …  
    29072948         * without changing the requested non-bbPress post types.
    29082949         */
    2909         if ( ! empty( $non_bbp_post_types ) && $posts_query->get( 'suppress_filters' ) ) {
    2910                 $posts_query->set( 'post_type', array_values( $non_bbp_post_types ) );
    2911                 return;
     2950        if ( $posts_query->get( 'suppress_filters' ) ) {
     2951                if ( ! empty( $non_bbp_post_types ) ) {
     2952                        $posts_query->set( 'post_type', array_values( $non_bbp_post_types ) );
     2953                        return;
     2954                }
     2955
     2956                // Password inheritance requires a posts_where clause during searches.
     2957                if ( $password_search ) {
     2958                        // Explicit IDs take precedence over post__in in WordPress.
     2959                        $posts_query->set( 'p', 0 );
     2960                        $posts_query->set( 'page_id', 0 );
     2961                        $posts_query->set( 'attachment_id', 0 );
     2962                        $posts_query->set( 'post__in', array( 0 ) );
     2963                        return;
     2964                }
    29122965        }
    29132966
    29142967        // Get forums to exclude.
    2915         $forum_ids = bbp_exclude_forum_ids( 'array' );
     2968        $forum_ids          = bbp_exclude_forum_ids( 'array' );
     2969        $password_forum_ids = array();
     2970
     2971        /**
     2972         * Search matches must not reveal whether password-protected content contains
     2973         * a term. Add password-protected forum trees to the normal visibility rules
     2974         * and exclude protected topics through the password-search clause.
     2975         */
     2976        if ( $password_search ) {
     2977                $password_forum_ids  = bbp_get_password_protected_forum_ids();
     2978                $forum_ids           = wp_parse_id_list( array_merge( $forum_ids, $password_forum_ids ) );
     2979                $posts_query->set( '_bbp_password_protected_search', true );
     2980                $posts_query->set( '_bbp_password_protected_forum_ids', $password_forum_ids );
     2981        }
    29162982
    29172983        // Forums
    … …  
    29242990                 * clause, private and hidden content inherits the broader forum statuses.
    29252991                 */
    2926                 if ( ! empty( $content_post_types ) ) {
     2992                if ( ! $is_admin_list && ! empty( $content_post_types ) ) {
    29272993                        $content_post_statuses = bbp_get_string_array_values( $posts_query->get( 'post_status' ) );
    29282994
    … …  
    29383004                }
    29393005
    2940                 // Add all supported forum visibilities to bbPress-only queries.
    2941                 if ( empty( $non_bbp_post_types ) ) {
     3006                // Add forum visibilities without replacing WordPress admin-list statuses.
     3007                if ( ! $is_admin_list && empty( $non_bbp_post_types ) ) {
    29423008                        $posts_query->set(
    29433009                                'post_status',
    … …  
    29893055
    29903056        /**
    2991          * Mixed queries need a post-type-aware SQL clause. A meta query would use an
    2992          * inner join and unintentionally remove non-bbPress posts that do not have
    2993          * bbPress forum metadata.
     3057         * Mixed queries need a post-type-aware clause to preserve non-bbPress posts.
     3058         * Searches also use it to reject every excluded forum metadata value, rather
     3059         * than allowing a duplicate public value to satisfy a NOT IN meta query.
    29943060         */
    2995         if ( ! empty( $non_bbp_post_types ) ) {
     3061        if ( ! empty( $non_bbp_post_types ) || $password_search ) {
    29963062                $posts_query->set( '_bbp_forum_visibility_post_types', $content_post_types );
    29973063                $posts_query->set( '_bbp_forum_visibility_forum_ids',  $forum_ids          );
    2998                 return;
    2999         }
    3000 
    3001         // Get forum meta query.
     3064
     3065                // Mixed queries cannot use a meta join without losing unrelated posts.
     3066                if ( ! empty( $non_bbp_post_types ) ) {
     3067                        return;
     3068                }
     3069        }
     3070
     3071        // Preserve the existing meta-query filter for bbPress-only queries.
    30023072        $forum_meta_query = bbp_exclude_forum_ids( 'meta_query' );
    30033073
    … …  
    30253095
    30263096/**
    3027  * Excludes protected bbPress content from mixed post-type queries.
     3097 * Excludes protected bbPress content from mixed post-type queries and searches.
    30283098 *
    30293099 * A normal meta query cannot scope its join to bbPress post types, causing
    … …  
    31173187
    31183188        /**
    3119          * Filters the forum visibility SQL appended to mixed post-type queries.
     3189         * Filters the forum visibility SQL appended to mixed queries and searches.
    31203190         *
    31213191         * @since 2.6.15 bbPress (r7415)
  • trunk/tests/phpunit/testcases/replies/password-visibility.php

    r8057 r8132  
    7373                                $this->set_current_user( $user_id );
    7474
    75                                 foreach ( array( '/?post_type=reply', '/?s=Protected&post_type=reply' ) as $url ) {
    76                                         $this->go_to_without_reregistering_blocks( $url );
    77                                         $this->assertContains( $reply_id, wp_list_pluck( $GLOBALS['wp_query']->posts, 'ID' ) );
    78                                         $this->assertTrue( post_password_required( $reply_id ) );
    79                                         $this->assertStringNotContainsString( 'Protected core reply marker', get_the_content( null, false, $reply_id ) );
    80                                 }
     75                                $this->go_to_without_reregistering_blocks( '/?post_type=reply' );
     76                                $this->assertContains( $reply_id, wp_list_pluck( $GLOBALS['wp_query']->posts, 'ID' ) );
     77                                $this->assertTrue( post_password_required( $reply_id ) );
     78                                $this->assertStringNotContainsString( 'Protected core reply marker', get_the_content( null, false, $reply_id ) );
     79
     80                                $this->go_to_without_reregistering_blocks( '/?s=Protected&post_type=reply' );
     81                                $this->assertNotContains( $reply_id, wp_list_pluck( $GLOBALS['wp_query']->posts, 'ID' ) );
    8182                        }
    8283
    … …  
    8889                        $this->assertFalse( post_password_required( $reply_id ) );
    8990                        $this->assertStringContainsString( 'Protected core reply marker', get_the_content( null, false, $reply_id ) );
     91
     92                        $this->go_to_without_reregistering_blocks( '/?s=Protected&post_type=reply' );
     93                        $this->assertNotContains( $reply_id, wp_list_pluck( $GLOBALS['wp_query']->posts, 'ID' ) );
    9094                } finally {
    9195                        $this->set_current_user( 0 );
  • trunk/tests/phpunit/testcases/search/template/visibility.php

    r7623 r8132  
    6868                $this->assertContains( $reply_id, wp_list_pluck( bbpress()->search_query->posts, 'ID' ) );
    6969        }
     70
     71        /**
     72         * @covers ::bbp_has_search_results
     73         * @covers ::bbp_get_password_protected_forum_ids
     74         * @covers ::bbp_password_protected_search_where
     75         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     76         * @covers ::bbp_public_topic_replies_where
     77         */
     78        public function test_search_excludes_password_protected_content_for_all_roles() {
     79                $forum_id = $this->factory->forum->create(
     80                        array(
     81                                'post_content'  => 'Protected forum search sentinel 8143',
     82                                'post_password' => 'forum-secret',
     83                        )
     84                );
     85                $topic_id = $this->factory->topic->create(
     86                        array(
     87                                'post_content' => 'Protected topic search sentinel 8462',
     88                                'post_parent'  => $forum_id,
     89                                'topic_meta'   => array( 'forum_id' => $forum_id ),
     90                        )
     91                );
     92                $reply_id = $this->factory->reply->create(
     93                        array(
     94                                'post_parent'  => $topic_id,
     95                                'post_content' => 'Protected search oracle sentinel 4816',
     96                                'reply_meta'   => array( 'forum_id' => $forum_id, 'topic_id' => $topic_id ),
     97                        )
     98                );
     99                $child_forum_id = $this->factory->forum->create(
     100                        array( 'post_parent' => $forum_id )
     101                );
     102                $child_topic_id = $this->factory->topic->create(
     103                        array(
     104                                'post_parent' => $child_forum_id,
     105                                'topic_meta'  => array( 'forum_id' => $child_forum_id ),
     106                        )
     107                );
     108                $child_reply_id = $this->factory->reply->create(
     109                        array(
     110                                'post_parent'  => $child_topic_id,
     111                                'post_content' => 'Nested protected search sentinel 5937',
     112                                'reply_meta'   => array( 'forum_id' => $child_forum_id, 'topic_id' => $child_topic_id ),
     113                        )
     114                );
     115                $draft_forum_id = $this->factory->forum->create(
     116                        array(
     117                                'post_parent' => $forum_id,
     118                                'post_status' => 'draft',
     119                        )
     120                );
     121                $deep_forum_id = $this->factory->forum->create(
     122                        array( 'post_parent' => $draft_forum_id )
     123                );
     124                $deep_topic_id = $this->factory->topic->create(
     125                        array(
     126                                'post_parent' => $deep_forum_id,
     127                                'topic_meta'  => array( 'forum_id' => $deep_forum_id ),
     128                        )
     129                );
     130                $deep_reply_id = $this->factory->reply->create(
     131                        array(
     132                                'post_parent'  => $deep_topic_id,
     133                                'post_content' => 'Deep protected search sentinel 9164',
     134                                'reply_meta'   => array( 'forum_id' => $deep_forum_id, 'topic_id' => $deep_topic_id ),
     135                        )
     136                );
     137                $public_forum_id = $this->factory->forum->create();
     138                $protected_topic_id = $this->factory->topic->create(
     139                        array(
     140                                'post_parent'   => $public_forum_id,
     141                                'post_password' => 'topic-secret',
     142                                'topic_meta'    => array( 'forum_id' => $public_forum_id ),
     143                        )
     144                );
     145                $topic_reply_id = $this->factory->reply->create(
     146                        array(
     147                                'post_parent'  => $protected_topic_id,
     148                                'post_content' => 'Topic protected search sentinel 6249',
     149                                'reply_meta'   => array( 'forum_id' => $public_forum_id, 'topic_id' => $protected_topic_id ),
     150                        )
     151                );
     152                $public_topic_id = $this->factory->topic->create(
     153                        array(
     154                                'post_parent' => $public_forum_id,
     155                                'topic_meta'  => array( 'forum_id' => $public_forum_id ),
     156                        )
     157                );
     158                $public_reply_id = $this->factory->reply->create(
     159                        array(
     160                                'post_parent'  => $public_topic_id,
     161                                'post_content' => 'Public search sentinel 7351',
     162                                'reply_meta'   => array( 'forum_id' => $public_forum_id, 'topic_id' => $public_topic_id ),
     163                        )
     164                );
     165                $public_post_id = $this->factory->post->create(
     166                        array( 'post_content' => 'Topic protected search sentinel 6249' )
     167                );
     168                $restricted = array(
     169                        'Protected forum search sentinel 8143' => $forum_id,
     170                        'Protected topic search sentinel 8462' => $topic_id,
     171                        'Protected search oracle sentinel 4816' => $reply_id,
     172                        'Nested protected search sentinel 5937' => $child_reply_id,
     173                        'Deep protected search sentinel 9164'   => $deep_reply_id,
     174                        'Topic protected search sentinel 6249'  => $topic_reply_id,
     175                );
     176                $user_ids = array( 0 );
     177                $this->assertFalse( bbp_has_search_results( array( 's' => 'Protected search oracle sentinel 4816' ) ) );
     178                $this->assertStringContainsString( 'post_password ) = 0', bbpress()->search_query->request );
     179
     180                $custom_query = new WP_Query(
     181                        array(
     182                                'post_type' => bbp_get_reply_post_type(),
     183                                's'         => 'Topic protected search sentinel 6249',
     184                        )
     185                );
     186                $this->assertNotContains( $topic_reply_id, wp_list_pluck( $custom_query->posts, 'ID' ) );
     187
     188                $mixed_query = new WP_Query(
     189                        array(
     190                                'post_type' => array( 'post', bbp_get_reply_post_type() ),
     191                                's'         => 'Topic protected search sentinel 6249',
     192                        )
     193                );
     194                $this->assertContains( $public_post_id, wp_list_pluck( $mixed_query->posts, 'ID' ) );
     195                $this->assertNotContains( $topic_reply_id, wp_list_pluck( $mixed_query->posts, 'ID' ) );
     196
     197                $suppressed_query = new WP_Query(
     198                        array(
     199                                'post_type'        => bbp_get_reply_post_type(),
     200                                's'                => 'Topic protected search sentinel 6249',
     201                                'suppress_filters' => true,
     202                        )
     203                );
     204                $this->assertEmpty( $suppressed_query->posts );
     205
     206                foreach ( array( '', bbp_get_participant_role(), bbp_get_moderator_role(), bbp_get_keymaster_role() ) as $role ) {
     207                        $user_id = $this->factory->user->create();
     208                        $user    = new WP_User( $user_id );
     209
     210                        if ( empty( $role ) ) {
     211                                $user->set_role( '' );
     212                        } else {
     213                                bbp_set_user_role( $user_id, $role );
     214                        }
     215
     216                        $user_ids[] = $user_id;
     217                }
     218
     219                foreach ( $user_ids as $user_id ) {
     220                        $this->set_current_user( $user_id );
     221
     222                        foreach ( $restricted as $search_term => $restricted_reply_id ) {
     223                                $this->assertFalse( bbp_has_search_results( array( 's' => $search_term ) ) );
     224                                $this->assertNotContains( $restricted_reply_id, wp_list_pluck( bbpress()->search_query->posts, 'ID' ) );
     225                        }
     226
     227                        $this->assertTrue( bbp_has_search_results( array( 's' => 'Public search sentinel 7351' ) ) );
     228                        $this->assertContains( $public_reply_id, wp_list_pluck( bbpress()->search_query->posts, 'ID' ) );
     229                }
     230
     231                require_once ABSPATH . WPINC . '/class-phpass.php';
     232                $hasher     = new PasswordHash( 8, true );
     233                $cookie     = 'wp-postpass_' . COOKIEHASH;
     234                $old_cookie = isset( $_COOKIE[ $cookie ] ) ? $_COOKIE[ $cookie ] : null;
     235
     236                try {
     237                        $this->set_current_user( 0 );
     238
     239                        foreach ( array( 'forum-secret', 'topic-secret' ) as $password ) {
     240                                $_COOKIE[ $cookie ] = $hasher->HashPassword( $password );
     241
     242                                foreach ( $restricted as $search_term => $restricted_post_id ) {
     243                                        $this->assertFalse( bbp_has_search_results( array( 's' => $search_term ) ) );
     244                                        $this->assertNotContains( $restricted_post_id, wp_list_pluck( bbpress()->search_query->posts, 'ID' ) );
     245                                }
     246                        }
     247                } finally {
     248                        if ( null === $old_cookie ) {
     249                                unset( $_COOKIE[ $cookie ] );
     250                        } else {
     251                                $_COOKIE[ $cookie ] = $old_cookie;
     252                        }
     253                }
     254        }
     255
     256        /**
     257         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     258         * @covers ::bbp_password_protected_search_where
     259         */
     260        public function test_search_terms_remain_protected_in_singular_requests() {
     261                $protected = $this->create_search_tree( 'forum-secret' );
     262                $public    = $this->create_search_tree();
     263
     264                foreach ( array( $protected, $public ) as $tree ) {
     265                        foreach ( array( 'forum', 'topic', 'reply' ) as $type ) {
     266                                $post_id = $tree[ $type ];
     267                                $post    = get_post( $post_id );
     268
     269                                foreach ( array( 'p' => $post_id, 'page_id' => $post_id, 'name' => $post->post_name ) as $selector => $value ) {
     270                                        foreach ( array( 'sentinel', '0', 'absentterm' ) as $term ) {
     271                                                $args = array(
     272                                                        'post_type' => $post->post_type,
     273                                                        $selector   => $value,
     274                                                        's'         => $term,
     275                                                );
     276                                                $query = new WP_Query( $args );
     277                                                $ids   = wp_list_pluck( $query->posts, 'ID' );
     278
     279                                                if ( ( $tree === $public ) && ( 'absentterm' !== $term ) ) {
     280                                                        $this->assertContains( $post_id, $ids );
     281                                                } else {
     282                                                        $this->assertEmpty( $ids );
     283                                                        $this->assertSame( 0, (int) $query->found_posts );
     284                                                }
     285
     286                                                // Explicit IDs must not override the fail-closed fallback.
     287                                                $args['suppress_filters'] = true;
     288                                                $suppressed = new WP_Query( $args );
     289                                                $this->assertEmpty( $suppressed->posts );
     290                                        }
     291                                }
     292
     293                                // A request with no search term retains normal direct navigation.
     294                                $direct = new WP_Query( array( 'post_type' => $post->post_type, 'p' => $post_id ) );
     295                                $this->assertContains( $post_id, wp_list_pluck( $direct->posts, 'ID' ) );
     296                        }
     297                }
     298
     299                $attachment = new WP_Query( array( 'post_type' => bbp_get_reply_post_type(), 'attachment_id' => $protected['reply'], 's' => 'sentinel', 'suppress_filters' => true ) );
     300                $this->assertEmpty( $attachment->posts );
     301
     302                // Exercise the actual public main-query route, not only secondary queries.
     303                $register_blocks = remove_action( 'bbp_register', 'bbp_register_blocks', 10 );
     304                try {
     305                        $this->go_to( add_query_arg( array( 'post_type' => bbp_get_reply_post_type(), 'p' => $protected['reply'], 's' => 'sentinel' ), home_url( '/' ) ) );
     306                        $this->assertEmpty( $GLOBALS['wp_query']->posts );
     307                        $this->go_to( add_query_arg( array( 'post_type' => bbp_get_reply_post_type(), 'p' => $public['reply'], 's' => 'sentinel' ), home_url( '/' ) ) );
     308                        $this->assertContains( $public['reply'], wp_list_pluck( $GLOBALS['wp_query']->posts, 'ID' ) );
     309                } finally {
     310                        if ( $register_blocks ) {
     311                                add_action( 'bbp_register', 'bbp_register_blocks', 10 );
     312                        }
     313                }
     314        }
     315
     316        /**
     317         * @covers ::bbp_password_protected_search_where
     318         * @covers ::bbp_public_topic_replies_where
     319         * @covers ::_bbp_forum_visibility_where
     320         */
     321        public function test_search_checks_canonical_ancestry_and_all_forum_metadata() {
     322                $protected = $this->create_search_tree( 'forum-secret' );
     323                $public    = $this->create_search_tree();
     324                $post_id   = $this->factory->post->create( array( 'post_content' => 'Search ancestry sentinel 0' ) );
     325
     326                foreach ( array( 'duplicate', 'stale', 'missing', 'fallback' ) as $metadata ) {
     327                        foreach ( array( 'topic', 'reply' ) as $type ) {
     328                                delete_post_meta( $protected[ $type ], '_bbp_forum_id' );
     329
     330                                if ( 'missing' !== $metadata ) {
     331                                        add_post_meta( $protected[ $type ], '_bbp_forum_id', ( 'fallback' === $metadata ) ? $protected['forum'] : $public['forum'] );
     332                                }
     333                                if ( 'duplicate' === $metadata ) {
     334                                        add_post_meta( $protected[ $type ], '_bbp_forum_id', $protected['forum'] );
     335                                }
     336                        }
     337
     338                        if ( 'fallback' === $metadata ) {
     339                                wp_update_post( array( 'ID' => $protected['topic'], 'post_parent' => 0 ) );
     340                        }
     341
     342                        foreach ( array( bbp_get_post_types(), array_merge( array( 'post' ), bbp_get_post_types() ) ) as $post_types ) {
     343                                $query = new WP_Query( array( 'post_type' => $post_types, 's' => 'sentinel', 'posts_per_page' => -1 ) );
     344                                $ids   = wp_list_pluck( $query->posts, 'ID' );
     345
     346                                foreach ( $protected as $protected_id ) {
     347                                        $this->assertNotContains( $protected_id, $ids );
     348                                }
     349                                foreach ( $public as $public_id ) {
     350                                        $this->assertContains( $public_id, $ids );
     351                                }
     352                                if ( in_array( 'post', $post_types, true ) ) {
     353                                        $this->assertContains( $post_id, $ids );
     354                                        $this->assertSame( 4, (int) $query->found_posts );
     355                                } else {
     356                                        $this->assertSame( 3, (int) $query->found_posts );
     357                                }
     358                        }
     359                }
     360        }
     361
     362        /**
     363         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     364         */
     365        public function test_search_excludes_whitespace_only_passwords() {
     366                foreach ( array( 'forum', 'topic', 'reply' ) as $type ) {
     367                        $tree = $this->create_search_tree();
     368                        wp_update_post( array( 'ID' => $tree[ $type ], 'post_password' => '   ' ) );
     369                        $this->assertTrue( post_password_required( $tree[ $type ] ) );
     370
     371                        $query = new WP_Query( array( 'post_type' => bbp_get_post_types(), 'post__in' => array_values( $tree ), 's' => 'sentinel' ) );
     372                        $ids   = wp_list_pluck( $query->posts, 'ID' );
     373                        $types = array_keys( $tree );
     374                        $index = array_search( $type, $types, true );
     375
     376                        foreach ( array_slice( $tree, $index ) as $protected_id ) {
     377                                $this->assertNotContains( $protected_id, $ids );
     378                        }
     379                }
     380        }
     381
     382        /**
     383         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     384         */
     385        public function test_authorized_admin_search_does_not_require_a_status_parameter() {
     386                $protected   = $this->create_search_tree( 'forum-secret' );
     387                $old_request = $_REQUEST;
     388                $old_pagenow = isset( $GLOBALS['pagenow'] ) ? $GLOBALS['pagenow'] : null;
     389                $old_query   = $GLOBALS['wp_the_query'];
     390
     391                try {
     392                        unset( $_REQUEST['post_status'] );
     393                        set_current_screen( 'edit-topic' );
     394                        $GLOBALS['pagenow'] = 'edit.php';
     395
     396                        foreach ( array( bbp_get_moderator_role(), bbp_get_keymaster_role() ) as $role ) {
     397                                $user_id = $this->factory->user->create();
     398                                bbp_set_user_role( $user_id, $role );
     399                                $this->set_current_user( $user_id );
     400                                $query = new WP_Query();
     401                                $GLOBALS['wp_the_query'] = $query;
     402                                $query->query( array( 'post_type' => bbp_get_topic_post_type(), 's' => 'sentinel' ) );
     403                                $this->assertContains( $protected['topic'], wp_list_pluck( $query->posts, 'ID' ) );
     404
     405                                $secondary = new WP_Query( array( 'post_type' => bbp_get_topic_post_type(), 's' => 'sentinel' ) );
     406                                $this->assertEmpty( $secondary->posts );
     407
     408                                // Admin context alone does not exempt an unauthorized main query.
     409                                $this->set_current_user( 0 );
     410                                $query = new WP_Query();
     411                                $GLOBALS['wp_the_query'] = $query;
     412                                $query->query( array( 'post_type' => bbp_get_topic_post_type(), 's' => 'sentinel' ) );
     413                                $this->assertEmpty( $query->posts );
     414                        }
     415                } finally {
     416                        $_REQUEST = $old_request;
     417                        $GLOBALS['wp_the_query'] = $old_query;
     418                        $GLOBALS['pagenow'] = $old_pagenow;
     419                        set_current_screen( 'front' );
     420                }
     421        }
     422
     423        /**
     424         * @covers ::bbp_get_password_protected_forum_ids
     425         */
     426        public function test_password_forum_lookup_caches_empty_results_and_invalidates_with_posts() {
     427                global $wpdb;
     428
     429                $forum_id = $this->factory->forum->create();
     430                $this->assertSame( array(), bbp_get_password_protected_forum_ids() );
     431                $queries = $wpdb->num_queries;
     432                $this->assertSame( array(), bbp_get_password_protected_forum_ids() );
     433                $this->assertSame( $queries, $wpdb->num_queries );
     434
     435                wp_update_post( array( 'ID' => $forum_id, 'post_password' => 'forum-secret' ) );
     436                $this->assertContains( $forum_id, bbp_get_password_protected_forum_ids() );
     437                $queries = $wpdb->num_queries;
     438                $this->assertContains( $forum_id, bbp_get_password_protected_forum_ids() );
     439                $this->assertSame( $queries, $wpdb->num_queries );
     440
     441                $child_id = $this->factory->forum->create( array( 'post_parent' => $forum_id ) );
     442                $this->assertContains( $child_id, bbp_get_password_protected_forum_ids() );
     443
     444                wp_update_post( array( 'ID' => $child_id, 'post_parent' => 0 ) );
     445                $this->assertNotContains( $child_id, bbp_get_password_protected_forum_ids() );
     446
     447                wp_update_post( array( 'ID' => $forum_id, 'post_password' => '' ) );
     448                $this->assertSame( array(), bbp_get_password_protected_forum_ids() );
     449        }
     450
     451        /**
     452         * @group multisite
     453         * @covers ::bbp_get_password_protected_forum_ids
     454         */
     455        public function test_password_forum_lookup_is_scoped_to_the_current_site() {
     456                if ( ! is_multisite() ) {
     457                        $this->markTestSkipped( 'Requires multisite.' );
     458                }
     459
     460                $forum_id = $this->factory->forum->create( array( 'post_password' => 'forum-secret' ) );
     461                $site_id  = $this->factory->blog->create();
     462                $this->assertContains( $forum_id, bbp_get_password_protected_forum_ids() );
     463
     464                switch_to_blog( $site_id );
     465                try {
     466                        $this->assertSame( array(), bbp_get_password_protected_forum_ids() );
     467                        $other_id = $this->factory->forum->create( array( 'post_password' => 'other-secret' ) );
     468                        $this->assertSame( array( $other_id ), bbp_get_password_protected_forum_ids() );
     469                } finally {
     470                        restore_current_blog();
     471                }
     472
     473                $this->assertSame( array( $forum_id ), bbp_get_password_protected_forum_ids() );
     474        }
     475
     476        /**
     477         * @covers ::bbp_password_protected_search_where
     478         * @covers ::bbp_public_topic_replies_where
     479         */
     480        public function test_password_search_preserves_secondary_reply_status_opt_ins() {
     481                $public  = $this->create_search_tree();
     482                $private = $this->create_search_tree();
     483                $pending = $this->create_search_tree();
     484                wp_update_post( array( 'ID' => $private['topic'], 'post_status' => bbp_get_private_status_id() ) );
     485                wp_update_post( array( 'ID' => $pending['topic'], 'post_status' => bbp_get_pending_status_id() ) );
     486                $args = array(
     487                        'post_type'      => bbp_get_reply_post_type(),
     488                        'post__in'       => array( $public['reply'], $private['reply'], $pending['reply'] ),
     489                        's'              => 'sentinel',
     490                        'fields'         => 'ids',
     491                        'posts_per_page' => -1,
     492                );
     493
     494                foreach ( array( bbp_get_moderator_role(), bbp_get_keymaster_role() ) as $role ) {
     495                        $user_id = $this->factory->user->create();
     496                        bbp_set_user_role( $user_id, $role );
     497                        $this->set_current_user( $user_id );
     498
     499                        // Password protection must not opt a custom query into status filtering.
     500                        $query = new WP_Query( $args );
     501                        $this->assertContains( $public['reply'], $query->posts );
     502                        $this->assertContains( $private['reply'], $query->posts );
     503                        $this->assertContains( $pending['reply'], $query->posts );
     504
     505                        // Existing public-listing and private-search opt-ins retain their meaning.
     506                        $public_args = array_merge( $args, array( '_bbp_public_topic_replies' => true ) );
     507                        $query = new WP_Query( $public_args );
     508                        $this->assertContains( $public['reply'], $query->posts );
     509                        $this->assertNotContains( $private['reply'], $query->posts );
     510                        $this->assertNotContains( $pending['reply'], $query->posts );
     511
     512                        $public_args['_bbp_search_private_topic_replies'] = true;
     513                        $query = new WP_Query( $public_args );
     514                        $this->assertContains( $public['reply'], $query->posts );
     515                        $this->assertContains( $private['reply'], $query->posts );
     516                        $this->assertNotContains( $pending['reply'], $query->posts );
     517                }
     518        }
     519
     520        /**
     521         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     522         */
     523        public function test_ajax_password_exemption_preserves_existing_forum_filters() {
     524                $excluded  = $this->create_search_tree();
     525                $protected = $this->create_search_tree( 'forum-secret' );
     526                $user_id   = $this->factory->user->create();
     527                bbp_set_user_role( $user_id, bbp_get_moderator_role() );
     528                $this->set_current_user( $user_id );
     529
     530                // Model an integration's additional forum restrictions, not stock role caps.
     531                $filter = function( $value, $forum_ids, $type ) use ( $excluded ) {
     532                        if ( 'array' === $type ) {
     533                                return wp_parse_id_list( array_merge( $value, array( $excluded['forum'] ) ) );
     534                        }
     535                        if ( 'meta_query' === $type ) {
     536                                return array(
     537                                        'key'     => '_bbp_forum_id',
     538                                        'value'   => array( $excluded['forum'] ),
     539                                        'compare' => 'NOT IN',
     540                                        'type'    => 'NUMERIC',
     541                                );
     542                        }
     543
     544                        return $value;
     545                };
     546                add_filter( 'bbp_exclude_forum_ids', $filter, 10, 3 );
     547                add_filter( 'wp_doing_ajax', '__return_true' );
     548
     549                try {
     550                        $args = array(
     551                                'post_type'        => bbp_get_topic_post_type(),
     552                                'post__in'         => array( $excluded['topic'], $protected['topic'] ),
     553                                's'                => 'sentinel',
     554                                'fields'           => 'ids',
     555                                'suppress_filters' => true,
     556                        );
     557                        $query = new WP_Query( $args );
     558                        $this->assertNotContains( $excluded['topic'], $query->posts );
     559                        $this->assertContains( $protected['topic'], $query->posts );
     560                        $this->assertNotEmpty( $query->get( 'meta_query' ) );
     561
     562                        $this->set_current_user( 0 );
     563                        $query = new WP_Query( $args );
     564                        $this->assertEmpty( $query->posts );
     565
     566                        $this->set_current_user( $user_id );
     567                        remove_filter( 'wp_doing_ajax', '__return_true' );
     568                        $query = new WP_Query( $args );
     569                        $this->assertEmpty( $query->posts );
     570                } finally {
     571                        remove_filter( 'bbp_exclude_forum_ids', $filter, 10 );
     572                        remove_filter( 'wp_doing_ajax', '__return_true' );
     573                }
     574        }
     575
     576        /**
     577         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     578         */
     579        public function test_empty_search_excludes_password_content_but_archive_does_not() {
     580                $protected = $this->create_search_tree( 'forum-secret' );
     581                $public    = $this->create_search_tree();
     582                $args = array(
     583                        'post_type' => bbp_get_topic_post_type(),
     584                        'post__in'  => array( $protected['topic'], $public['topic'] ),
     585                        'fields'    => 'ids',
     586                        's'         => '',
     587                );
     588                $query = new WP_Query( $args );
     589                $this->assertTrue( $query->is_search() );
     590                $this->assertContains( $public['topic'], $query->posts );
     591                $this->assertNotContains( $protected['topic'], $query->posts );
     592
     593                unset( $args['s'] );
     594                $query = new WP_Query( $args );
     595                $this->assertContains( $public['topic'], $query->posts );
     596                $this->assertContains( $protected['topic'], $query->posts );
     597                $this->assertTrue( post_password_required( $protected['topic'] ) );
     598        }
     599
     600        /**
     601         * @covers ::bbp_clean_post_cache
     602         * @covers ::bbp_get_password_protected_forum_ids
     603         */
     604        public function test_password_cache_invalidates_with_missing_or_non_forum_parents() {
     605                $other_post = $this->factory->post->create();
     606                foreach ( array( 0, $other_post + 100000, $other_post ) as $parent_id ) {
     607                        $tree = $this->create_search_tree();
     608                        wp_update_post( array( 'ID' => $tree['forum'], 'post_parent' => $parent_id ) );
     609                        $this->assertSame( $parent_id, (int) get_post_field( 'post_parent', $tree['forum'] ) );
     610
     611                        // Warm the exclusion cache before an ordinary WordPress password update.
     612                        $this->assertNotContains( $tree['forum'], bbp_get_password_protected_forum_ids() );
     613                        wp_update_post( array( 'ID' => $tree['forum'], 'post_password' => 'forum-secret' ) );
     614                        $this->assertContains( $tree['forum'], bbp_get_password_protected_forum_ids(), 'Parent: ' . $parent_id );
     615
     616                        $args = array(
     617                                'post_type' => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ),
     618                                'post__in'  => array( $tree['topic'], $tree['reply'] ),
     619                                's'         => 'sentinel',
     620                                'fields'    => 'ids',
     621                        );
     622                        $query = new WP_Query( $args );
     623                        $this->assertEmpty( $query->posts );
     624
     625                        wp_update_post( array( 'ID' => $tree['forum'], 'post_password' => '' ) );
     626                        $this->assertNotContains( $tree['forum'], bbp_get_password_protected_forum_ids() );
     627                        $query = new WP_Query( $args );
     628                        $this->assertContains( $tree['topic'], $query->posts );
     629                        $this->assertContains( $tree['reply'], $query->posts );
     630                }
     631        }
     632
     633        /**
     634         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     635         */
     636        public function test_authorized_admin_forum_all_view_retains_non_public_statuses() {
     637                $old_request = $_REQUEST;
     638                $old_pagenow = isset( $GLOBALS['pagenow'] ) ? $GLOBALS['pagenow'] : null;
     639                $old_query   = $GLOBALS['wp_the_query'];
     640                $user_id     = $this->factory->user->create();
     641                bbp_set_user_role( $user_id, bbp_get_moderator_role() );
     642                $this->set_current_user( $user_id );
     643                $forum_ids = array();
     644
     645                foreach ( array( bbp_get_public_status_id(), bbp_get_private_status_id(), bbp_get_hidden_status_id(), 'draft', 'pending', 'future' ) as $status ) {
     646                        $forum_id  = $this->factory->forum->create( array( 'post_author' => $user_id ) );
     647                        $post_date = gmdate( 'Y-m-d H:i:s', time() + ( 'future' === $status ? DAY_IN_SECONDS : -DAY_IN_SECONDS ) );
     648                        wp_update_post(
     649                                array(
     650                                        'ID'            => $forum_id,
     651                                        'post_status'   => $status,
     652                                        'post_date'     => $post_date,
     653                                        'post_date_gmt' => $post_date,
     654                                )
     655                        );
     656                        $forum_ids[] = $forum_id;
     657                        $this->assertSame( $status, get_post_status( $forum_id ) );
     658                }
     659
     660                try {
     661                        unset( $_REQUEST['post_status'] );
     662                        set_current_screen( 'edit-forum' );
     663                        $GLOBALS['pagenow'] = 'edit.php';
     664                        $query = new WP_Query();
     665                        $GLOBALS['wp_the_query'] = $query;
     666                        $query->query( array( 'post_type' => bbp_get_forum_post_type(), 'post__in' => $forum_ids, 'fields' => 'ids', 'posts_per_page' => -1 ) );
     667                        foreach ( $forum_ids as $forum_id ) {
     668                                $this->assertContains( $forum_id, $query->posts );
     669                        }
     670                } finally {
     671                        $_REQUEST = $old_request;
     672                        $GLOBALS['wp_the_query'] = $old_query;
     673                        $GLOBALS['pagenow'] = $old_pagenow;
     674                        set_current_screen( 'front' );
     675                }
     676        }
     677
     678        /**
     679         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     680         */
     681        public function test_admin_all_view_preserves_custom_forum_exclusions() {
     682                $excluded    = $this->create_search_tree();
     683                $protected   = $this->create_search_tree( 'forum-secret' );
     684                $public      = $this->create_search_tree();
     685                $old_request = $_REQUEST;
     686                $old_pagenow = isset( $GLOBALS['pagenow'] ) ? $GLOBALS['pagenow'] : null;
     687                $old_query   = $GLOBALS['wp_the_query'];
     688                $user_id     = $this->factory->user->create();
     689                bbp_set_user_role( $user_id, bbp_get_moderator_role() );
     690                $this->set_current_user( $user_id );
     691
     692                $filter = function( $value, $forum_ids, $type ) use ( $excluded ) {
     693                        if ( 'array' === $type ) {
     694                                return wp_parse_id_list( array_merge( $value, array( $excluded['forum'] ) ) );
     695                        }
     696                        if ( 'meta_query' === $type ) {
     697                                return array(
     698                                        'key'     => '_bbp_forum_id',
     699                                        'value'   => array( $excluded['forum'] ),
     700                                        'compare' => 'NOT IN',
     701                                        'type'    => 'NUMERIC',
     702                                );
     703                        }
     704
     705                        return $value;
     706                };
     707                add_filter( 'bbp_exclude_forum_ids', $filter, 10, 3 );
     708
     709                try {
     710                        unset( $_REQUEST['post_status'] );
     711                        $GLOBALS['pagenow'] = 'edit.php';
     712
     713                        foreach ( array( 'forum', 'topic', 'reply' ) as $type ) {
     714                                set_current_screen( 'edit-' . get_post_type( $public[ $type ] ) );
     715                                foreach ( array( null, 'sentinel' ) as $term ) {
     716                                        $query = new WP_Query();
     717                                        $GLOBALS['wp_the_query'] = $query;
     718                                        $args = array(
     719                                                'post_type'      => get_post_type( $public[ $type ] ),
     720                                                'post__in'       => array( $excluded[ $type ], $protected[ $type ], $public[ $type ] ),
     721                                                'fields'         => 'ids',
     722                                                'posts_per_page' => -1,
     723                                        );
     724                                        if ( null !== $term ) {
     725                                                $args['s'] = $term;
     726                                        }
     727                                        $query->query( $args );
     728
     729                                        $this->assertNotContains( $excluded[ $type ], $query->posts );
     730                                        $this->assertContains( $public[ $type ], $query->posts );
     731                                        $this->assertContains( $protected[ $type ], $query->posts );
     732                                }
     733                        }
     734                } finally {
     735                        remove_filter( 'bbp_exclude_forum_ids', $filter, 10 );
     736                        $_REQUEST = $old_request;
     737                        $GLOBALS['pagenow'] = $old_pagenow;
     738                        $GLOBALS['wp_the_query'] = $old_query;
     739                        set_current_screen( 'front' );
     740                }
     741        }
     742
     743        /**
     744         * @covers ::bbp_pre_get_posts_normalize_forum_visibility
     745         * @covers ::_bbp_forum_visibility_where
     746         */
     747        public function test_password_search_preserves_meta_query_only_exclusion_filter() {
     748                $this->factory->forum->create( array( 'post_status' => bbp_get_hidden_status_id() ) );
     749                $excluded  = $this->create_search_tree();
     750                $protected = $this->create_search_tree( 'forum-secret' );
     751                $public    = $this->create_search_tree();
     752
     753                // Duplicate public metadata must not bypass the password-search clause.
     754                foreach ( array( 'topic', 'reply' ) as $type ) {
     755                        add_post_meta( $protected[ $type ], '_bbp_forum_id', $public['forum'] );
     756                }
     757
     758                $filter = function( $value, $forum_ids, $type ) use ( $excluded ) {
     759                        if ( 'meta_query' === $type ) {
     760                                $value['value'] = wp_parse_id_list( array_merge( $value['value'], array( $excluded['forum'] ) ) );
     761                        }
     762
     763                        return $value;
     764                };
     765                add_filter( 'bbp_exclude_forum_ids', $filter, 10, 3 );
     766                $this->set_current_user( 0 );
     767
     768                try {
     769                        foreach ( array( array(), array(
     770                                'relation' => 'OR',
     771                                array( 'key' => '_bbp_forum_id', 'value' => $public['forum'] ),
     772                                array( 'key' => '_bbp_forum_id', 'value' => $excluded['forum'] ),
     773                        ) ) as $meta_query ) {
     774                                $query = new WP_Query(
     775                                        array(
     776                                                'post_type'      => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ),
     777                                                'post__in'       => array_merge( array_values( $excluded ), array_values( $protected ), array_values( $public ) ),
     778                                                's'              => 'sentinel',
     779                                                'fields'         => 'ids',
     780                                                'posts_per_page' => -1,
     781                                                'meta_query'     => $meta_query,
     782                                        )
     783                                );
     784                                foreach ( array( 'topic', 'reply' ) as $type ) {
     785                                        $this->assertNotContains( $excluded[ $type ], $query->posts );
     786                                        $this->assertNotContains( $protected[ $type ], $query->posts );
     787                                        $this->assertContains( $public[ $type ], $query->posts );
     788                                }
     789                        }
     790                } finally {
     791                        remove_filter( 'bbp_exclude_forum_ids', $filter, 10 );
     792                }
     793        }
     794
     795        /**
     796         * Create matching forum, topic, and reply search controls.
     797         */
     798        private function create_search_tree( $password = '' ) {
     799                $forum_id = $this->factory->forum->create(
     800                        array( 'post_password' => $password, 'post_content' => 'Search ancestry sentinel 0' )
     801                );
     802                $topic_id = $this->factory->topic->create(
     803                        array( 'post_parent' => $forum_id, 'post_content' => 'Search ancestry sentinel 0', 'topic_meta' => array( 'forum_id' => $forum_id ) )
     804                );
     805                $reply_id = $this->factory->reply->create(
     806                        array( 'post_parent' => $topic_id, 'post_content' => 'Search ancestry sentinel 0', 'reply_meta' => array( 'forum_id' => $forum_id, 'topic_id' => $topic_id ) )
     807                );
     808
     809                return array( 'forum' => $forum_id, 'topic' => $topic_id, 'reply' => $reply_id );
     810        }
     811
    70812}
Note: See TracChangeset for help on using the changeset viewer.