PluginProbe
ActivityPub / 5.1.0
ActivityPub v5.1.0
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 5.1.0, at includes/rest/class-outbox-controller.php

328 lines 9.6 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\Activity;
11 use Activitypub\Collection\Actors;
12 use Activitypub\Collection\Outbox;
13 use Activitypub\Transformer\Factory;
14 use function ActivityPub\get_rest_url_by_path;
15
16 /**
17 * ActivityPub Outbox Controller.
18 *
19 * @author Matthias Pfefferle
20 *
21 * @see https://www.w3.org/TR/activitypub/#outbox
22 */
23 class Outbox_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 = '(?:users|actors)/(?P<user_id>[\w\-\.]+)/outbox';
37
38 /**
39 * Register routes.
40 */
41 public function register_routes() {
42 \register_rest_route(
43 $this->namespace,
44 '/' . $this->rest_base,
45 array(
46 'args' => array(
47 'user_id' => array(
48 'description' => 'The ID of the user or actor.',
49 'type' => 'string',
50 'validate_callback' => array( $this, 'validate_user_id' ),
51 ),
52 ),
53 array(
54 'methods' => \WP_REST_Server::READABLE,
55 'callback' => array( $this, 'get_items' ),
56 'permission_callback' => array( 'Activitypub\Rest\Server', 'verify_signature' ),
57 'args' => array(
58 'page' => array(
59 'description' => 'Current page of the collection.',
60 'type' => 'integer',
61 'default' => 1,
62 'minimum' => 1,
63 ),
64 'per_page' => array(
65 'description' => 'Maximum number of items to be returned in result set.',
66 'type' => 'integer',
67 'default' => 10,
68 'minimum' => 1,
69 'maximum' => 100,
70 ),
71 ),
72 ),
73 'schema' => array( $this, 'get_collection_schema' ),
74 )
75 );
76 }
77
78 /**
79 * Validates the user_id parameter.
80 *
81 * @param mixed $user_id The user_id parameter.
82 * @return bool|\WP_Error True if the user_id is valid, WP_Error otherwise.
83 */
84 public function validate_user_id( $user_id ) {
85 $user = Actors::get_by_various( $user_id );
86 if ( \is_wp_error( $user ) ) {
87 return $user;
88 }
89
90 return true;
91 }
92
93 /**
94 * Retrieves a collection of outbox items.
95 *
96 * @param \WP_REST_Request $request Full details about the request.
97 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
98 */
99 public function get_items( $request ) {
100 $user_id = $request->get_param( 'user_id' );
101 $page = $request->get_param( 'page' );
102 $user = Actors::get_by_various( $user_id );
103
104 /**
105 * Action triggered prior to the ActivityPub profile being created and sent to the client.
106 *
107 * @param \WP_REST_Request $request The request object.
108 */
109 \do_action( 'activitypub_rest_outbox_pre', $request );
110
111 /**
112 * Filters the list of activity types to include in the outbox.
113 *
114 * @param string[] $activity_types The list of activity types.
115 */
116 $activity_types = apply_filters( 'rest_activitypub_outbox_activity_types', array( 'Announce', 'Create', 'Like', 'Update' ) );
117
118 switch ( $user_id ) {
119 case Actors::APPLICATION_USER_ID:
120 $actor_type = 'application';
121 break;
122 case Actors::BLOG_USER_ID:
123 $actor_type = 'blog';
124 break;
125 default:
126 $actor_type = 'user';
127 break;
128 }
129
130 $args = array(
131 'posts_per_page' => $request->get_param( 'per_page' ),
132 'author' => $user_id > 0 ? $user_id : null,
133 'paged' => $page,
134 'post_type' => Outbox::POST_TYPE,
135 'post_status' => 'any',
136
137 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
138 'meta_query' => array(
139 array(
140 'key' => '_activitypub_activity_actor',
141 'value' => $actor_type,
142 ),
143 ),
144 );
145
146 if ( get_current_user_id() !== $user_id && ! current_user_can( 'activitypub' ) ) {
147 $args['meta_query'][] = array(
148 'key' => '_activitypub_activity_type',
149 'value' => $activity_types,
150 'compare' => 'IN',
151 );
152
153 $args['meta_query'][] = array(
154 'relation' => 'OR',
155 array(
156 'key' => 'activitypub_content_visibility',
157 'compare' => 'NOT EXISTS',
158 ),
159 array(
160 'key' => 'activitypub_content_visibility',
161 'value' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
162 ),
163 );
164 }
165
166 /**
167 * Filters WP_Query arguments when querying Outbox items via the REST API.
168 *
169 * Enables adding extra arguments or setting defaults for an outbox collection request.
170 *
171 * @param array $args Array of arguments for WP_Query.
172 * @param \WP_REST_Request $request The REST API request.
173 */
174 $args = apply_filters( 'rest_activitypub_outbox_query', $args, $request );
175
176 $outbox_query = new \WP_Query();
177 $query_result = $outbox_query->query( $args );
178
179 $response = array(
180 '@context' => array( 'https://www.w3.org/ns/activitystreams' ),
181 'id' => get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ),
182 'generator' => 'https://wordpress.org/?v=' . \get_bloginfo( 'version' ),
183 'actor' => $user->get_id(),
184 'type' => 'OrderedCollectionPage',
185 'partOf' => get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ),
186 'totalItems' => $outbox_query->found_posts,
187 'orderedItems' => array(),
188 );
189
190 \update_postmeta_cache( \wp_list_pluck( $query_result, 'ID' ) );
191 foreach ( $query_result as $outbox_item ) {
192 $response['orderedItems'][] = $this->prepare_item_for_response( $outbox_item, $request );
193 }
194
195 $max_pages = \ceil( $response['totalItems'] / $request->get_param( 'per_page' ) );
196 $response['first'] = \add_query_arg( 'page', 1, $response['partOf'] );
197 $response['last'] = \add_query_arg( 'page', \max( $max_pages, 1 ), $response['partOf'] );
198
199 if ( $max_pages > $page ) {
200 $response['next'] = \add_query_arg( 'page', $page + 1, $response['partOf'] );
201 }
202
203 if ( $page > 1 ) {
204 $response['prev'] = \add_query_arg( 'page', $page - 1, $response['partOf'] );
205 }
206
207 /**
208 * Filter the ActivityPub outbox array.
209 *
210 * @param array $response The ActivityPub outbox array.
211 * @param \WP_REST_Request $request The request object.
212 */
213 $response = \apply_filters( 'activitypub_rest_outbox_array', $response, $request );
214
215 /**
216 * Action triggered after the ActivityPub profile has been created and sent to the client.
217 *
218 * @param \WP_REST_Request $request The request object.
219 */
220 \do_action( 'activitypub_outbox_post', $request );
221
222 $response = \rest_ensure_response( $response );
223 $response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
224
225 return $response;
226 }
227
228 /**
229 * Prepares the item for the REST response.
230 *
231 * @param mixed $item WordPress representation of the item.
232 * @param \WP_REST_Request $request Request object.
233 * @return array Response object on success, or WP_Error object on failure.
234 */
235 public function prepare_item_for_response( $item, $request ) {
236 $type = \get_post_meta( $item->ID, '_activitypub_activity_type', true );
237
238 $activity = new Activity();
239 $activity->set_type( $type );
240 $activity->set_id( $item->guid );
241 // Pre-fill the Activity with data (for example cc and to).
242 $activity->set_object( \json_decode( $item->post_content, true ) );
243 $activity->set_actor( Actors::get_by_various( $request->get_param( 'user_id' ) )->get_id() );
244
245 return $activity->to_array( false );
246 }
247
248 /**
249 * Retrieves the outbox schema, conforming to JSON Schema.
250 *
251 * @return array Collection schema data.
252 */
253 public function get_collection_schema() {
254 if ( $this->schema ) {
255 return $this->add_additional_fields_schema( $this->schema );
256 }
257
258 $schema = array(
259 '$schema' => 'http://json-schema.org/draft-04/schema#',
260 'title' => 'outbox',
261 'type' => 'object',
262 'properties' => array(
263 '@context' => array(
264 'description' => 'The JSON-LD context for the collection.',
265 'type' => array( 'string', 'array', 'object' ),
266 'required' => true,
267 ),
268 'id' => array(
269 'description' => 'The unique identifier for the collection.',
270 'type' => 'string',
271 'format' => 'uri',
272 'required' => true,
273 ),
274 'type' => array(
275 'description' => 'The type of the collection.',
276 'type' => 'string',
277 'enum' => array( 'OrderedCollection', 'OrderedCollectionPage' ),
278 'required' => true,
279 ),
280 'actor' => array(
281 'description' => 'The actor who owns this outbox.',
282 'type' => 'string',
283 'format' => 'uri',
284 'required' => true,
285 ),
286 'totalItems' => array(
287 'description' => 'The total number of items in the collection.',
288 'type' => 'integer',
289 'minimum' => 0,
290 'required' => true,
291 ),
292 'orderedItems' => array(
293 'description' => 'The items in the collection.',
294 'type' => 'array',
295 'items' => array(
296 'type' => 'object',
297 ),
298 'required' => true,
299 ),
300 'first' => array(
301 'description' => 'The first page of the collection.',
302 'type' => 'string',
303 'format' => 'uri',
304 ),
305 'last' => array(
306 'description' => 'The last page of the collection.',
307 'type' => 'string',
308 'format' => 'uri',
309 ),
310 'next' => array(
311 'description' => 'The next page of the collection.',
312 'type' => 'string',
313 'format' => 'uri',
314 ),
315 'prev' => array(
316 'description' => 'The previous page of the collection.',
317 'type' => 'string',
318 'format' => 'uri',
319 ),
320 ),
321 );
322
323 $this->schema = $schema;
324
325 return $this->add_additional_fields_schema( $this->schema );
326 }
327 }
328