Skip to content

Add default-private uploads for draft and unattached media#457

Closed
mikelittle wants to merge 2 commits intomasterfrom
issue-162-default-private-uploads
Closed

Add default-private uploads for draft and unattached media#457
mikelittle wants to merge 2 commits intomasterfrom
issue-162-default-private-uploads

Conversation

@mikelittle
Copy link
Contributor

@mikelittle mikelittle commented Mar 3, 2026

Summary

  • Attachments on unpublished (draft, pending, future) posts are automatically marked private via the s3_uploads_is_attachment_private filter
  • Unattached media (post_parent = 0) is also private by default
  • Publishing a post transitions its attachments to public-read; unpublishing reverts them to private
  • Manual per-attachment override via _s3_privacy post meta (values: private, public-read, or empty for default behaviour)
  • Private attachment URLs are presigned via S3 Uploads' existing presigning infrastructure
  • Privacy field added to the attachment edit UI

New files

  • inc/private_uploads/namespace.php — core logic: privacy filters, ACL management, post transition hooks, admin UI field
  • docs/private-uploads.md — user-facing documentation
  • tests/integration.suite.yml — Codeception integration suite config
  • tests/integration/private-uploads/PrivateUploadsTest.php — 12 unit-style tests for privacy logic
  • tests/integration/private-uploads/PrivateUploadsUrlChainTest.php — 6 tests for presigned URL generation chain
  • tests/integration/private-uploads/PrivateUploadsAccessTest.php — 7 E2E tests for HTTP access control

Running the tests

From the project root (inside the Docker PHP container):

composer dev-tools codecept run integration -c vendor/codeception.yml

Or from the host via docker exec:

docker exec product-dev-php bash -c 'cd /usr/src/app && vendor/bin/codecept run integration -c vendor/codeception.yml'

Test results and known limitations

Current results on local-server (VersityGW S3): 25 tests, 15 pass, 10 skip, 1 pre-existing failure.

Tests that skip on local-server

S3 Uploads not fully active (2 tests):
testPresignedUrlForPrivateAttachment and testFullChainPrivateAttachmentToTachyonUrl skip because S3 Uploads' plugins_loaded init does not fire during the WPLoader test bootstrap, so the wp_get_attachment_url presigning filter is not registered. These tests are expected to pass on a full WordPress environment where S3 Uploads is active.

Tachyon plugin not available (2 tests):
testTachyonUrlIncludesPresignParam and testTachyonUrlNoPresignForPublicUrl skip because the Tachyon plugin (tachyon_url() function) is not loaded during tests. Same root cause — plugin loading order in the test bootstrap.

S3 server does not support object ACLs (6 tests):
All E2E access tests in PrivateUploadsAccessTest skip with "S3 server does not support object ACLs (e.g. VersityGW)." VersityGW returns 501 Not Implemented for GetObjectAcl, so per-object access control cannot be verified locally. These tests are correctly written for AWS S3 and should pass on a real AWS stack.

Next steps

  • Test on a real AWS stack with S3 to verify ACL enforcement and presigned URL access
  • Investigate test bootstrap to get S3 Uploads and Tachyon plugin fully active during integration tests

Test plan

  • Verify the 14 passing tests continue to pass
  • Deploy to a staging environment with real S3 and verify the 10 skipped tests pass
  • Manual test: upload media to a draft post, confirm direct URL returns 403
  • Manual test: publish the post, confirm direct URL returns 200
  • Manual test: set manual _s3_privacy override, confirm it takes precedence

Ref: #162
Companion PR: humanmade/altis-local-server#899

🤖 Generated with Claude Code

Introduce default-private uploads for draft/unattached media. Attachments
on unpublished posts are marked private via the s3_uploads_is_attachment_private
filter, and publishing a post transitions its attachments to public-read.

Includes integration tests for privacy logic, presigned URL generation,
and E2E HTTP access verification. Tests gracefully skip when S3 Uploads
is not fully active or when the S3 server lacks object ACL support
(e.g. VersityGW).

Ref: #162

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce _s3_privacy='auto' meta to distinguish new uploads (managed
by the private uploads feature) from pre-existing images. Images without
the meta are always treated as public, preserving existing behaviour.

- set_acl_on_metadata_save() now sets _s3_privacy='auto' on new uploads
- is_attachment_private() returns false when _s3_privacy meta is absent
- handle_post_status_transition() only processes 'auto' attachments
- Admin UI uses 'auto' value instead of empty string

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mikelittle
Copy link
Contributor Author

Another implementation has superseded this PR.

@mikelittle mikelittle closed this Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant