PluginProbe
ActivityPub / 7.8.4
ActivityPub v7.8.4
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 / class-outbox-controller.php

class-outbox-controller.php in ActivityPub 7.8.4, at includes/rest/class-outbox-controller.php

321 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Outbox Controller file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Rest;
9
10 use Activitypub\Activity\Base_Object;
11 use Activitypub\Collection\Actors;
12 use Activitypub\Collection\Outbox;
13
14 use function Activitypub\get_masked_wp_version;
15 use function Activitypub\get_rest_url_by_path;
16
17 /**
18 * ActivityPub Outbox Controller.
19 *
20 * @author Matthias Pfefferle
21 *
22 * @see https://www.w3.org/TR/activitypub/#outbox
23 */
24 class Outbox_Controller extends \WP_REST_Controller {
25 use Collection;
26
27 /**
28 * The namespace of this controller's route.
29 *
30 * @var string
31 */
32 protected $namespace = ACTIVITYPUB_REST_NAMESPACE;
33
34 /**
35 * The base of this controller's route.
36 *
37 * @var string
38 */
39 protected $rest_base = '(?:users|actors)/(?P<user_id>[-]?\d+)/outbox';
40
41 /**
42 * Register routes.
43 */
44 public function register_routes() {
45 \register_rest_route(
46 $this->namespace,
47 '/' . $this->rest_base,
48 array(
49 'args' => array(
50 'user_id' => array(
51 'description' => 'The ID of the user or actor.',
52 'type' => 'integer',
53 'validate_callback' => array( $this, 'validate_user_id' ),
54 ),
55 ),
56 array(
57 'methods' => \WP_REST_Server::READABLE,
58 'callback' => array( $this, 'get_items' ),
59 'permission_callback' => array( 'Activitypub\Rest\Server', 'verify_signature' ),
60 'args' => array(
61 'page' => array(
62 'description' => 'Current page of the collection.',
63 'type' => 'integer',
64 'minimum' => 1,
65 // No default so we can differentiate between Collection and CollectionPage requests.
66 ),
67 'per_page' => array(
68 'description' => 'Maximum number of items to be returned in result set.',
69 'type' => 'integer',
70 'default' => 20,
71 'minimum' => 1,
72 'maximum' => 100,
73 ),
74 ),
75 ),
76 'schema' => array( $this, 'get_item_schema' ),
77 )
78 );
79
80 \add_filter( 'activitypub_rest_outbox_array', array( $this, 'overload_total_items' ), 10, 2 );
81 }
82
83 /**
84 * Validates the user_id parameter.
85 *
86 * @param mixed $user_id The user_id parameter.
87 * @return bool|\WP_Error True if the user_id is valid, WP_Error otherwise.
88 */
89 public function validate_user_id( $user_id ) {
90 $user = Actors::get_by_id( $user_id );
91 if ( \is_wp_error( $user ) ) {
92 return $user;
93 }
94
95 return true;
96 }
97
98 /**
99 * Retrieves a collection of outbox items.
100 *
101 * @param \WP_REST_Request $request Full details about the request.
102 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
103 */
104 public function get_items( $request ) {
105 $page = $request->get_param( 'page' ) ?? 1;
106 $user_id = $request->get_param( 'user_id' );
107 $user = Actors::get_by_id( $user_id );
108
109 /**
110 * Action triggered prior to the ActivityPub profile being created and sent to the client.
111 *
112 * @param \WP_REST_Request $request The request object.
113 */
114 \do_action( 'activitypub_rest_outbox_pre', $request );
115
116 /**
117 * Filters the list of activity types to include in the outbox.
118 *
119 * @param string[] $activity_types The list of activity types.
120 */
121 $activity_types = apply_filters( 'rest_activitypub_outbox_activity_types', array( 'Announce', 'Create', 'Like', 'Update' ) );
122
123 $args = array(
124 'posts_per_page' => $request->get_param( 'per_page' ),
125 'author' => $user_id > 0 ? $user_id : null,
126 'paged' => $page,
127 'post_type' => Outbox::POST_TYPE,
128 'post_status' => 'any',
129
130 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
131 'meta_query' => array(
132 array(
133 'key' => '_activitypub_activity_actor',
134 'value' => Actors::get_type_by_id( $user_id ),
135 ),
136 ),
137 );
138
139 if ( get_current_user_id() !== $user_id && ! current_user_can( 'activitypub' ) ) {
140 $args['meta_query'][] = array(
141 'key' => '_activitypub_activity_type',
142 'value' => $activity_types,
143 'compare' => 'IN',
144 );
145
146 $args['meta_query'][] = array(
147 'relation' => 'OR',
148 array(
149 'key' => 'activitypub_content_visibility',
150 'compare' => 'NOT EXISTS',
151 ),
152 array(
153 'key' => 'activitypub_content_visibility',
154 'value' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
155 ),
156 );
157 }
158
159 /**
160 * Filters WP_Query arguments when querying Outbox items via the REST API.
161 *
162 * Enables adding extra arguments or setting defaults for an outbox collection request.
163 *
164 * @param array $args Array of arguments for WP_Query.
165 * @param \WP_REST_Request $request The REST API request.
166 */
167 $args = \apply_filters( 'activitypub_rest_outbox_query', $args, $request );
168
169 $outbox_query = new \WP_Query();
170 $query_result = $outbox_query->query( $args );
171
172 $response = array(
173 '@context' => Base_Object::JSON_LD_CONTEXT,
174 'id' => get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ),
175 'generator' => 'https://wordpress.org/?v=' . get_masked_wp_version(),
176 'actor' => $user->get_id(),
177 'type' => 'OrderedCollection',
178 'totalItems' => (int) $outbox_query->found_posts,
179 'orderedItems' => array(),
180 );
181
182 \update_postmeta_cache( \wp_list_pluck( $query_result, 'ID' ) );
183 foreach ( $query_result as $outbox_item ) {
184 if ( ! $outbox_item instanceof \WP_Post ) {
185 /**
186 * Action triggered when an outbox item is not a WP_Post.
187 *
188 * @param mixed $outbox_item The outbox item.
189 * @param array $args The arguments used to query the outbox.
190 * @param array $query_result The result of the query.
191 * @param \WP_REST_Request $request The request object.
192 */
193 do_action( 'activitypub_rest_outbox_item_error', $outbox_item, $args, $query_result, $request );
194
195 continue;
196 }
197
198 $response['orderedItems'][] = $this->prepare_item_for_response( $outbox_item, $request );
199 }
200
201 $response = $this->prepare_collection_response( $response, $request );
202 if ( is_wp_error( $response ) ) {
203 return $response;
204 }
205
206 /**
207 * Filter the ActivityPub outbox array.
208 *
209 * @param array $response The ActivityPub outbox array.
210 * @param \WP_REST_Request $request The request object.
211 */
212 $response = \apply_filters( 'activitypub_rest_outbox_array', $response, $request );
213
214 /**
215 * Action triggered after the ActivityPub profile has been created and sent to the client.
216 *
217 * @param \WP_REST_Request $request The request object.
218 */
219 \do_action( 'activitypub_rest_outbox_post', $request );
220
221 $response = \rest_ensure_response( $response );
222 $response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
223
224 return $response;
225 }
226
227 /**
228 * Prepares the item for the REST response.
229 *
230 * @param mixed $item WordPress representation of the item.
231 * @param \WP_REST_Request $request Request object.
232 * @return array Response object on success, or WP_Error object on failure.
233 */
234 public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
235 $activity = Outbox::get_activity( $item->ID );
236
237 return $activity->to_array( false );
238 }
239
240 /**
241 * Retrieves the outbox schema, conforming to JSON Schema.
242 *
243 * @return array Collection schema data.
244 */
245 public function get_item_schema() {
246 if ( $this->schema ) {
247 return $this->add_additional_fields_schema( $this->schema );
248 }
249
250 $item_schema = array(
251 'type' => 'object',
252 );
253
254 $schema = $this->get_collection_schema( $item_schema );
255
256 // Add outbox-specific properties.
257 $schema['title'] = 'outbox';
258 $schema['properties']['actor'] = array(
259 'description' => 'The actor who owns this outbox.',
260 'type' => 'string',
261 'format' => 'uri',
262 'required' => true,
263 );
264 $schema['properties']['generator'] = array(
265 'description' => 'The software used to generate the collection.',
266 'type' => 'string',
267 'format' => 'uri',
268 );
269
270 $this->schema = $schema;
271
272 return $this->add_additional_fields_schema( $this->schema );
273 }
274
275 /**
276 * Overload total items.
277 *
278 * The `totalItems` property is used by Mastodon to show the overall
279 * number of federated posts and comments.
280 *
281 * @param array $response The response array.
282 * @param \WP_REST_Request $request The request object.
283 *
284 * @return array The modified response array.
285 */
286 public function overload_total_items( $response, $request ) {
287 $posts = new \WP_Query(
288 array(
289 'post_status' => 'publish',
290 'author' => $request->get_param( 'user_id' ),
291 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
292 'meta_query' => array(
293 array(
294 'key' => 'activitypub_status',
295 'compare' => 'EXISTS',
296 ),
297 ),
298 'fields' => 'ids',
299 'no_found_rows' => false,
300 'number' => 1,
301 )
302 );
303
304 $comments = new \WP_Comment_Query(
305 array(
306 'status' => 'approve',
307 'user_id' => $request->get_param( 'user_id' ),
308 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
309 'meta_key' => 'activitypub_status',
310 'fields' => 'ids',
311 'no_found_rows' => false,
312 'number' => 1,
313 )
314 );
315
316 $response['totalItems'] = (int) $posts->found_posts + (int) $comments->found_comments;
317
318 return $response;
319 }
320 }
321