PluginProbe
ActivityPub / trunk
ActivityPub vtrunk
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-liked-controller.php

class-liked-controller.php in ActivityPub trunk, at includes/rest/class-liked-controller.php

224 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Liked_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 Liked Controller.
19 *
20 * Serves the `liked` collection for an actor, listing all objects
21 * the actor has liked that have not been subsequently undone.
22 *
23 * @see https://www.w3.org/TR/activitypub/#liked
24 *
25 * @since 8.1.0
26 */
27 class Liked_Controller extends Actors_Controller {
28 use Collection;
29
30 /**
31 * Register routes.
32 */
33 public function register_routes() {
34 \register_rest_route(
35 $this->namespace,
36 '/' . $this->rest_base . '/liked',
37 array(
38 'args' => array(
39 'user_id' => array(
40 'description' => 'The ID of the actor.',
41 'type' => 'integer',
42 'required' => true,
43 'validate_callback' => array( $this, 'validate_user_id' ),
44 ),
45 ),
46 array(
47 'methods' => \WP_REST_Server::READABLE,
48 'callback' => array( $this, 'get_items' ),
49 'permission_callback' => array( $this, 'verify_signature' ),
50 'args' => array(
51 'page' => array(
52 'description' => 'Current page of the collection.',
53 'type' => 'integer',
54 'minimum' => 1,
55 // No default so we can differentiate between Collection and CollectionPage requests.
56 ),
57 'per_page' => array(
58 'description' => 'Maximum number of items to be returned in result set.',
59 'type' => 'integer',
60 'default' => 20,
61 'minimum' => 1,
62 'maximum' => 100,
63 ),
64 ),
65 ),
66 'schema' => array( $this, 'get_item_schema' ),
67 )
68 );
69 }
70
71 /**
72 * Retrieves the liked collection for an actor.
73 *
74 * Queries the outbox for Like activities, excluding those that
75 * have been subsequently undone. Handles re-likes correctly by
76 * checking the most recent activity for each object.
77 *
78 * @since 8.1.0
79 *
80 * @param \WP_REST_Request $request Full details about the request.
81 * @return \WP_REST_Response|\WP_Error Response object.
82 */
83 public function get_items( $request ) {
84 $user_id = $request->get_param( 'user_id' );
85 $page = $request->get_param( 'page' );
86 $per_page = $request->get_param( 'per_page' );
87
88 $liked_objects = $this->get_liked_object_ids( $user_id );
89
90 // Paginate the results.
91 $offset = ( null !== $page ) ? ( $page - 1 ) * $per_page : 0;
92 $ordered_items = \array_slice( $liked_objects, $offset, $per_page );
93
94 $response = array(
95 '@context' => Base_Object::JSON_LD_CONTEXT,
96 'id' => get_rest_url_by_path( \sprintf( 'actors/%d/liked', $user_id ) ),
97 'generator' => 'https://wordpress.org/?v=' . get_masked_wp_version(),
98 'actor' => Actors::get_by_id( $user_id )->get_id(),
99 'type' => 'OrderedCollection',
100 'totalItems' => \count( $liked_objects ),
101 'orderedItems' => $ordered_items,
102 );
103
104 $response = $this->prepare_collection_response( $response, $request );
105
106 if ( \is_wp_error( $response ) ) {
107 return $response;
108 }
109
110 $response = \rest_ensure_response( $response );
111 $response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
112
113 return $response;
114 }
115
116 /**
117 * Get all currently liked object IDs for an actor.
118 *
119 * Queries all Like and Undo activities from the outbox, ordered
120 * newest first. For each unique object ID, the most recent
121 * activity determines whether the object is still liked.
122 *
123 * @since 8.1.0
124 *
125 * @param int $user_id The actor user ID.
126 * @return string[] Array of liked object URLs.
127 */
128 private function get_liked_object_ids( $user_id ) {
129 $args = array(
130 // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
131 'posts_per_page' => 1000,
132 'post_type' => Outbox::POST_TYPE,
133 'post_status' => 'any',
134 'orderby' => array(
135 'date' => 'DESC',
136 'ID' => 'DESC',
137 ),
138 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
139 'meta_query' => array(
140 array(
141 'key' => '_activitypub_activity_actor',
142 'value' => Actors::get_type_by_id( $user_id ),
143 ),
144 array(
145 'key' => '_activitypub_activity_type',
146 'value' => array( 'Like', 'Undo' ),
147 'compare' => 'IN',
148 ),
149 ),
150 );
151
152 if ( $user_id > 0 ) {
153 $args['author'] = $user_id;
154 }
155
156 $posts = \get_posts( $args );
157
158 \update_postmeta_cache( \wp_list_pluck( $posts, 'ID' ) );
159
160 /*
161 * Walk newest-first. For each unique object ID, the first
162 * occurrence determines the current state: if it is a Like
163 * the object is still liked, if it is an Undo the like was
164 * revoked. Skip any object ID we have already seen.
165 */
166 $seen = array();
167 $liked = array();
168
169 foreach ( $posts as $post ) {
170 $object_id = \get_post_meta( $post->ID, '_activitypub_object_id', true );
171 $activity_type = \get_post_meta( $post->ID, '_activitypub_activity_type', true );
172
173 if ( ! $object_id || isset( $seen[ $object_id ] ) ) {
174 continue;
175 }
176
177 $seen[ $object_id ] = true;
178
179 if ( 'Like' === $activity_type ) {
180 $liked[] = $object_id;
181 }
182 }
183
184 return $liked;
185 }
186
187 /**
188 * Retrieves the schema for the liked endpoint.
189 *
190 * @since 8.1.0
191 *
192 * @return array Schema data.
193 */
194 public function get_item_schema() {
195 if ( $this->schema ) {
196 return $this->add_additional_fields_schema( $this->schema );
197 }
198
199 $item_schema = array(
200 'type' => 'string',
201 'format' => 'uri',
202 );
203
204 $schema = $this->get_collection_schema( $item_schema );
205
206 $schema['title'] = 'liked';
207 $schema['properties']['actor'] = array(
208 'description' => 'The actor who owns this liked collection.',
209 'type' => 'string',
210 'format' => 'uri',
211 'required' => true,
212 );
213 $schema['properties']['generator'] = array(
214 'description' => 'The software used to generate the collection.',
215 'type' => 'string',
216 'format' => 'uri',
217 );
218
219 $this->schema = $schema;
220
221 return $this->add_additional_fields_schema( $this->schema );
222 }
223 }
224