[buddypress-trac] [BuddyPress Trac] #9328: Enhancement Request: Add query filter hooks to `BP_Messages_Thread::get_current_threads_for_user()` (was: Feature Request: Add query filter hooks to `BP_Messages_Thread::get_current_threads_for_user()`)

buddypress-trac noreply at wordpress.org
Fri Feb 6 01:13:24 UTC 2026


#9328: Enhancement Request: Add query filter hooks to
`BP_Messages_Thread::get_current_threads_for_user()`
-------------------------+------------------------------
 Reporter:  indigetal    |       Owner:  (none)
     Type:  enhancement  |      Status:  new
 Priority:  normal       |   Milestone:  Awaiting Review
Component:  Messages     |     Version:
 Severity:  normal       |  Resolution:
 Keywords:               |
-------------------------+------------------------------
Changes (by emaralive):

 * version:  14.4.0 =>
 * component:  Core => Messages


Old description:

> #### The Gap
>
> BuddyPress's activity component has excellent query-level extensibility:
>
> ```php
> // From BP_Activity_Activity::get() — lines 666, 682
> $where_conditions = apply_filters( 'bp_activity_get_where_conditions',
> $where_conditions, $r, $select_sql, $from_sql, $join_sql );
> $join_sql = apply_filters( 'bp_activity_get_join_sql', $join_sql, $r,
> $select_sql, $from_sql, $where_sql );
> ```
>
> These filters allow any addon to inject WHERE clauses and JOINs into the
> main activity query without modifying core. BuddyBoss's moderation
> system, for example, hooks into `bp_activity_get_where_conditions` to
> hide suspended content — all from addon-level code.
>
> The messages component has **no equivalent**.
> `BP_Messages_Thread::get_current_threads_for_user()` builds its SQL in a
> `$sql` array (lines 790–794) and executes it directly:
>
> ```php
> $sql['select'] = 'SELECT m.thread_id, MAX(m.date_sent) AS date_sent';
> $sql['from']   = "FROM {$bp->messages->table_name_recipients} r INNER
> JOIN {$bp->messages->table_name_messages} m ON m.thread_id = r.thread_id
> {$meta_query_sql['join']}";
> $sql['where']  = "WHERE {$deleted_sql} {$user_id_sql} {$sender_sql}
> {$type_sql} {$search_sql} {$meta_query_sql['where']}";
> $sql['misc']   = "GROUP BY m.thread_id ORDER BY date_sent DESC
> {$pag_sql}";
>
> $thread_ids = $wpdb->get_results( implode( ' ', $sql ) );
> ```
>
> The only filter (`bp_messages_thread_current_threads`) fires AFTER the
> query on the fully-constructed result set (line 841) — meaning addons
> must filter in PHP, not SQL. This is:
>
> 1. **A performance problem** — filtering in PHP means fetching rows from
> the database that will be discarded, and constructing full
> `BP_Messages_Thread` objects for threads that are then thrown away. This
> gets worse as thread count grows.
> 2. **An extensibility gap** — addons that need custom thread filtering
> (archiving, priority inbox, moderation, read/unread management) have no
> clean way to modify the query
> 3. **Inconsistent with the activity API** — activity has comprehensive
> pre-query filters; messages doesn't
>
> #### Proposed Change
>
> Add filter hooks to `get_current_threads_for_user()` before query
> execution, following the pattern established by
> `BP_Activity_Activity::get()`:
>
> ```php
> // After building $sql array (around line 794):
>
> /**
>  * Filters the WHERE SQL for the current threads query.
>  *
>  * @since {next_version}
>  *
>  * @param string $where_sql  Current WHERE clause.
>  * @param array  $r          Parsed query arguments.
>  * @param string $select_sql Current SELECT clause.
>  * @param string $from_sql   Current FROM clause (includes JOINs).
>  */
> $sql['where'] = apply_filters( 'bp_messages_thread_get_where_conditions',
> $sql['where'], $r, $sql['select'], $sql['from'] );
>
> /**
>  * Filters the FROM/JOIN SQL for the current threads query.
>  *
>  * @since {next_version}
>  *
>  * @param string $from_sql   Current FROM clause (includes JOINs).
>  * @param array  $r          Parsed query arguments.
>  * @param string $select_sql Current SELECT clause.
>  * @param string $where_sql  Current WHERE clause.
>  */
> $sql['from'] = apply_filters( 'bp_messages_thread_get_join_sql',
> $sql['from'], $r, $sql['select'], $sql['where'] );
> ```
>
> **Note on parameter style:** In `BP_Activity_Activity::get()`, the WHERE
> conditions are passed as an array (joined to a string after the filter),
> whereas in the messages method they are already a string. This proposal
> preserves the existing messages code structure and filters the string
> directly, which is the minimal-change approach. If BP maintainers prefer,
> the messages SQL could also be refactored to use an array of conditions
> for parity with activity — but filtering the string is sufficient for all
> the use cases described below.
>
> #### Scope
>
> - ~12 lines added to one file (`class-bp-messages-thread.php`)
> - Zero behavioral change — the filters pass through existing values by
> default
> - Follows the pattern established by the activity component's existing
> filters
> - The existing `bp_messages_thread_current_threads` post-query filter
> continues to work as-is
>
> #### What This Enables for Addons
>
> With these hooks, addon plugins can implement:
>
> - **Message archiving** — Add `is_hidden` column to
> `bp_messages_recipients`, filter `WHERE` to exclude `r.is_hidden = 1`
> - **Message soft-delete** — Add `is_deleted` column to
> `bp_messages_messages`, filter `FROM`/`JOIN` to exclude deleted messages
> from thread listing
> - **Content moderation** — Inject suspend/block conditions into thread
> queries
> - **Priority inbox** — Filter by custom meta or thread properties
> - **Group messaging** — Filter threads by group membership context
>
> None of these features require core changes — they just need the ability
> to modify the query, which activity already provides but messages
> doesn't.
>
> #### Prior Art
>
> BuddyBoss Platform needed exactly these extension points. Because the
> hooks don't exist in BuddyPress core, BuddyBoss had to:
>
> 1. **Restructure `get_current_threads_for_user()`** entirely — they
> replaced it with a delegating call to a new `get_threads_for_user()`
> method containing ~400 lines of rewritten SQL logic
> 2. **Add `is_hidden` and `is_deleted` columns** directly into the
> restructured query conditions
> 3. **Add their own filter hooks** —
> `bp_messages_recipient_get_where_conditions` and
> `bp_messages_recipient_get_join_sql` — with signatures functionally
> identical to what this proposal suggests
>
> The fact that BuddyBoss independently arrived at the same solution (pre-
> query filter hooks on the messages SQL) validates the need. If these
> hooks had existed in BuddyPress core, BuddyBoss could have achieved the
> same result from addon-level code without forking the class.
>
> I'm happy to submit a PR for this change.

New description:

 == The Gap

 !BuddyPress's activity component has excellent query-level extensibility:


 From `BP_Activity_Activity::get()` — lines 666, 682
 {{{
 $where_conditions = apply_filters( 'bp_activity_get_where_conditions',
 $where_conditions, $r, $select_sql, $from_sql, $join_sql );
 $join_sql = apply_filters( 'bp_activity_get_join_sql', $join_sql, $r,
 $select_sql, $from_sql, $where_sql );
 }}}

 These filters allow any addon to inject WHERE clauses and JOINs into the
 main activity query without modifying core. BuddyBoss's moderation system,
 for example, hooks into `bp_activity_get_where_conditions` to hide
 suspended content — all from addon-level code.

 The messages component has **no equivalent**.
 `BP_Messages_Thread::get_current_threads_for_user()` builds its SQL in a
 `$sql` array (lines 790–794) and executes it directly:

 {{{
 $sql['select'] = 'SELECT m.thread_id, MAX(m.date_sent) AS date_sent';
 $sql['from']   = "FROM {$bp->messages->table_name_recipients} r INNER JOIN
 {$bp->messages->table_name_messages} m ON m.thread_id = r.thread_id
 {$meta_query_sql['join']}";
 $sql['where']  = "WHERE {$deleted_sql} {$user_id_sql} {$sender_sql}
 {$type_sql} {$search_sql} {$meta_query_sql['where']}";
 $sql['misc']   = "GROUP BY m.thread_id ORDER BY date_sent DESC
 {$pag_sql}";

 $thread_ids = $wpdb->get_results( implode( ' ', $sql ) );
 }}}

 The only filter (`bp_messages_thread_current_threads`) fires AFTER the
 query on the fully-constructed result set (line 841) — meaning addons must
 filter in PHP, not SQL. This is:

 1. **A performance problem** — filtering in PHP means fetching rows from
 the database that will be discarded, and constructing full
 `BP_Messages_Thread` objects for threads that are then thrown away. This
 gets worse as thread count grows.
 2. **An extensibility gap** — addons that need custom thread filtering
 (archiving, priority inbox, moderation, read/unread management) have no
 clean way to modify the query
 3. **Inconsistent with the activity API** — activity has comprehensive
 pre-query filters; messages doesn't

 == Proposed Change

 Add filter hooks to `get_current_threads_for_user()` before query
 execution, following the pattern established by
 `BP_Activity_Activity::get()`:


 After building `$sql` array (around line 794):

 {{{
 /**
  * Filters the WHERE SQL for the current threads query.
  *
  * @since {next_version}
  *
  * @param string $where_sql  Current WHERE clause.
  * @param array  $r          Parsed query arguments.
  * @param string $select_sql Current SELECT clause.
  * @param string $from_sql   Current FROM clause (includes JOINs).
  */
 $sql['where'] = apply_filters( 'bp_messages_thread_get_where_conditions',
 $sql['where'], $r, $sql['select'], $sql['from'] );

 /**
  * Filters the FROM/JOIN SQL for the current threads query.
  *
  * @since {next_version}
  *
  * @param string $from_sql   Current FROM clause (includes JOINs).
  * @param array  $r          Parsed query arguments.
  * @param string $select_sql Current SELECT clause.
  * @param string $where_sql  Current WHERE clause.
  */
 $sql['from'] = apply_filters( 'bp_messages_thread_get_join_sql',
 $sql['from'], $r, $sql['select'], $sql['where'] );
 }}}

 **Note on parameter style:** In `BP_Activity_Activity::get()`, the WHERE
 conditions are passed as an array (joined to a string after the filter),
 whereas in the messages method they are already a string. This proposal
 preserves the existing messages code structure and filters the string
 directly, which is the minimal-change approach. If BP maintainers prefer,
 the messages SQL could also be refactored to use an array of conditions
 for parity with activity — but filtering the string is sufficient for all
 the use cases described below.

 == Scope

 * ~12 lines added to one file (`class-bp-messages-thread.php`)
 * Zero behavioral change — the filters pass through existing values by
 default
 * Follows the pattern established by the activity component's existing
 filters
 * The existing `bp_messages_thread_current_threads` post-query filter
 continues to work as-is

 == What This Enables for Addons

 With these hooks, addon plugins can implement:

 - **Message archiving** — Add `is_hidden` column to
 `bp_messages_recipients`, filter `WHERE` to exclude `r.is_hidden = 1`
 - **Message soft-delete** — Add `is_deleted` column to
 `bp_messages_messages`, filter `FROM`/`JOIN` to exclude deleted messages
 from thread listing
 - **Content moderation** — Inject suspend/block conditions into thread
 queries
 - **Priority inbox** — Filter by custom meta or thread properties
 - **Group messaging** — Filter threads by group membership context

 None of these features require core changes — they just need the ability
 to modify the query, which activity already provides but messages doesn't.

 == Prior Art

 BuddyBoss Platform needed exactly these extension points. Because the
 hooks don't exist in !BuddyPress core, BuddyBoss had to:

 1. **Restructure `get_current_threads_for_user()`** entirely — they
 replaced it with a delegating call to a new `get_threads_for_user()`
 method containing ~400 lines of rewritten SQL logic
 2. **Add `is_hidden` and `is_deleted` columns** directly into the
 restructured query conditions
 3. **Add their own filter hooks** —
 `bp_messages_recipient_get_where_conditions` and
 `bp_messages_recipient_get_join_sql` — with signatures functionally
 identical to what this proposal suggests

 The fact that BuddyBoss independently arrived at the same solution (pre-
 query filter hooks on the messages SQL) validates the need. If these hooks
 had existed in !BuddyPress core, BuddyBoss could have achieved the same
 result from addon-level code without forking the class.

 I'm happy to submit a PR for this change.

--

-- 
Ticket URL: <https://buddypress.trac.wordpress.org/ticket/9328#comment:1>
BuddyPress Trac <http://buddypress.org/>
BuddyPress Trac


More information about the buddypress-trac mailing list