BuddyPress Integration
Developer reference for the BuddyPress coexistence integration. This page is for plugin/theme developers extending or debugging the integration. End users should start with the BuddyPress integration guide.
What You Will Learn
- Where the integration lives and when it loads
- What state persists (group meta + options) and where
- Which BuddyPress and Jetonomy hooks the integration consumes
- How the activity broadcast and comment-to-reply bridge work, and how to extend them
- How loop protection, identity keying, and stale-pair handling are implemented
File Layout
The integration lives in a single class:
includes/integrations/class-buddypress.php
Loaded from includes/class-jetonomy.php only when the BuddyPress Groups component is active:
if ( function_exists( 'bp_is_active' ) && bp_is_active( 'groups' ) ) {
require_once JETONOMY_DIR . 'includes/integrations/class-buddypress.php';
new Integrations\BuddyPress();
}
The broadcast and comment-bridge methods additionally gate themselves on bp_is_active( 'activity' ) at runtime, so a BP install with Groups but not Activity stays fatal-free.
Persisted State
| Where | Key | Type | Purpose |
|---|---|---|---|
| BP group meta | jetonomy_space_id |
int | Points a group at its paired Jetonomy space. One value per group. |
| WP option | jetonomy_bp_broadcast |
'1' / '0' |
Toggle for JT topic → BP group activity broadcast. Defaults on. |
| WP option | jetonomy_bp_comment_bridge |
'1' / '0' |
Toggle for BP activity comment → JT reply bridge. Defaults on. |
| BP activity meta | jetonomy_post_id |
int | Tags a broadcast activity row with its originating Jetonomy post ID. The comment bridge reads this to decide which activity comments should round-trip as JT replies. |
Class constants
BuddyPress::META_KEY = 'jetonomy_space_id';
BuddyPress::OPT_BROADCAST = 'jetonomy_bp_broadcast';
BuddyPress::OPT_COMMENT_BRIDGE = 'jetonomy_bp_comment_bridge';
BuddyPress::ACTIVITY_META_POST = 'jetonomy_post_id';
BuddyPress::ACTIVITY_TYPE = 'jetonomy_topic';
Reading the pair
$space_id = (int) groups_get_groupmeta( $group_id, \Jetonomy\Integrations\BuddyPress::META_KEY, true );
Reverse lookup
$group_id = \Jetonomy\Integrations\BuddyPress::find_group_by_space( $space_id );
This runs a single meta-keyed query, no get_posts() loop.
BuddyPress Hooks Consumed
Group lifecycle
| Hook | Handler | Note |
|---|---|---|
groups_created_group |
on_group_created + save_group_forum_settings_on_create |
Reads the jt_bp_forum_action form field ('create', 'link_{id}', 'none'). Only creates a new space when explicitly requested. |
groups_delete_group |
on_group_deleted |
Unlinks the space. Space itself is preserved. |
groups_details_updated |
on_group_updated |
Syncs name, description, and visibility (public/private/hidden) to the paired space. |
Member sync
| Hook | Handler | Direction |
|---|---|---|
groups_join_group |
on_member_join |
BP → JT |
groups_leave_group |
on_member_leave |
BP → JT |
groups_remove_member |
on_member_leave |
BP → JT |
groups_ban_member |
on_member_leave |
BP → JT |
groups_unban_member |
on_member_join |
BP → JT |
groups_promote_member |
on_member_promote |
BP → JT (admin/mod) |
groups_demote_member |
on_member_demote |
BP → JT (back to member) |
Activity
| Hook | Handler | Note |
|---|---|---|
bp_register_activity_actions |
register_activity_type |
Registers the jetonomy_topic activity type with bp_activity_set_action so BP renders it alongside native types. |
bp_activity_comment_posted |
on_bp_activity_comment_posted |
Runs the comment-to-reply bridge when the parent activity carries the broadcast meta marker. |
bp_activity_allowed_tags |
filter_broadcast_allowed_tags |
Adds <br> and <p> to BP's kses allowlist so broadcast paragraphs survive save AND display. Both tags carry no attributes, so no XSS surface. |
Render
| Hook | Handler |
|---|---|
bp_setup_nav (priority 20) |
register_group_forum_tab + register_profile_forum_tab |
groups_custom_group_fields_editable |
render_group_forum_settings (the dropdown in Create + Manage > Details) |
groups_group_details_edited |
save_group_forum_settings |
bp_after_group_details_creation_step |
render_group_forum_settings (creation step) |
Jetonomy Hooks Consumed
| Hook | Handler | Surface |
|---|---|---|
jetonomy_before_content |
render_back_to_group_banner |
Renders the "← Group Name" link at the top of paired space / topic pages. |
jetonomy_sidebar_about_after_meta |
render_sidebar_group_link |
Renders the small tag in the sidebar About card linking back to the BP group. |
jetonomy_user_joined_space |
not directly hooked; member sync is BP → JT only (BP is the source of truth for group membership). | n/a |
jetonomy_after_create_post |
on_jt_post_created_for_bp |
Triggers the broadcast to the paired BP group activity stream. |
Activity Broadcast Flow
On jetonomy_after_create_post:
- If broadcast is disabled or no pair exists for the space, return.
- If the post is private (
is_private), return. - If the BP Activity component is not active, return.
- Build the activity body: excerpt converted to
<p>paragraphs with block-level tag boundaries preserved, plus a trailing "Shared from the forum · View discussion" attribution line. - Call
bp_activity_addwithcomponent=groups,type=jetonomy_topic,item_id=$group_id,secondary_item_id=$post_id, andhide_sitewideset when the group is not public. - Store the post ID in activity meta:
bp_activity_update_meta( $activity_id, 'jetonomy_post_id', $post_id ).
The bp_activity_allowed_tags filter that whitelists <br> and <p> is attached globally while broadcast is enabled. BP runs kses both on save and on display, so a per-call toggle would strip the tags when the activity is rendered later.
Comment-to-Reply Bridge Flow
On bp_activity_comment_posted( $comment_id, $r, $activity ):
- If the loop-guard flag is set, return. Prevents boomerang writes.
- If
bp_activity_get_meta( $activity->id, 'jetonomy_post_id' )is empty, the parent activity is not one of ours, return. - Load the Jetonomy post; if it is not published, return (the broadcast survives, but we do not create replies against draft/trashed topics).
- Build the reply content:
wp_kses_poston the comment HTML for display,wp_strip_all_tagsfor the plain version. - Create the reply via
Reply::createwith the same author as the BP commenter.
Edits and deletes on BP do NOT propagate. The JT thread is the durable record.
Loop Protection
A shared static $syncing flag stops a write on one side from triggering a boomerang write back. Every member-sync, broadcast, and bridge method flips it for the duration of the write:
self::$syncing = true;
// do the write that might fire hooks we listen to
self::$syncing = false;
Both the group-lifecycle handlers (on_group_created, on_group_updated) and the member-sync handlers read self::$syncing at entry.
Identity Keying
Everything joins on user_id. BP member profiles and Jetonomy user profiles share the same WP user ID, so username divergence is not a problem.
Stale Pair Handling
Every render hook resolves the paired entity lazily. If the paired space no longer exists when the forum tab is about to render, the tab callback returns early without emitting markup. The same pattern applies to the sidebar link and back-banner.
Extending
Three clean extension points:
- Disable member-leave propagation. Remove the
groups_leave_group,groups_remove_member, andgroups_ban_memberactions from the integration atinit + 30or later if you want the add-only semantics the FluentCommunity integration uses. - Custom activity rendering. Filter
bp_activity_action_before_saveor add a filter onbp_get_activity_actionto override howjetonomy_topicrows render without touching the integration. - Custom permission gate on forum tab. Filter
bp_is_user_in_group(or callgroups_is_user_memberdirectly) inside your own hook handler onregister_group_forum_tab(priority < 20) to restrict the Forum tab to certain roles.
Destructive or privacy-affecting extensions (forcing role sync one-way, propagating flags cross-surface, cascading deletes) belong in a Pro extension with explicit per-pair toggles, not as drop-in replacements for the free integration.