Embargoed Content
Embargoed Content prevents embargoed posts from being published, exposed via queries, or read by unauthorized users until the embargo lifts. It enforces embargoes through two layers: publishing guards (prevent invalid states) and display and capability guards (hide embargoed content from queries and capability checks).
Who can view embargoed content
Section titled “Who can view embargoed content”Access is decided in two stages. A bypass skips the guards entirely (for every embargoed post); a per-post exemption lifts only the one post that carries the granting meta. Anonymous visitors (logged out, user ID 0) are always denied — they can neither bypass nor be exempted.
Bypass — Helpers::can_user_bypass_guards() (skips both guard layers for all embargoed posts):
- User’s ID is in the site-wide allowed-users setting (
mediapress_embargoed_content_settings_allowed_users). - User holds a role in the site-wide allowed-roles setting (
mediapress_embargoed_content_settings_allowed_roles).
Per-post exemption — Helpers::is_post_viewable_by_user() (applies to the single embargoed post carrying the meta):
- Post author — the user authored the post.
- Allowed users — the user’s ID is in the post’s
allowed_usersmeta. - Allowed roles — the user holds a role in the post’s
allowed_rolesmeta. - Allow everyone — the post’s
allowed_alltoggle is enabled (grants any logged-in user; anonymous visitors remain embargoed).
Publishing guards apply unconditionally — nobody can publish an embargoed post regardless of capability, bypass, or exemption.
How an embargo is defined
Section titled “How an embargo is defined”- Date — the post is embargoed until its UTC lift date (
mediapress_embargoed_content_lift_date_utc) passes, after which the guards step aside. - Indefinitely — setting
mediapress_embargoed_content_is_indefinitelytotruerestricts the post with no expiry. It can never be published, scheduled, or shown until the flag is cleared.
Embargoed media indicator
Section titled “Embargoed media indicator”Embargoed attachments are flagged with a lock badge in the MediaPress media UI — on grid items, list items, and in the media info panel. This is a visual cue only; access is enforced by the display and capability guards.
The badge is registered by consuming two JavaScript extension points exposed by the core media package (documented under Filters/Actions → JavaScript → Filters in the Media docs):
mediaPress.media.mediaItem— HOC wrapping each grid/list item; readsmediaItemfrom props.mediaPress.media.mediaInfo— HOC wrapping the info panel; the record isn’t passed as a prop, so the callback re-fetches it withuseEntityRecordusing theattachmentIdprop (core-data caches it, so no extra request).
Both callbacks render the badge when the attachment’s mediapress_embargoed_content_is_embargoed field is true. The badge component and all positioning styles live in Embargoed Content — the Media package references the badge nowhere, keeping the dependency direction one-way (Embargoed Content → Media).
Media lifecycle
Section titled “Media lifecycle”When an embargoed post is trashed, restored, or permanently deleted, the package propagates that operation to all attachments whose post_parent is the affected post. This keeps media consistent with its parent and prevents orphaned files from lingering after the parent is removed.
| Action on embargoed post | Effect on attachments | WordPress action hook | Condition |
|---|---|---|---|
| Trashed | Attachments are moved to trash | trashed_post |
EMPTY_TRASH_DAYS and MEDIA_TRASH are set |
| Restored from trash | Trashed attachments are restored | untrashed_post |
EMPTY_TRASH_DAYS and MEDIA_TRASH are set |
| Permanently deleted | Attachments are permanently deleted | before_delete_post |
Always |
mediapress_embargoed_content_post_types
Section titled “mediapress_embargoed_content_post_types”Maps each post type slug to the embargo supports it declares.
Default
[ 'post' => [ 'self' ], 'page' => [ 'self' ],]Supported values for the supports array:
'self'— the post type carries its own embargo meta.'parent'— the post type inherits its embargo from itspost_parent(most-restrictive wins). Only the direct post parent is checked;parenttypes are not resolved recursively through otherparenttypes.
Usage
add_filter( 'mediapress_embargoed_content_post_types', function ( $post_types ) { $post_types['mediapress_custom_post_type'] = [ 'self' ];
return $post_types;});The revision and attachment post types are always appended as [ 'parent' ] after the filter runs and cannot be removed or overridden. They carry no embargo meta of their own at this time; their embargoed state is inherited entirely from their parent post.
Publishing guards
Section titled “Publishing guards”Publishing guards block attempts to publish embargoed posts or write contradictory embargo metadata. They aim to cover as much of the standard publishing surface as possible: block editor, quick edit, REST API, WP-CLI, programmatic calls, and cron.
flowchart TD
A[Publish / update attempt] --> B{Entry point}
B -->|"<code>wp_insert_post()</code> / <code>wp_update_post()</code>"| C["<code>wp_insert_post_data</code>"]
B -->|"REST POST / PUT <code>/wp/v2/posts</code>"| D["<code>rest_pre_insert_{post_type}</code>"]
B -->|"<code>wp_publish_post()</code> / cron"| E["<code>publish_{post_type}</code>"]
B -->|"<code>update_post_meta()</code> / <code>add_post_meta()</code>"| F["<code>update_post_metadata</code> /<br/><code>add_post_metadata</code>"]
C --> G{Resulting status<br/><code>publish</code> or <code>future</code>,<br/>and embargo conflicts?}
D --> H{Resulting status<br/><code>publish</code> or <code>future</code>,<br/>and embargo conflicts?}
E --> I{Currently embargoed<br/>and old status != <code>publish</code>?}
F --> J{Meta change creates<br/>contradictory state?}
G -- Yes --> K[Downgrade to previous status<br/>or <code>draft</code>]
G -- No --> L[Allow]
H -- Yes --> M[Return <code>WP_Error</code> — block request]
H -- No --> L
I -- Yes --> N[Revert to previous status<br/>via <code>wp_update_post</code>]
I -- No --> L
J -- Yes --> O[Return <code>false</code> — block meta write]
J -- No --> LDisplay and capability guards
Section titled “Display and capability guards”Display and capability guards hide embargoed content from queries and block unauthorized access via capability checks. Queries are filtered via pre_get_posts, while frontend singular views are protected via template_redirect.
flowchart TD
A[Read attempt] --> B{Entry point}
B -->|"<code>WP_Query</code> (frontend / admin)"| C["<code>pre_get_posts</code>"]
B -->|"<code>user_can</code> / <code>current_user_can</code>"| D["<code>map_meta_cap</code>"]
B -->|"Singular frontend view"| E["<code>template_redirect</code>"]
C --> F{Query has <code>post__in</code>?}
F -- Yes --> G["Diff embargoed IDs out of <code>post__in</code><br/>(<code>[0]</code> sentinel if empty)"]
F -- No --> H[Append embargoed IDs<br/>to <code>post__not_in</code>]
D --> I{cap in<br/><code>read_post</code> / <code>edit_post</code> /<br/><code>delete_post</code>?}
I -- No --> J[Pass through unchanged]
I -- Yes --> I2{Post embargoed?}
I2 -- No --> J
I2 -- Yes --> K{"Viewable by user?<br/>author, <code>allowed_users</code>,<br/><code>allowed_roles</code>, or <code>allowed_all</code><br/>anonymous always denied"}
K -- Yes --> L[Grant capability]
K -- No --> M["Return <code>['do_not_allow']</code>"]
E --> N{Embargoed and<br/>not viewable by user?}
N -- Yes --> O[404 + exit]
N -- No --> P[Allow]Coverage matrix
Section titled “Coverage matrix”| Entry point | Guard | Result when blocked |
|---|---|---|
wp_insert_post(), wp_update_post() |
wp_insert_post_data |
Status forced to draft / previous |
REST POST /wp/v2/posts[/{id}] |
rest_pre_insert_{post_type} |
400 WP_Error |
wp_publish_post(), WP-Cron auto-publish |
publish_{post_type} |
Reverted via wp_update_post |
update_post_meta() / add_post_meta() on embargoed keys |
update_post_metadata / add_post_metadata |
Returns false, meta unchanged |
WP_Query (frontend / admin) |
Display_Guards::exclude_embargoed_posts_from_queries_pre_get_posts |
Embargoed IDs excluded |
| Singular frontend view | Display_Guards::redirect_embargoed_posts |
404 + exit |
user_can( 'read_post' / 'edit_post' / 'delete_post' ) |
Access_Control::restrict_user_meta_capabilities |
do_not_allow |