Skip to content

Conversation

@obenland
Copy link
Member

@obenland obenland commented Jan 7, 2025

Fixes #970.

See https://mas.to/@obietester/115741669531612641
See https://obietester.blog/2025/12/02/battery-powered-tools-vs-air-compressor-tools-a-comprehensive-comparison/#comment-1946

Proposed changes:

  • Adds callback function that replaces custom emoji strings with their image representations when available.
  • Adds unit test to cover the new function.
  • Runs author name and comment content through the new callback on insert and update.

Other information:

  • Have you written new tests for your changes, if applicable?

Testing instructions:

  • Go to Mastodon, for example, and reply to a federated test post with a custom emoji. :yikes:, :AngeryCat:, etc.
  • Go to the comment list in wp-admin and make sure the emoji render as images.

@obenland obenland requested review from a team and jeherve January 7, 2025 17:32
@obenland obenland self-assigned this Jan 7, 2025
@pfefferle
Copy link
Member

I think before allowing images in comments, we have to think about/implement a proper blocking and moderation tooling (even more if it is about side-loading images): https://www.theverge.com/2023/7/24/23806093/mastodon-csam-study-decentralized-network

@obenland
Copy link
Member Author

I just pushed an update that moves the replacement into a filter that runs after comment_content and the author have been sanitized. That way, only custom emoji images will be added to the content.

@jeherve
Copy link
Member

jeherve commented Jan 13, 2025

Heads up: This PR adds img tags to the list of allowed HTML tags for Interactions!

Would sideloading the image onto the site with media_sideload_image be helpful here?

  • It would ensure the images remain available in the future.
  • It would avoid image hotlinking, which can be frowned upon.
  • It would add some validation to the image data before to simply display it on the site.

Of course, we'd need to be okay adding potentially a lot of new media to a site once that's enabled. I'm not sure that's okay.

@obenland
Copy link
Member Author

It would avoid image hotlinking, which can be frowned upon.

With custom emoji being shared tags, is that not kind of expected?

@jeherve
Copy link
Member

jeherve commented Jan 14, 2025

With custom emoji being shared tags, is that not kind of expected?

I'm honestly not sure what the common practice is with other Fediverse software. From what I can tell, on Mastodon and on GoToSocial the images from remote instances seem to be cached on the local server:

GoToSocial:

image

Mastodon

image

@obenland obenland marked this pull request as draft February 19, 2025 23:26
@Jiwoon-Kim

This comment was marked as outdated.

@obenland
Copy link
Member Author

obenland commented Jun 9, 2025

@Jiwoon-Kim I appreciate your input and feedback, but comments and issues of this length are not helpful. They generally lack a specific ask or suggestion and are incredibly hard to read. Going forward, please keep comments/issues concise and actionable, like I asked for previously.

@Jiwoon-Kim

This comment was marked as off-topic.

@obenland
Copy link
Member Author

I do understand, maybe part of your workflow could be to instruct your LLM to use natural language and distill its response to a sentence or two?

@Jiwoon-Kim

This comment was marked as outdated.

@Jiwoon-Kim

This comment was marked as outdated.

@Jiwoon-Kim
Copy link
Contributor

Jiwoon-Kim commented Sep 30, 2025

@obenland
Copy link
Member Author

obenland commented Oct 2, 2025

Todo:

  • Store emoji files for comment authors on comment save.
  • Update replacement with using attachment

@Jiwoon-Kim
Copy link
Contributor

Jiwoon-Kim commented Oct 25, 2025

It’s possible to display custom emojis from remote instances by extending the Smiley feature.
See: https://wordpress.org/documentation/article/what-are-smilies/#where-are-my-smiley-images-kept

#2371

Replace multiple database queries with single batched query and fast lookup arrays. This eliminates N+1 query problem where each emoji required separate database calls.

Changes:
- Single get_posts() query instead of one per emoji
- Fast O(1) lookup arrays instead of O(n) loops
- Consolidated meta query building into single loop
- Removed redundant array collections
Instead of storing HTML markup in the comment_author field, now stores
the shortcode (e.g., ":emoji:") and replaces it on display. This makes
the implementation more future-proof:

- If the plugin is disabled, shortcodes display gracefully
- Emoji images can be updated dynamically
- Database stays clean without HTML markup

Implementation:
- Store ActivityPub emoji tag data in comment meta
- Use get_comment_author filter to replace shortcodes with img tags
- Use comment_author filter to selectively unescape only emoji images
- Maintain WordPress security by only unescaping known emoji markup
- Add import_emoji() method to Attachments class for downloading and caching emoji images
- Store emoji files organized by source domain: /activitypub/emoji/{domain}/
- Simplify Emoji class from 225 lines to 89 lines by delegating storage to Attachments
- Remove WordPress attachment posts for emoji, use lightweight file storage instead
- Add img tag to allowed HTML in comments for emoji support
- Fix comment_author to store shortcodes (replaced at display time, not save time)
- Add prepare_comment_data() for content emoji replacement at insert-time
- Add prepare_actor_meta() for storing emoji data on remote actors
- Add replace_from_json() for display-time emoji replacement
- Store emoji on remote actors instead of duplicating in comment meta
- Look up author emoji from linked remote actor at display time
- Remove redundant pre_comment_content filters from Interactions
- Add get_emoji_tags() to filter only Emoji-type tags
- Store filtered emoji tags instead of entire tag array
- Use to_json() and to_array() while filters are removed
- Add accessibility attributes to emoji img tags (width, height, title, draggable)
- Add CSS for reduced-motion preference to pause animated emoji
- Update allowed HTML attributes for emoji in comments
- Add cache invalidation via filemtime for emoji with updated timestamps
- Add emoji support for actor names and descriptions in follower emails
- Add tests for emoji cache invalidation behavior
- Use correct method Remote_Actors::get_by_uri() instead of get_by_actor()
- Update test assertions to check for locally cached emoji URLs
Extracts emoji data from stored actor JSON and saves it as
_activitypub_emoji post meta, enabling display-time emoji
replacement for comments from actors that were stored before
emoji support was added.
Mastodon sends emoji names in lowercase in tags but may use different
casing in the content (e.g., :KannaWave: vs :kannawave:). Changed
str_replace to str_ireplace for case-insensitive matching.

Added comprehensive unit tests for the Emoji class covering multiple
emoji, repeated emoji, adjacent emoji, and case-insensitive replacement.
Use wp_add_inline_style to add emoji float fix only on edit-comments.php,
attached to wp-emoji-styles handle. Removes unused CSS from admin stylesheet.
@obenland obenland marked this pull request as ready for review December 18, 2025 17:10
Copilot AI review requested due to automatic review settings December 18, 2025 17:10
@obenland
Copy link
Member Author

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements custom emoji support for ActivityPub interactions, enabling custom emoji from federated sources (like Mastodon) to be displayed as images in WordPress comments. The implementation caches emoji images locally, stores emoji metadata with remote actors, and provides display-time rendering for author names while processing emoji in comment content at insert-time.

Key Changes

  • Adds new Emoji class to handle custom emoji replacement with image tags
  • Implements emoji caching system that downloads and stores emoji files organized by domain
  • Adds migration to extract and store emoji metadata from existing remote actors

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
includes/class-emoji.php New class providing emoji processing, replacement, and metadata extraction functionality
includes/class-attachments.php Adds emoji import/caching with timestamp-based refresh logic
includes/collection/class-interactions.php Integrates emoji processing into comment creation/updates and allows img tags in comments
includes/class-comment.php Adds filters to render emoji in author names at display-time
includes/collection/class-remote-actors.php Stores emoji metadata when upserting remote actors
includes/class-migration.php Adds batch migration to populate emoji metadata for existing actors
includes/class-mailer.php Applies emoji replacement in email notifications
includes/wp-admin/class-admin.php Adds CSS to prevent emoji float in comment author column
tests/phpunit/tests/includes/class-test-emoji.php Comprehensive test suite for emoji functionality
tests/phpunit/tests/includes/class-test-interactions.php Adds integration test for emoji in comments
tests/phpunit/tests/includes/class-test-migration.php Tests emoji migration batching and data extraction
tests/phpunit/tests/includes/class-test-attachments.php Tests emoji import, caching, and refresh logic
Comments suppressed due to low confidence (1)

includes/class-attachments.php:184

  • The emoji import accepts any valid URL without checking if it's an image file. While download_url will fetch any URL, consider validating that the URL path ends with a recognized image extension (.png, .jpg, .jpeg, .gif, .webp) or checking the Content-Type header after download to prevent non-image files from being cached in the emoji directory.
	public static function import_emoji( $emoji_url, $updated = null ) {
		if ( empty( $emoji_url ) || ! \filter_var( $emoji_url, FILTER_VALIDATE_URL ) ) {
			return false;
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Validate emoji downloads are actual images using wp_get_image_mime()
- Handle wp_mkdir_p() failure by cleaning up temp file
- Use glob for timestamp comparison to handle webp-optimized files
- Add unslash/re-slash in prepare_comment_data() to avoid escaping img attributes
- Add test for download failure handling
- Add Emoji::get_kses_allowed_html() with strict validation:
  - Requires class="emoji" attribute
  - Validates src URL points to local emoji uploads directory
  - Requires standard emoji dimensions (20x20)
- Update Interactions::allowed_comment_html() to use strict KSES
- Update Comment::unescape_emoji() to use strict KSES
- Only replace emoji shortcodes when local import succeeds
- Add activitypub_pre_import_emoji filter for test mocking
- Non-emoji img tags are now stripped from incoming content
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +150 to +157
foreach ( $emoji_data as $emoji ) {
$local_url = Attachments::import_emoji( $emoji['url'], $emoji['updated'] ?? null );

// Only replace if the emoji was successfully uploaded locally.
if ( $local_url ) {
$text = self::replace_emoji_in_text( $text, $emoji['name'], $local_url );
}
}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance concern: Emoji images are imported one by one in a loop, with each import potentially making an HTTP request to download the emoji. For comments with many emojis, this could result in multiple sequential HTTP requests. Consider implementing batch download or parallel processing of emoji imports to reduce latency when processing comments with multiple emojis.

Copilot uses AI. Check for mistakes.
Verifies that sanitize_file_name() prevents glob patterns like
[abc].png, test*.png, tes?.png, and {foo,bar}.png from matching
unintended cached files.
- Add missing @param tags for data provider test
- Add emoji import mock to interactions test for KSES validation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Federated comments: Fediverse handles including custom emoticons are displayed in plain text

5 participants