PluginProbe
ActivityPub / 8.3.0
ActivityPub v8.3.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-replies-controller.php

class-replies-controller.php in ActivityPub 8.3.0, at includes/rest/class-replies-controller.php

266 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Replies_Controller file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Rest;
9
10 use Activitypub\Activity\Base_Object;
11 use Activitypub\Collection\Interactions;
12 use Activitypub\Collection\Replies;
13
14 use function Activitypub\get_rest_url_by_path;
15 use function Activitypub\is_post_publicly_queryable;
16
17 /**
18 * ActivityPub Replies_Controller class.
19 */
20 class Replies_Controller extends \WP_REST_Controller {
21
22 /**
23 * The namespace of this controller's route.
24 *
25 * @var string
26 */
27 protected $namespace = ACTIVITYPUB_REST_NAMESPACE;
28
29 /**
30 * The base of this controller's route.
31 *
32 * @var string
33 */
34 protected $rest_base = '(?P<object_type>[\w\-\.]+)s/(?P<id>[\d]+)/(?P<type>[\w\-\.]+)';
35
36 /**
37 * Register routes.
38 */
39 public function register_routes() {
40 \register_rest_route(
41 $this->namespace,
42 '/' . $this->rest_base,
43 array(
44 'args' => array(
45 'object_type' => array(
46 'description' => 'The type of object to get replies for.',
47 'type' => 'string',
48 'enum' => array( 'post', 'comment' ),
49 'required' => true,
50 ),
51 'id' => array(
52 'description' => 'The ID of the object.',
53 'type' => 'integer',
54 'minimum' => 1,
55 'required' => true,
56 ),
57 'type' => array(
58 'description' => 'The type of collection to query.',
59 'type' => 'string',
60 'enum' => array( 'replies', 'likes', 'shares' ),
61 'required' => true,
62 ),
63 ),
64 array(
65 'methods' => \WP_REST_Server::READABLE,
66 'callback' => array( $this, 'get_items' ),
67 'permission_callback' => '__return_true',
68 'args' => array(
69 'page' => array(
70 'description' => 'Current page of the collection.',
71 'type' => 'integer',
72 'minimum' => 1,
73 // No default so we can differentiate between Collection and CollectionPage requests.
74 ),
75 ),
76 ),
77 'schema' => array( $this, 'get_item_schema' ),
78 )
79 );
80 }
81
82 /**
83 * Retrieves a collection of replies.
84 *
85 * @param \WP_REST_Request $request The request object.
86 *
87 * @return \WP_REST_Response|\WP_Error Response object or WP_Error object.
88 */
89 public function get_items( $request ) {
90 $object_type = $request->get_param( 'object_type' );
91 $id = (int) $request->get_param( 'id' );
92
93 if ( 'comment' === $object_type ) {
94 $wp_object = \get_comment( $id );
95
96 /*
97 * A comment inherits the visibility of its parent post. Reject
98 * collections for comments whose parent post is not currently
99 * publicly queryable, so we don't leak metadata for private,
100 * draft, password-protected, or local-only posts — including
101 * posts that were once federated and have since been made
102 * non-public.
103 */
104 $publicly_queryable = $wp_object && is_post_publicly_queryable( $wp_object->comment_post_ID );
105 } else {
106 $wp_object = \get_post( $id );
107 $publicly_queryable = $wp_object && is_post_publicly_queryable( $wp_object );
108 }
109
110 if ( ! $publicly_queryable ) {
111 return new \WP_Error(
112 'activitypub_replies_collection_does_not_exist',
113 \sprintf(
114 // translators: %s: The type (post, comment, etc.) for which no replies collection exists.
115 \__( 'No reply collection exists for the type %s.', 'activitypub' ),
116 $object_type
117 ),
118 array( 'status' => 404 )
119 );
120 }
121
122 switch ( $request->get_param( 'type' ) ) {
123 case 'replies':
124 $response = $this->get_replies( $request, $wp_object );
125 break;
126
127 case 'likes':
128 $response = $this->get_likes( $request, $wp_object );
129 break;
130
131 case 'shares':
132 $response = $this->get_shares( $request, $wp_object );
133 break;
134
135 default:
136 $response = new \WP_Error( 'rest_unknown_collection_type', 'Unknown collection type.', array( 'status' => 404 ) );
137 }
138
139 // Prepend ActivityPub Context.
140 $response = array_merge( array( '@context' => Base_Object::JSON_LD_CONTEXT ), $response );
141 $response = \rest_ensure_response( $response );
142 $response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
143
144 return $response;
145 }
146
147 /**
148 * Retrieves a collection of replies.
149 *
150 * @param \WP_REST_Request $request The request object.
151 * @param \WP_Post|\WP_Comment $wp_object The WordPress object.
152 *
153 * @return array Response collection of replies.
154 */
155 public function get_replies( $request, $wp_object ) {
156 $page = $request->get_param( 'page' );
157
158 // If the request parameter page is present get the CollectionPage otherwise the Replies collection.
159 if ( null === $page ) {
160 $response = Replies::get_collection( $wp_object );
161 } else {
162 $response = Replies::get_collection_page( $wp_object, $page );
163 }
164
165 return $response;
166 }
167
168 /**
169 * Retrieves a collection of likes.
170 *
171 * @param \WP_REST_Request $request The request object.
172 * @param \WP_Post|\WP_Comment $wp_object The WordPress object.
173 *
174 * @return array Response collection of likes.
175 */
176 public function get_likes( $request, $wp_object ) {
177 if ( $wp_object instanceof \WP_Post ) {
178 $likes = Interactions::count_by_type( $wp_object->ID, 'like' );
179 } else {
180 $likes = 0;
181 }
182
183 return array(
184 'id' => get_rest_url_by_path( sprintf( 'posts/%d/likes', $wp_object->ID ) ),
185 'type' => 'Collection',
186 'totalItems' => $likes,
187 );
188 }
189
190 /**
191 * Retrieves a collection of shares.
192 *
193 * @param \WP_REST_Request $request The request object.
194 * @param \WP_Post|\WP_Comment $wp_object The WordPress object.
195 *
196 * @return array Response collection of shares.
197 */
198 public function get_shares( $request, $wp_object ) {
199 if ( $wp_object instanceof \WP_Post ) {
200 $shares = Interactions::count_by_type( $wp_object->ID, 'repost' ) + Interactions::count_by_type( $wp_object->ID, 'quote' );
201 } else {
202 $shares = 0;
203 }
204
205 return array(
206 'id' => get_rest_url_by_path( sprintf( 'posts/%d/shares', $wp_object->ID ) ),
207 'type' => 'Collection',
208 'totalItems' => $shares,
209 );
210 }
211
212 /**
213 * Retrieves the schema for the Replies endpoint.
214 *
215 * @return array Schema data.
216 */
217 public function get_item_schema() {
218 if ( $this->schema ) {
219 return $this->add_additional_fields_schema( $this->schema );
220 }
221
222 $schema = array(
223 '$schema' => 'http://json-schema.org/draft-04/schema#',
224 'title' => 'replies',
225 'type' => 'object',
226 'properties' => array(
227 '@context' => array(
228 'type' => 'array',
229 'items' => array(
230 'type' => 'string',
231 ),
232 'required' => true,
233 ),
234 'id' => array(
235 'type' => 'string',
236 'format' => 'uri',
237 'required' => true,
238 ),
239 'type' => array(
240 'type' => 'string',
241 'enum' => array( 'Collection', 'OrderedCollection', 'CollectionPage', 'OrderedCollectionPage' ),
242 'required' => true,
243 ),
244 'first' => array(
245 'type' => 'string',
246 'format' => 'uri',
247 ),
248 'last' => array(
249 'type' => 'string',
250 'format' => 'uri',
251 ),
252 'items' => array(
253 'type' => 'array',
254 'items' => array(
255 'type' => 'object',
256 ),
257 ),
258 ),
259 );
260
261 $this->schema = $schema;
262
263 return $this->add_additional_fields_schema( $this->schema );
264 }
265 }
266