You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Display images attached to Fediverse comments - #3850
Add an option "Show images attached to replies" in the ActivityPub settings. It is off by default, because WordPress itself does not show images in comments and this changes how comments look.
With the option on, images attached to incoming Fediverse replies are shown below the comment text, including image-only replies and updated attachments. Up to three images per comment, the limit is filterable with activitypub_comment_image_limit. Audio and video are not included.
With the option off, nothing changes: no image blocks are stored and image-only replies are rejected like before.
I reuse the existing attachment parser, the activitypub/image block and the lazy Media cache. Comment images share the cache directory of their parent post, which is cleaned up when the post is deleted.
Image descriptions survive the comment sanitization. Only our own leaf emoji and image blocks are rendered, and image blocks only for received Fediverse comments.
Attachment URLs must be absolute HTTP(S) URLs on a public host.
One thing to keep in mind: on sites that auto-approve comments, remote images become visible without moderation once the option is on.
Testing instructions:
Enable "Show images attached to replies" under Settings → ActivityPub.
Reply to a federated post from Mastodon with an image and an image description. Check that the image and description show up, and that the image URL points to /uploads/activitypub/posts/{post-id}/ after caching.
Repeat with an image-only reply, and edit a reply to replace or remove its image.
Disable the option and send another reply with an image. The comment should show only the text.
Delete the parent post permanently and check that its cached image directory is removed.
Prevent remote image tracking URLs in ordinary WordPress comments
includes/class-comment.php:74
comment_text also handles ordinary WordPress comments, which do not pass through the remote-content sanitizer. A commenter can submit <!-- wp:activitypub/image {"url":"https://example.com/tracker.jpg","alt":""} /-->; KSES preserves the block attributes, and the new image callback reconstructs an image despite comment HTML restrictions. If caching is disabled or fails, readers load the remote tracking URL. Require a supplied comment with was_received() before rendering image blocks, while preserving emoji rendering. Update the rendering tests to use received-comment metadata and add an ordinary-comment submission regression test.
Preserve linked image attachments during comment creation and updates
includes/collection/class-interactions.php:528
Both comment creation here and updates at line 164 can omit an image attachment when the reply also contains its URL in text or a link. process_remote_images() skips URLs found anywhere in the content (includes/functions-media.php:171–174), even when sanitization has removed the linked image. Generate attachment blocks with process_remote_images( '', ... ) and append them to the sanitized content at both call sites. Add creation and update tests where the reply links to its attachment.
Addressed both "Previously missed" findings in 3457443. Image-block rendering now requires a supplied comment recognized by Comment::was_received(); emoji rendering is unchanged. Comment creation and updates append attachment blocks independently of sanitized text, so linked attachment URLs no longer suppress images. Regression coverage includes ordinary comment submission with unavailable caching, missing comment context, emoji compatibility, and plain URL / linked / linked-image attachments on creation and update. Full PHPUnit suite passes (3,464 tests, 25 skipped); PHPStan, PHPCS, and code review pass.
Prefix this call with \wp_delete_post() to follow the namespaced WordPress-function convention in AGENTS.md:30. The current call works through PHP's global fallback, so this is a consistency fix rather than a behavior change.
Fixed the previously missed namespace-convention finding in 9cd8dad: the media cache cleanup test now calls \wp_delete_post(). This is a test-only consistency change with no behavior change. The focused cleanup regression test and PHPCS both pass.
Every case here supplies attachment as a list, so the new single-attachment normalization in Remote_Posts::extract_attachments() is not covered. Existing single-object tests exercise the separate image fallback instead. Run these cases with a single associative array and a stdClass as well, and assert the URL, description, and type survive normalization. This would catch regressions that silently drop valid comment images.
Addressed the summary-only finding Add coverage for single-attachment normalization in e630293. The existing classifier test now runs each of seven media/type cases as a list, a single associative attachment, and a stdClass attachment (21 combinations). It asserts the complete normalized result, including URL, description, and type. No production changes or new helpers. Focused attachment/image-fallback tests pass: 7 tests / 27 assertions; PHPCS passes.
Preserve less-than signs in serialized image alt text
includes/functions-media.php:63
get_attribute() returns decoded text, but WordPress sanitizes block attribute strings as HTML when saving comments. A description such as width < height can therefore lose everything from the less-than sign onward before the image is rebuilt. HTML-encode alt before serialization, as with url; the renderer already escapes it with esc_attr(), which preserves existing entities. Update the block-generation expectations and add a comment save/render regression containing a literal <.
Whole-PR review and feedback assessment for d0892ee:
The earlier tests bypassed inbox validation and did not follow descriptions through comment persistence. That was too narrow. This review traced ingestion, attachment normalization, sanitization, block generation, saving/updating, rendering, and shared-cache cleanup, with an independent review of the full diff.
Missing-content validation: valid, fixed by reusing attachment extraction while retaining required IDs and previous validation vetoes. Both inbox routes now have regressions. I assess the impact as P2 functionality, not a demonstrated security vulnerability.
Alt-text preservation: valid, fixed by encoding decoded text before block serialization. The wider review also found literal entity spellings were lost earlier during attachment-to-HTML conversion; that step now encodes JSON-derived descriptions before the existing esc_attr() call. The regression starts with attachment.name and follows conversion, comment creation/update, and rendering. Existing HTML stripping remains intact.
Rendering boundaries: received-comment metadata is still required for image rendering; ordinary comments cannot reconstruct tracking images from block attributes. Only allowed leaf image/emoji blocks render. The nested-block regression now supplies a received comment, exercising the image-permitted branch instead of trivially rejecting every image.
KISS: no new helper classes, cache lifecycle, or generic media support. Removed duplicated image-fallback normalization; the common normalization pass already handles it.
Verification: full PHPUnit suite passed (3,474 tests, 8,519 assertions, 25 skipped); final PR-focused suite passed (607 tests, 1,594 assertions, one skipped). PHPStan and coding standards passed. A selective-run authentication failure was traced to a pre-existing test setup issue: an earlier page query leaked into REQUEST_URI and thus into the signed request-target. The signature test now supplies its actual inbox URI for dispatch and restores server state; production authentication is unchanged.
No additional blocker found in the whole-PR review. The description and testing instructions have been updated.
Reject explicitly invalid attachment types instead of defaulting to image
includes/collection/class-remote-posts.php:441
When mediaType is absent, an explicitly non-string type falls back to image. A reply with no content and attachment: {"type": 42, "url": "https://example.com/photo.jpg"} therefore passes the new image-only Create validation and gets an image block, even though its attachment type is invalid. Keep the image default for omitted types only; classify explicit invalid types as unsupported. Add regression cases for the extractor and Create validator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1852
Proposed changes:
activitypub_comment_image_limit. Audio and video are not included.activitypub/imageblock and the lazyMediacache. Comment images share the cache directory of their parent post, which is cleaned up when the post is deleted.One thing to keep in mind: on sites that auto-approve comments, remote images become visible without moderation once the option is on.
Testing instructions:
/uploads/activitypub/posts/{post-id}/after caching.