Skip to:
Content

BuddyPress.org

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: indigetal's profile indigetal Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Activity Keywords: has-patch
Cc:

Description (last modified by emaralive)

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_conditions lets addons inject WHERE clauses
  • bp_activity_get_join_sql lets addons add JOINs
  • Data hydration uses SELECT * (in populate() and the cache-miss path of get()), so any column added to the table is automatically returned to calling code
  • bp_activity_before_save / bp_activity_after_save provide 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):

  1. Add public $privacy = 'public'; property
  2. In save() — add privacy to the INSERT/UPDATE column list alongside the existing columns (user_id, component, type, action, content, etc.)
  3. In get() — add 'privacy' to the $r defaults (default false, meaning no filtering). When set, add AND a.privacy IN (...) to the WHERE conditions.

REST API (BP_REST_Activity_Endpoint):

  1. In prepare_item_for_response() — expose privacy in the response object (paralleling how hide_sitewide is currently exposed as hidden)
  2. In prepare_item_for_database() — accept privacy as 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 privacy return all activities as before
  • hide_sitewide continues to work as-is (orthogonal — hide_sitewide controls directory listing, privacy controls 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 privacy value 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_conditions to 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 @emaralive
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

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

#2 @indigetal
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

  1. bp_activity_add() and helper write paths:
    • Adding privacy only in the model is not sufficient.
    • bp_activity_post_update() / group update helper paths can rebuild payloads, so privacy must be preserved through those branches too.
  1. Hydration consistency:
    • SELECT * exists, but populate() and list hydration paths still map/cast explicit fields.
    • privacy needs explicit hydration handling to avoid path-dependent object state.
  1. Query compatibility:
    • BP_Activity_Query validates allowed columns.
    • privacy must be added to BP_Activity_Query::$db_columns so filter_query-based callers can use it.
  1. 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.
  1. Upgrade sequencing:
    • Runtime SQL paths that reference privacy require 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.

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_update helper path vs direct bp_activity_add())
  • Safe / intentional - filter_query behavior now allowing column => 'privacy'
  • Safe - Schema edit in isolation (additive)

Scope remains intentionally limited

This is still a core infrastructure change only:

  • Add/store privacy column + index
  • Allow querying/filtering by privacy
  • Expose/accept privacy in 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 persists privacy
  • Verify BP_Activity_Activity::get() and filter_query return expected results with privacy
  • 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 privacy in populate() and both get_activity_data() paths (cached + uncached) to keep object shape consistent.
  • Add save-time filter hook:
    • bp_activity_privacy_before_save
  • Persist privacy in both SQL branches:
    • UPDATE includes privacy = %s
    • INSERT includes privacy column/value
  • Extend get():
    • new arg default: 'privacy' => false
    • optional WHERE constraint via a.privacy IN (...)
    • allow ordering by privacy in order_by whitelist.

Write-path parity (bp_activity_add + helpers):

  • bp_activity_add():
    • add parsed arg default 'privacy' => 'public'
    • assign $activity->privacy before save
    • docblock updated
  • bp_activity_post_update():
    • accept/pass privacy through helper payload to bp_activity_add()
    • docblock updated
  • Groups helper path:
    • groups_record_activity() default includes privacy
    • groups_post_update() accepts/passes privacy
    • docblock updated

Query allowlist:

  • Add 'privacy' to BP_Activity_Query::$db_columns so filter_query clauses using column => 'privacy' are validated and applied.

REST activity endpoint:

  • prepare_item_for_response() now includes privacy in response data.
  • prepare_item_for_database() accepts/propagates request privacy (with fallback to existing activity value on updates).
  • get_item_schema() adds privacy property (string) with contexts and sanitize callback (sanitize_text_field).

Verification performed:

  • php -l passed 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 correctly
    • filter_query with column => privacy works
    • REST create/update branches persist + return privacy
  • Note: local environment had downgraded DB-version state during testing; runtime behavior checks still passed after schema parity adjustments.
Note: See TracTickets for help on using tickets.