Skip to content

Conversation

@obenland
Copy link
Member

@obenland obenland commented Jul 28, 2025

See #418.

Proposed changes:

  • Add comprehensive ActivityPub moderation system with hierarchical blocking
  • Implement three block types: actors (users), domains (instances), and keywords (content)
  • Add site-wide moderation interface for admins in Settings → ActivityPub
  • Add user-specific blocking controls in user profiles
  • Update inbox controllers to filter activities based on moderation rules
  • Create modern JavaScript interface with WordPress AJAX utilities
  • Implement responsive UI matching WordPress design standards

Other information:

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

Testing instructions:

Site-wide Moderation (Admin Users)

  • Go to 'Settings → ActivityPub'
  • Scroll down to the "Moderation" section
  • Test adding actors by full URL (e.g., https://mastodon.social/@username)
  • Test adding domains (e.g., spam-instance.com)
  • Test adding keywords (e.g., spam, advertisement)
  • Verify all blocks are saved and displayed correctly
  • Test removing blocks using the "Remove" buttons

User-specific Blocking

  • Go to 'Users → Your Profile' (or edit another user's profile as admin)
  • Scroll to the "Moderation" section under ActivityPub settings
  • Test adding personal blocks for actors, domains, and keywords
  • Verify these only affect the specific user, not site-wide
  • Test removing personal blocks

Activity Filtering

  • Send test ActivityPub activities that match blocked criteria
  • Verify activities are filtered correctly at both levels
  • Check that blocked activities trigger the activitypub_rest_inbox_disallowed action
  • Verify hierarchical blocking: site-wide blocks affect all users, user blocks only affect individual users

Changelog entry

  • Automatically create a changelog entry from the details below.
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Added - for new features
  • Changed - for changes in existing functionality
  • Deprecated - for soon-to-be removed features
  • Removed - for now removed features
  • Fixed - for any bug fixes
  • Security - in case of vulnerabilities

Message

Comprehensive ActivityPub moderation system with hierarchical blocking for actors, domains, and keywords. Includes site-wide admin controls and individual user blocking options.

Implements hierarchical blocking system (site-wide → user-specific → WordPress disallowed list) with three block types:
- Actors: Block specific ActivityPub users by URL
- Domains: Block entire instances by domain
- Keywords: Block content containing specific keywords

Features:
- Site-wide moderation for admins via Settings → ActivityPub
- User-specific blocks via user profiles
- Modern JavaScript with WordPress AJAX utilities
- Responsive UI matching WordPress standards
- Proper security with nonces and permission checks
- Integration with existing inbox controllers
obenland added 4 commits July 28, 2025 16:02
Implements thorough test coverage for the ActivityPub moderation system including:

- User block management (add/remove/get) for all three types (actors, domains, keywords)
- Site-wide block management with admin controls
- Activity blocking logic with hierarchical priority testing
- Edge cases: malformed data, invalid inputs, special characters, Unicode
- Complex scenarios: duplicate blocks, array re-indexing, WordPress fallback
- Boundary testing: extremely long values, invalid user IDs, empty data
- Integration testing: WordPress comment disallowed list fallback

All 22 tests pass with 91 assertions covering core functionality,
error handling, and real-world usage scenarios.
Automated code style fixes applied by PHP_CodeSniffer including:
- Consistent spacing and indentation
- WordPress coding standards compliance
- Array formatting and alignment
- Comment formatting improvements

No functional changes, only code style improvements.
- Remove page reloads from AJAX operations for better UX
- Replace location.reload() with dynamic UI updates
- Add helper functions to manage DOM updates properly
- Fix table removal when last item is deleted
- Rename functions from 'block' to 'blockedTerm' to avoid confusion with WordPress blocks
- Fix issue where adding terms created tables in all sections
- Use specific selectors to target correct containers for each type
- Add @Covers annotations to all test methods for proper coverage tracking
- Add @coversDefaultClass to test and main Moderation classes
- Refactor activity_is_blocked_site_wide and activity_is_blocked_for_user to eliminate code duplication
- Extract common blocking logic into private check_activity_against_blocks method
- Maintain full test coverage with all 22 tests passing

Improves code maintainability and test coverage visibility.
@obenland obenland marked this pull request as ready for review July 29, 2025 01:14
Copilot AI review requested due to automatic review settings July 29, 2025 01:14
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 adds a comprehensive ActivityPub moderation system with hierarchical blocking capabilities. The system allows both site-wide admin controls and individual user-specific blocking for actors, domains, and keywords, with site-wide blocks taking precedence over user-specific ones.

Key Changes:

  • Implements three-tier blocking system (actors, domains, keywords) at both site and user levels
  • Adds modern JavaScript-based admin interface for managing blocks via AJAX
  • Integrates moderation checks into ActivityPub inbox controllers to filter incoming activities

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
includes/class-moderation.php Core moderation API with blocking logic and activity filtering
includes/wp-admin/class-moderation.php Admin interface for moderation with AJAX handlers
includes/wp-admin/class-settings-fields.php Site-wide moderation settings in ActivityPub admin page
includes/wp-admin/class-user-settings-fields.php User-specific moderation controls in profile settings
includes/rest/class-inbox-controller.php Integration of moderation checks into shared inbox
includes/rest/class-actors-inbox-controller.php Integration of moderation checks into user-specific inbox
assets/js/activitypub-moderation-admin.js JavaScript interface for managing blocks with accessibility features
tests/includes/class-test-moderation.php Comprehensive test suite for moderation functionality
activitypub.php Plugin initialization to include moderation admin functionality

Documents the new comprehensive moderation features including:
- User-specific blocking controls for actors, domains, and keywords
- Site-wide moderation interface for administrators
- Integration with existing WordPress disallowed list
- Modern responsive UI with WordPress utilities
@pfefferle
Copy link
Member

What about the Blog-User?

@obenland
Copy link
Member Author

The blog user would adhere to site-wide settings

Move nonce verification to the beginning of all AJAX handlers before accessing any  data, as recommended by Copilot security review. This ensures proper nonce validation occurs before any other processing.

- ajax_add_user_block: Verify nonce first
- ajax_remove_user_block: Verify nonce first
- ajax_add_site_block: Verify nonce first
- ajax_remove_site_block: Verify nonce first, remove duplicate check

All tests continue to pass (22 tests, 91 assertions).
@pfefferle
Copy link
Member

pfefferle commented Jul 29, 2025

The Actor list is formatted differently as the other two.

@obenland
Copy link
Member Author

The Actor list is formatted differently as the other two.

Is that a good thing?

obenland added 3 commits July 29, 2025 08:56
…flict

- Dissolve separate wp-admin/class-moderation.php into main class-admin.php
- Rename AJAX action to 'activitypub_moderation_settings' for clarity
- Fix AJAX parameter conflict by changing 'action' to 'operation'
- Remove separate moderation class initialization from main plugin file
- Integrate moderation scripts and handlers into unified Admin class
- Update JavaScript to use single nonce and new parameter names

The 'action' parameter was conflicting with WordPress's internal AJAX routing
mechanism, causing "Invalid context or action" errors. Using 'operation'
resolves this conflict.

Benefits:
- Reduces codebase by 70 lines while maintaining full functionality
- More descriptive AJAX action name clearly indicates purpose
- Simplified admin architecture with fewer files to maintain
- All 22 moderation tests continue to pass with 91 assertions
Introduces user meta and site-wide settings for blocking ActivityPub actors, domains, and keywords. This enables more granular moderation capabilities for both individual users and the entire site.
Adjusted whitespace for variable assignments in the ajax_moderation_settings method to improve code readability and maintain consistent formatting.
@pfefferle
Copy link
Member

pfefferle commented Jul 29, 2025

I do not think so! I would prefer them to look the same!?

(Domain and Block-Words have a box and white background and the actor list not... At least at the time I wrote the comment 😉)

@obenland
Copy link
Member Author

I see what you mean!

@obenland
Copy link
Member Author

@pfefferle I think I'd like to remove the UI for user blocks for now and do that in a follow up PR with a full table etc and make it nice

obenland added 4 commits July 29, 2025 11:10
This commit removes the ability to block specific ActivityPub actors (users) by their actor URL from both site-wide and user moderation settings. The related UI elements, JavaScript logic, and server-side validation have been updated to only support domain and keyword blocks.
@obenland
Copy link
Member Author

@pfefferle This should be ready for a review

@pfefferle
Copy link
Member

pfefferle commented Jul 30, 2025

Newly added items will be added twice and even more times and the formatting (even/odd) is only visible after a reload.

Screenshot 2025-07-30 at 10 43 49

Should we validate for a real hos/domain or is it fine to have simple words that will be matched in the domain?

// Extract actor information.
$actor_id = '';
if ( isset( $activity_data['actor'] ) ) {
$actor_id = is_string( $activity_data['actor'] ) ? $activity_data['actor'] : ( $activity_data['actor']['id'] ?? '' );
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
$actor_id = is_string( $activity_data['actor'] ) ? $activity_data['actor'] : ( $activity_data['actor']['id'] ?? '' );
$actor_id = object_to_uri( $activity_data );

if ( $domain && in_array( $domain, $blocked_domains, true ) ) {
return true;
}
}
Copy link
Member

@pfefferle pfefferle Jul 30, 2025

Choose a reason for hiding this comment

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

should we fallback to ids and urls of the Activity or the Activity-Object?

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated it to include the author Id, activity id and activity object id. I assume the urls use the same domains as ids?

Copy link
Member

Choose a reason for hiding this comment

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

I would assume so, but there is no guarantee.

@pfefferle
Copy link
Member

pfefferle commented Jul 30, 2025

The current code is very explicit when it comes to domains (only checks the actor-id) but the keywords will be used for the whole Activity. Should we be more explicit with the keywords too? Maybe check keywords only in content and summary?

Should we re-use the Disallowed Comment Keys list for keyword blocking and only add custom code for domains and actors?

@pfefferle
Copy link
Member

We should explain the different options a bit more detailed so that users understand when to use what option and what it will check/block.

Something like:

  • "Blocked Domains" – This checks all domains (URLs and IDs) associated with an Activity against the list, but does not examine the content or summary fields.
  • "Blocked Keywords" - This checks summaries, contents and usernames, but not Domains.

@obenland
Copy link
Member Author

Newly added items will be added twice and even more times and the formatting (even/odd) is only visible after a reload.
Should we validate for a real hos/domain or is it fine to have simple words that will be matched in the domain?
#2020 (comment)

Did you test with the latest version of this PR? I can't reproduce that locally, I'm afraid.

@pfefferle
Copy link
Member

Did you test with the latest version of this PR? I can't reproduce that locally, I'm afraid.

It seems that this only happens if you start with an empty list and add multiple items without reloading. If there are already items in the list, it seems to add the right design.

@pfefferle
Copy link
Member

The "added twice and more" is also "only" a temporary thing. If you reload the site it will be properly uniqueified.

obenland added 3 commits July 30, 2025 11:10
Updated Moderation methods and related controllers to accept and process Activity objects instead of arrays. Adjusted tests to use Activity::init_from_array for consistency and improved type safety. This change streamlines activity handling and ensures more reliable moderation logic.
@obenland
Copy link
Member Author

Should we be more explicit with the keywords too? Maybe check keywords only in content and summary?

Yup, I like that, updated to only look in content.

Should we re-use the Disallowed Comment Keys list for keyword blocking and only add custom code for domains and actors?

I worry about fracturing the UI more that it already is between actor and domain & keywords.
Under the hood we could pipe the keywords into the disallowed comments key list to have wp_check_comment_disallowed_list() check for them, too, but I'm not sure whether it's more or less effort.

…nd domain validation

- Add client-side duplicate prevention to prevent submitting already blocked terms
- Add domain validation to ensure proper domain format (requires dot and valid characters)
- Add proper error messages with both accessibility announcements and visible alerts
- Include localized error messages for better user experience
- Prevent invalid domains like "fred" from being submitted
@obenland
Copy link
Member Author

We should explain the different options a bit more detailed so that users understand when to use what option and what it will check/block.

I think we're already pretty clear in what they do, not sure end-users should have to worry about details like URLs and IDs.

@obenland
Copy link
Member Author

@pfefferle It looks like in blog and actor mode, a user-specific block doesn't prevent an activity from going through when the blog actor boosted the user's post. Both get notified of a comment, for example, and the blog actor's notification goes through.

I assume that will be true for any boosts, even between multiple users?

@pfefferle
Copy link
Member

I think we're already pretty clear in what they do, not sure end-users should have to worry about details like URLs and IDs.

It was not so much about the URLs and IDs, but more what fields will be checked. As an example: The domain block will not search for blocked domains in the content.

@obenland
Copy link
Member Author

It was not so much about the URLs and IDs, but more what fields will be checked. As an example: The domain block will not search for blocked domains in the content.

Gotcha! Yeah, I feel like that's an implementation detail we should get right and not something end users should have to consider. It should just work. There'll likely be some iteration based on user feedback, but eventually it should just work.

@pfefferle
Copy link
Member

Gotcha! Yeah, I feel like that's an implementation detail we should get right and not something end users should have to consider. It should just work. There'll likely be some iteration based on user feedback, but eventually it should just work.

Yes, but it should be also transparent and/or self explanatory. Users should not be confused by why a post might be blocked or not!

@mediaformat
Copy link
Contributor

Maybe check keywords only in content and summary?

I would add Display name to the places keywords should check. Especially since remote comments are not 'editable'.

Abusive/uncivil actors have been known to use slurs in their display names.

Replaces direct object ID parsing with object_to_uri for more robust URI extraction. Also appends preferred username to content when the object is an actor, enhancing blocked keyword detection.
Updated test cases in Test_Moderation to include an 'id' field for Note objects. This ensures test data more accurately reflects expected object structure and improves test reliability.
Introduces a new test method and data provider to verify that activities with certain blocked attributes (such as 'content', 'preferredUsername', 'summary', and 'name') are correctly identified as blocked by the Moderation::activity_is_blocked method.
@obenland obenland merged commit 8d260e2 into trunk Aug 1, 2025
12 checks passed
@obenland obenland deleted the add/block-lists branch August 1, 2025 14:51
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.

4 participants