PluginProbe
ActivityPub / 9.2.0
ActivityPub v9.2.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-post-controller.php

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

267 lines 6.7 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_raw()`
169 * rejects `javascript:` and other unsafe schemes without
170 * HTML-encoding ampersands (this is JSON, not markup).
171 */
172 return array(
173 'name' => \wp_strip_all_tags( \html_entity_decode( $comment->comment_author, ENT_QUOTES ) ),
174 'url' => \esc_url_raw( $comment->comment_author_url ),
175 'avatar' => \esc_url_raw( \get_avatar_url( $comment ) ),
176 );
177 },
178 $comments
179 ),
180 );
181 }
182
183 return new \WP_REST_Response( $reactions );
184 }
185
186 /**
187 * Get the context for a post.
188 *
189 * @param \WP_REST_Request $request The request.
190 *
191 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
192 */
193 public function get_context( $request ) {
194 $post_id = $request->get_param( 'id' );
195 $collection = Replies::get_context_collection( $post_id );
196
197 if ( false === $collection ) {
198 return new \WP_Error( 'activitypub_post_not_found', \__( 'Post not found', 'activitypub' ), array( 'status' => 404 ) );
199 }
200
201 $response = \array_merge(
202 array(
203 '@context' => Base_Object::JSON_LD_CONTEXT,
204 'id' => get_rest_url_by_path( \sprintf( 'posts/%d/context', $post_id ) ),
205 ),
206 $collection
207 );
208
209 $response = \rest_ensure_response( $response );
210 $response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
211
212 return $response;
213 }
214
215 /**
216 * Get the remote intent template for a post.
217 *
218 * @since 8.0.0
219 *
220 * @param \WP_REST_Request $request The request.
221 *
222 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
223 */
224 public function get_remote_intent_template( $request ) {
225 $post_id = $request->get_param( 'id' );
226 $resource = $request->get_param( 'resource' );
227 $intent = $request->get_param( 'intent' );
228 $post = \get_post( $post_id );
229
230 $template = Webfinger::get_intent_endpoint( $resource, $intent, true );
231
232 if ( \is_wp_error( $template ) ) {
233 return $template;
234 }
235
236 $id = get_post_id( $post_id );
237
238 $url = \str_replace(
239 array(
240 '{object}',
241 '{uri}',
242 '{inReplyTo}',
243 '{name}',
244 '{target}',
245 ),
246 array(
247 \rawurlencode( $id ),
248 \rawurlencode( $id ),
249 \rawurlencode( $id ),
250 \rawurlencode( $post->post_title ),
251 \rawurlencode( $resource ),
252 ),
253 $template
254 );
255
256 // Remove any other GET-Params with placeholders to avoid confusion.
257 $url = \preg_replace( '/([&?][^=]+=\{[^}]+\})/', '', $url );
258
259 return \rest_ensure_response(
260 array(
261 'url' => $url,
262 'template' => $template,
263 )
264 );
265 }
266 }
267