PluginProbe
ActivityPub / 9.2.1
ActivityPub v9.2.1
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / rest / admin / class-actions-controller.php

class-actions-controller.php in ActivityPub 9.2.1, at includes/rest/admin/class-actions-controller.php

266 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Actions REST Controller
4 *
5 * Handles administrative actions for followers/actors management.
6 *
7 * @package Activitypub
8 */
9
10 namespace Activitypub\Rest\Admin;
11
12 use Activitypub\Collection\Followers;
13 use Activitypub\Collection\Following;
14 use Activitypub\Collection\Remote_Actors;
15 use Activitypub\Moderation;
16 use Activitypub\OAuth\Server as OAuth_Server;
17
18 use function Activitypub\user_can_activitypub;
19
20 /**
21 * Admin Actions REST Controller Class.
22 */
23 class Actions_Controller extends \WP_REST_Controller {
24 /**
25 * The namespace of this controller's route.
26 *
27 * @var string
28 */
29 protected $namespace = ACTIVITYPUB_REST_NAMESPACE;
30
31 /**
32 * The base of this controller's route.
33 *
34 * @var string
35 */
36 protected $rest_base = 'admin/actors';
37
38 /**
39 * Register routes.
40 */
41 public function register_routes() {
42 // Delete follower relationship.
43 \register_rest_route(
44 $this->namespace,
45 '/' . $this->rest_base . '/(?P<id>[\d]+)/unfollow',
46 array(
47 'args' => array(
48 'id' => array(
49 'description' => 'The ID of the actor.',
50 'type' => 'integer',
51 'required' => true,
52 'validate_callback' => array( $this, 'validate_actor_id' ),
53 ),
54 ),
55 array(
56 'methods' => \WP_REST_Server::DELETABLE,
57 'callback' => array( $this, 'unfollow_actor' ),
58 'permission_callback' => array( $this, 'check_permission' ),
59 'show_in_index' => false,
60 ),
61 )
62 );
63
64 // Block actor.
65 \register_rest_route(
66 $this->namespace,
67 '/' . $this->rest_base . '/(?P<id>[\d]+)/block',
68 array(
69 'args' => array(
70 'id' => array(
71 'description' => 'The ID of the actor.',
72 'type' => 'integer',
73 'required' => true,
74 'validate_callback' => array( $this, 'validate_actor_id' ),
75 ),
76 ),
77 array(
78 'methods' => \WP_REST_Server::CREATABLE,
79 'callback' => array( $this, 'block_actor' ),
80 'permission_callback' => array( $this, 'check_permission' ),
81 'show_in_index' => false,
82 'args' => array(
83 'site_wide' => array(
84 'description' => 'Whether to block site-wide (admin only).',
85 'type' => 'boolean',
86 'default' => false,
87 ),
88 ),
89 ),
90 )
91 );
92
93 // Follow actor.
94 \register_rest_route(
95 $this->namespace,
96 '/' . $this->rest_base . '/(?P<id>[\d]+)/follow',
97 array(
98 'args' => array(
99 'id' => array(
100 'description' => 'The ID of the actor.',
101 'type' => 'integer',
102 'required' => true,
103 'validate_callback' => array( $this, 'validate_actor_id' ),
104 ),
105 ),
106 array(
107 'methods' => \WP_REST_Server::CREATABLE,
108 'callback' => array( $this, 'follow_actor' ),
109 'permission_callback' => array( $this, 'check_permission' ),
110 'show_in_index' => false,
111 ),
112 )
113 );
114 }
115
116 /**
117 * Check if the current user has permission to perform actions.
118 *
119 * @return bool|\WP_Error True if the request has permission, WP_Error object otherwise.
120 */
121 public function check_permission() {
122 // This is an admin endpoint; scoped OAuth C2S tokens must not drive it.
123 $denied = OAuth_Server::deny_if_oauth();
124 if ( null !== $denied ) {
125 return $denied;
126 }
127
128 /*
129 * A logged-out request has user ID 0, which is also the blog actor's ID
130 * (Actors::BLOG_USER_ID). Without the login check, user_can_activitypub( 0 ) reports the
131 * enabled blog actor, so an anonymous caller would pass and act with the blog's rights.
132 */
133 if ( ! \is_user_logged_in() || ! user_can_activitypub( \get_current_user_id() ) ) {
134 return new \WP_Error(
135 'rest_forbidden',
136 \__( 'Sorry, you are not allowed to perform this action.', 'activitypub' ),
137 array( 'status' => 403 )
138 );
139 }
140
141 return true;
142 }
143
144 /**
145 * Validate actor ID.
146 *
147 * @param int $value The actor ID.
148 * @return bool True if valid, false otherwise.
149 */
150 public function validate_actor_id( $value ) {
151 $actor = \get_post( $value );
152
153 return $actor instanceof \WP_Post && Remote_Actors::POST_TYPE === $actor->post_type;
154 }
155
156 /**
157 * Remove follower relationship.
158 *
159 * @param \WP_REST_Request $request Full data about the request.
160 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
161 */
162 public function unfollow_actor( $request ) {
163 $actor_id = $request->get_param( 'id' );
164 $user_id = \get_current_user_id();
165
166 $result = Followers::remove( $actor_id, $user_id );
167
168 if ( ! $result ) {
169 return new \WP_Error(
170 'rest_follower_removal_failed',
171 \__( 'Failed to remove follower.', 'activitypub' ),
172 array( 'status' => 500 )
173 );
174 }
175
176 return new \WP_REST_Response(
177 array(
178 'success' => true,
179 'message' => \__( 'Follower removed successfully.', 'activitypub' ),
180 ),
181 200
182 );
183 }
184
185 /**
186 * Block an actor.
187 *
188 * @param \WP_REST_Request $request Full data about the request.
189 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
190 */
191 public function block_actor( $request ) {
192 $actor_id = $request->get_param( 'id' );
193 $site_wide = $request->get_param( 'site_wide' );
194 $user_id = \get_current_user_id();
195
196 $actor = Remote_Actors::get_actor( $actor_id );
197 if ( \is_wp_error( $actor ) ) {
198 return $actor;
199 }
200
201 $actor_url = $actor->get_id();
202
203 // Add user-specific block.
204 $user_block_success = Moderation::add_user_block( $user_id, 'actor', $actor_url );
205
206 // Add site-wide block if requested and user has permission.
207 $site_block_success = true;
208 if ( $site_wide && \current_user_can( 'manage_options' ) ) {
209 $site_block_success = Moderation::add_site_block( 'actor', $actor_url );
210 }
211
212 if ( ! $user_block_success || ! $site_block_success ) {
213 return new \WP_Error(
214 'rest_actor_block_failed',
215 \__( 'Failed to block actor.', 'activitypub' ),
216 array( 'status' => 500 )
217 );
218 }
219
220 // Remove follower relationship after blocking.
221 Followers::remove( $actor_id, $user_id );
222
223 return new \WP_REST_Response(
224 array(
225 'success' => true,
226 'message' => \__( 'Actor blocked successfully.', 'activitypub' ),
227 ),
228 200
229 );
230 }
231
232 /**
233 * Follow an actor.
234 *
235 * @param \WP_REST_Request $request Full data about the request.
236 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
237 */
238 public function follow_actor( $request ) {
239 // Check if following UI is enabled.
240 if ( '1' !== \get_option( 'activitypub_following_ui', '0' ) ) {
241 return new \WP_Error(
242 'rest_following_disabled',
243 \__( 'Following feature is disabled.', 'activitypub' ),
244 array( 'status' => 403 )
245 );
246 }
247
248 $actor_id = $request->get_param( 'id' );
249 $user_id = \get_current_user_id();
250
251 $result = Following::follow( $actor_id, $user_id );
252
253 if ( \is_wp_error( $result ) ) {
254 return $result;
255 }
256
257 return new \WP_REST_Response(
258 array(
259 'success' => true,
260 'message' => \__( 'Actor followed successfully.', 'activitypub' ),
261 ),
262 200
263 );
264 }
265 }
266