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-post-controller.php

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

265 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ActivityPub Post REST Endpoints
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Rest;
9
10 use Activitypub\Activity\Base_Object;
11 use Activitypub\Collection\Replies;
12 use Activitypub\Comment;
13 use Activitypub\Sanitize;
14 use Activitypub\Webfinger;
15
16 use function Activitypub\get_post_id;
17 use function Activitypub\get_reaction_author_name;
18 use function Activitypub\get_rest_url_by_path;
19
20 /**
21 * Class Post_Controller
22 *
23 * @package Activitypub\Rest
24 */
25 class Post_Controller extends \WP_REST_Controller {
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 = 'posts/(?P<id>[\d]+)';
40
41 /**
42 * Register routes.
43 */
44 public function register_routes() {
45 \register_rest_route(
46 $this->namespace,
47 '/' . $this->rest_base . '/reactions',
48 array(
49 'args' => array(
50 'id' => array(
51 'required' => true,
52 'type' => 'integer',
53 'minimum' => 1,
54 'validate_callback' => 'Activitypub\is_post_publicly_queryable',
55 ),
56 ),
57 array(
58 'methods' => \WP_REST_Server::READABLE,
59 'callback' => array( $this, 'get_reactions' ),
60 'permission_callback' => '__return_true',
61 ),
62 )
63 );
64
65 \register_rest_route(
66 $this->namespace,
67 '/' . $this->rest_base . '/context',
68 array(
69 'args' => array(
70 'id' => array(
71 'required' => true,
72 'type' => 'integer',
73 'minimum' => 1,
74 'validate_callback' => 'Activitypub\is_post_publicly_queryable',
75 ),
76 ),
77 array(
78 'methods' => \WP_REST_Server::READABLE,
79 'callback' => array( $this, 'get_context' ),
80 'permission_callback' => '__return_true',
81 ),
82 )
83 );
84
85 \register_rest_route(
86 $this->namespace,
87 '/' . $this->rest_base . '/remote-intent',
88 array(
89 'args' => array(
90 'id' => array(
91 'description' => 'Unique identifier for the post.',
92 'type' => 'integer',
93 'minimum' => 1,
94 'required' => true,
95 'validate_callback' => 'Activitypub\is_post_publicly_queryable',
96 ),
97 ),
98 array(
99 'methods' => \WP_REST_Server::READABLE,
100 'callback' => array( $this, 'get_remote_intent_template' ),
101 'permission_callback' => '__return_true',
102 'args' => array(
103 'resource' => array(
104 'description' => 'The Fediverse profile handle or URL.',
105 'type' => 'string',
106 'required' => true,
107 'sanitize_callback' => array( Sanitize::class, 'webfinger' ),
108 ),
109 'intent' => array(
110 'description' => 'The intent type.',
111 'type' => 'string',
112 'default' => 'like',
113 'enum' => array( 'like', 'announce', 'create' ),
114 'sanitize_callback' => 'sanitize_text_field',
115 ),
116 ),
117 ),
118 )
119 );
120 }
121
122 /**
123 * Get reactions for a post.
124 *
125 * @param \WP_REST_Request $request The request.
126 *
127 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
128 */
129 public function get_reactions( $request ) {
130 $post_id = $request->get_param( 'id' );
131
132 $reactions = array();
133
134 foreach ( Comment::get_comment_types() as $type_object ) {
135 $comments = \get_comments(
136 array(
137 'post_id' => $post_id,
138 'type' => $type_object['type'],
139 'status' => 'approve',
140 'parent' => 0,
141 )
142 );
143
144 if ( empty( $comments ) ) {
145 continue;
146 }
147
148 $count = \count( $comments );
149 // phpcs:disable WordPress.WP.I18n
150 $label = \sprintf(
151 \_n(
152 $type_object['count_single'],
153 $type_object['count_plural'],
154 $count,
155 'activitypub'
156 ),
157 \number_format_i18n( $count )
158 );
159 // phpcs:enable WordPress.WP.I18n
160
161 $reactions[ $type_object['collection'] ] = array(
162 'label' => $label,
163 'items' => \array_map(
164 static function ( $comment ) {
165 /*
166 * `esc_url_raw()` rejects `javascript:` and other unsafe
167 * schemes without HTML-encoding ampersands (this is JSON,
168 * not markup).
169 */
170 return array(
171 'name' => get_reaction_author_name( $comment ),
172 'url' => \esc_url_raw( $comment->comment_author_url ),
173 'avatar' => \esc_url_raw( \get_avatar_url( $comment ) ),
174 );
175 },
176 $comments
177 ),
178 );
179 }
180
181 return new \WP_REST_Response( $reactions );
182 }
183
184 /**
185 * Get the context for a post.
186 *
187 * @param \WP_REST_Request $request The request.
188 *
189 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
190 */
191 public function get_context( $request ) {
192 $post_id = $request->get_param( 'id' );
193 $collection = Replies::get_context_collection( $post_id );
194
195 if ( false === $collection ) {
196 return new \WP_Error( 'activitypub_post_not_found', \__( 'Post not found', 'activitypub' ), array( 'status' => 404 ) );
197 }
198
199 $response = \array_merge(
200 array(
201 '@context' => Base_Object::JSON_LD_CONTEXT,
202 'id' => get_rest_url_by_path( \sprintf( 'posts/%d/context', $post_id ) ),
203 ),
204 $collection
205 );
206
207 $response = \rest_ensure_response( $response );
208 $response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
209
210 return $response;
211 }
212
213 /**
214 * Get the remote intent template for a post.
215 *
216 * @since 8.0.0
217 *
218 * @param \WP_REST_Request $request The request.
219 *
220 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
221 */
222 public function get_remote_intent_template( $request ) {
223 $post_id = $request->get_param( 'id' );
224 $resource = $request->get_param( 'resource' );
225 $intent = $request->get_param( 'intent' );
226 $post = \get_post( $post_id );
227
228 $template = Webfinger::get_intent_endpoint( $resource, $intent, true );
229
230 if ( \is_wp_error( $template ) ) {
231 return $template;
232 }
233
234 $id = get_post_id( $post_id );
235
236 $url = \str_replace(
237 array(
238 '{object}',
239 '{uri}',
240 '{inReplyTo}',
241 '{name}',
242 '{target}',
243 ),
244 array(
245 \rawurlencode( $id ),
246 \rawurlencode( $id ),
247 \rawurlencode( $id ),
248 \rawurlencode( $post->post_title ),
249 \rawurlencode( $resource ),
250 ),
251 $template
252 );
253
254 // Remove any other GET-Params with placeholders to avoid confusion.
255 $url = \preg_replace( '/([&?][^=]+=\{[^}]+\})/', '', $url );
256
257 return \rest_ensure_response(
258 array(
259 'url' => $url,
260 'template' => $template,
261 )
262 );
263 }
264 }
265