-
Notifications
You must be signed in to change notification settings - Fork 83
Add ActivityPub moderation system with hierarchical blocking #2020
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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
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.
There was a problem hiding this 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
|
What about the Blog-User? |
|
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).
|
The Actor list is formatted differently as the other two. |
Is that a good thing? |
…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.
|
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 😉) |
|
I see what you mean! |
|
@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 |
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.
|
@pfefferle This should be ready for a review |
includes/class-moderation.php
Outdated
| // Extract actor information. | ||
| $actor_id = ''; | ||
| if ( isset( $activity_data['actor'] ) ) { | ||
| $actor_id = is_string( $activity_data['actor'] ) ? $activity_data['actor'] : ( $activity_data['actor']['id'] ?? '' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| $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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
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 Should we re-use the Disallowed Comment Keys list for keyword blocking and only add custom code for domains and actors? |
|
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:
|
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. |
|
The "added twice and more" is also "only" a temporary thing. If you reload the site it will be properly uniqueified. |
Props @pfefferle
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.
Yup, I like that, updated to only look in content.
I worry about fracturing the UI more that it already is between actor and domain & keywords. |
…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
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. |
|
@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? |
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. |
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! |
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.

See #418.
Proposed changes:
Other information:
Testing instructions:
Site-wide Moderation (Admin Users)
User-specific Blocking
Activity Filtering
activitypub_rest_inbox_disallowedactionChangelog entry
Changelog Entry Details
Significance
Type
Message
Comprehensive ActivityPub moderation system with hierarchical blocking for actors, domains, and keywords. Includes site-wide admin controls and individual user blocking options.