Opened 5 weeks ago
Last modified 3 weeks ago
#9327 new enhancement
Enhancement Request: Add `privacy` column to `bp_activity` for per-item visibility control
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Awaiting Review | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Activity | Keywords: | has-patch |
| Cc: |
Description (last modified by )
The Need
BuddyPress activities have a single visibility control: hide_sitewide (0 or 1). This provides binary visibility — shown everywhere, or hidden from sitewide feeds. There's no way to express:
- "Only me" — private activity visible only to the author
- "Friends only" — visible only to the author's friends
- "Logged-in users" — hidden from anonymous visitors
- "Group members only" — visible only within the group context where it was posted
Per-item privacy is a foundational social platform feature. Facebook has had it since 2009. LinkedIn, Twitter/X (with protected tweets), Instagram, and every major social network support some form of per-post audience control. It's as fundamental to a social platform as hide_sitewide is — just multi-valued instead of binary.
This is not a request for any specific addon's needs. It's a request for the same kind of infrastructure that hide_sitewide provides — a first-class column that any addon can use to implement privacy-aware features.
Current State
BuddyPress 14.4.0's activity query system is well-designed for extensibility:
bp_activity_get_where_conditionslets addons inject WHERE clausesbp_activity_get_join_sqllets addons add JOINs- Data hydration uses
SELECT *(inpopulate()and the cache-miss path ofget()), so any column added to the table is automatically returned to calling code bp_activity_before_save/bp_activity_after_saveprovide save-time hooks
An addon can add a privacy column via dbDelta and filter queries via bp_activity_get_where_conditions. However, the save() method has a hardcoded column list in its INSERT/UPDATE SQL (in BP_Activity_Activity::save()), meaning an addon must do a separate $wpdb->update() after every save — a double-write on every activity creation. This works but is suboptimal, and the column's general-purpose nature (used by media addons, document addons, moderation addons, group addons — not just one feature) argues for core inclusion.
Proposed Change
Schema: Add one column + index to bp_activity in bp_core_install_activity_streams() (in bp-core-admin-schema.php). dbDelta() handles adding the column on upgrade automatically:
sql
-- Added after the existing `is_spam` column in the CREATE TABLE definition: privacy varchar(75) NOT NULL DEFAULT 'public', -- Added to the KEY list: KEY privacy (privacy)
Model class (BP_Activity_Activity):
- Add
public $privacy = 'public';property - In
save()— addprivacyto the INSERT/UPDATE column list alongside the existing columns (user_id, component, type, action, content, etc.) - In
get()— add'privacy'to the$rdefaults (defaultfalse, meaning no filtering). When set, addAND a.privacy IN (...)to the WHERE conditions.
REST API (BP_REST_Activity_Endpoint):
- In
prepare_item_for_response()— exposeprivacyin the response object (paralleling howhide_sitewideis currently exposed ashidden) - In
prepare_item_for_database()— acceptprivacyas a writable field on create/update
Migration: dbDelta() runs on every upgrade via bp_core_install(), so modifying the CREATE TABLE definition is sufficient for the schema change. A DB version bump in bp-core-update.php tracks the upgrade.
Scope
- ~40 lines of changes across 4 files (
bp-core-admin-schema.php,class-bp-activity-activity.php,class-bp-rest-activity-endpoint.php,bp-core-update.php) - Default value
'public'— 100% backward compatible - Existing queries that don't pass
privacyreturn all activities as before hide_sitewidecontinues to work as-is (orthogonal —hide_sitewidecontrols directory listing,privacycontrols per-item access)
What Core Provides vs. What Addons Handle
To be explicit about scope: this proposal adds storage and query filtering only. Core would:
- Store the
privacyvalue on each activity row - Allow
get()callers to filter by privacy value(s) - Expose/accept the field through the REST API
Core would not need to:
- Define a fixed set of allowed values (addons register the values they need — e.g.,
'onlyme','friends','loggedin') - Enforce access control based on privacy values (addons hook into
bp_activity_get_where_conditionsto inject viewer-aware WHERE clauses, exactly as they would today for any custom filtering) - Provide UI for selecting privacy (addon/theme territory)
This mirrors how hide_sitewide works today — core stores the flag and filters on it; the decision of *when* to set it is made by callers (groups component, plugins, etc.).
Prior Art
BuddyBoss Platform (a BuddyPress-derived platform) implemented this exact column. Their bp_activity schema includes privacy varchar(75) NOT NULL DEFAULT 'public', and their media, document, video, and moderation subsystems all depend on it for privacy filtering. The column is referenced in their activity save/query paths, REST API, and template layer — making it one of the most cross-cutting additions they made to the activity table. The fact that an independent platform needed this to build standard social features demonstrates both the demand and the general-purpose nature of the column.
I'm happy to submit a PR for this change.
Change History (3)
#1
@
5 weeks ago
- Component changed from Core to Activity
- Description modified (diff)
- Summary changed from Feature Request: Add `privacy` column to `bp_activity` for per-item visibility control to Enhancement Request: Add `privacy` column to `bp_activity` for per-item visibility control
- Version 14.4.0 deleted
#2
@
3 weeks ago
Scope refinement before opening PR
Thanks for considering this enhancement. Before opening the PR, I re-validated the implementation surface against current BuddyPress master and ran a targeted risk analysis. The proposal direction is unchanged (core storage + query support only), but the implementation scope has been refined to avoid rollout and write-path inconsistencies.
What changed since the original proposal
The original proposal described ~4 files. The updated implementation scope is broader to keep behavior consistent across existing activity write/read paths:
src/bp-core/admin/bp-core-admin-schema.php src/bp-activity/classes/class-bp-activity-activity.php src/bp-activity/bp-activity-functions.php src/bp-groups/bp-groups-activity.php src/bp-activity/classes/class-bp-activity-query.php src/bp-activity/classes/class-bp-activity-rest-controller.php src/class-buddypress.php (possibly src/bp-core/bp-core-update.php only if needed by release/versioning expectations)
Why the scope expanded
bp_activity_add()and helper write paths:- Adding
privacyonly in the model is not sufficient. bp_activity_post_update()/ group update helper paths can rebuild payloads, soprivacymust be preserved through those branches too.
- Adding
- Hydration consistency:
SELECT *exists, butpopulate()and list hydration paths still map/cast explicit fields.privacyneeds explicit hydration handling to avoid path-dependent object state.
- Query compatibility:
BP_Activity_Queryvalidates allowed columns.privacymust be added toBP_Activity_Query::$db_columnssofilter_query-based callers can use it.
- REST correctness:
- Current class is
BP_Activity_REST_Controller(file:class-bp-activity-rest-controller.php). - Response, schema, and DB-prep paths need synchronized updates for read/write parity.
- Current class is
- Upgrade sequencing:
- Runtime SQL paths that reference
privacyrequire schema availability on upgraded sites. - DB version bump is part of safe rollout sequencing so upgrade/install paths apply the column before these code paths are relied on.
- Runtime SQL paths that reference
Risk analysis summary
I classified identified risks as follows:
- Mitigable - Upgrade sequencing (schema + DB version + runtime SQL usage)
- Mitigable - Shared model fanout (
populate(),save(),get(), list hydration) - Mitigable - REST contract expansion (new writable/readable field)
- Mitigable (high priority) - REST write-branch divergence (
activity_updatehelper path vs directbp_activity_add()) - Safe / intentional -
filter_querybehavior now allowingcolumn => 'privacy' - Safe - Schema edit in isolation (additive)
Scope remains intentionally limited
This is still a core infrastructure change only:
- Add/store
privacycolumn + index - Allow querying/filtering by
privacy - Expose/accept
privacyin REST - Keep defaults backward compatible (
'public')
This does not introduce:
- Core UI for privacy selection
- Core enforcement policy for specific privacy values
- A fixed enum of privacy values
Planned verification before PR submission
- Verify schema upgrade path on existing installs (DB version gate + installer path)
- Verify both REST create/update write branches persist
privacy - Verify direct
bp_activity_add()path persistsprivacy - Verify
BP_Activity_Activity::get()andfilter_queryreturn expected results withprivacy - Run focused fail-fast checks/linting on touched files
I’ll open the PR once these refinements are implemented and validated, and link it back to this ticket.
This ticket was mentioned in PR #438 on buddypress/buddypress by indigetal.
3 weeks ago
#3
- Keywords has-patch added
Add privacy as a core activity field across schema, model persistence, query APIs, helper write paths, and REST serialization/input handling, so addons can rely on a single native storage/query contract instead of post-save double writes.
Trac ticket: https://buddypress.trac.wordpress.org/ticket/9327
Schema + upgrade gating:
bp_core_install_activity_streams()now defines:privacy varchar(75) NOT NULL DEFAULT 'public'KEY privacy (privacy)
- Bump BuddyPress DB version in
class-buddypress.php(13906->13907) so schema upgrades run through normal update flow.
Activity model (BP_Activity_Activity):
- Add class property default:
privacy = 'public'. - Hydrate
privacyinpopulate()and bothget_activity_data()paths (cached + uncached) to keep object shape consistent. - Add save-time filter hook:
bp_activity_privacy_before_save
- Persist
privacyin both SQL branches:- UPDATE includes
privacy = %s - INSERT includes
privacycolumn/value
- UPDATE includes
- Extend
get():- new arg default:
'privacy' => false - optional WHERE constraint via
a.privacy IN (...) - allow ordering by
privacyinorder_bywhitelist.
- new arg default:
Write-path parity (bp_activity_add + helpers):
bp_activity_add():- add parsed arg default
'privacy' => 'public' - assign
$activity->privacybefore save - docblock updated
- add parsed arg default
bp_activity_post_update():- accept/pass
privacythrough helper payload tobp_activity_add() - docblock updated
- accept/pass
- Groups helper path:
groups_record_activity()default includesprivacygroups_post_update()accepts/passesprivacy- docblock updated
Query allowlist:
- Add
'privacy'toBP_Activity_Query::$db_columnssofilter_queryclauses usingcolumn => 'privacy'are validated and applied.
REST activity endpoint:
prepare_item_for_response()now includesprivacyin response data.prepare_item_for_database()accepts/propagates requestprivacy(with fallback to existing activity value on updates).get_item_schema()addsprivacyproperty (string) with contexts and sanitize callback (sanitize_text_field).
Verification performed:
php -lpassed for all touched PHP files.- Local site runtime checks confirmed:
- direct
bp_activity_add()persists privacy - helper branches (
bp_activity_post_update, groups update flow) persist privacy BP_Activity_Activity::get( [ 'privacy' => ... ] )filters correctlyfilter_querywithcolumn => privacyworks- REST create/update branches persist + return
privacy
- direct
- Note: local environment had downgraded DB-version state during testing; runtime behavior checks still passed after schema parity adjustments.
Edited the description for readability. Slightly modified the Summary.
For reference, this started as a support forum topic, see Add `privacy` column to `bp_activity` for per-item visibility control