PluginProbe
ActivityPub / 8.2.1
ActivityPub v8.2.1
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 8.2.1, at includes/rest/class-post-controller.php

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