PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.7.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 / class-query.php

class-query.php in ActivityPub 5.7.0, at includes/class-query.php

352 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Query class.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Collection\Actors;
11 use Activitypub\Collection\Outbox;
12 use Activitypub\Transformer\Factory;
13
14 /**
15 * Singleton class to handle and store the ActivityPub query.
16 */
17 class Query {
18
19 /**
20 * The singleton instance.
21 *
22 * @var Query
23 */
24 private static $instance;
25
26 /**
27 * The ActivityPub object.
28 *
29 * @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object
30 *
31 * @var object
32 */
33 private $activitypub_object;
34
35 /**
36 * The ActivityPub object ID.
37 *
38 * @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-id
39 *
40 * @var string
41 */
42 private $activitypub_object_id;
43
44 /**
45 * Whether the current request is an ActivityPub request.
46 *
47 * @var bool
48 */
49 private $is_activitypub_request;
50
51 /**
52 * Whether the current request is from the old host.
53 *
54 * @var bool
55 */
56 private $is_old_host_request;
57
58 /**
59 * The constructor.
60 */
61 private function __construct() {
62 // Do nothing.
63 }
64
65 /**
66 * The destructor.
67 */
68 public function __destruct() {
69 self::$instance = null;
70 }
71
72 /**
73 * Get the singleton instance.
74 *
75 * @return Query The singleton instance.
76 */
77 public static function get_instance() {
78 if ( ! isset( self::$instance ) ) {
79 self::$instance = new self();
80 }
81
82 return self::$instance;
83 }
84
85 /**
86 * Get the ActivityPub object.
87 *
88 * @return object The ActivityPub object.
89 */
90 public function get_activitypub_object() {
91 if ( $this->activitypub_object ) {
92 return $this->activitypub_object;
93 }
94
95 if ( $this->prepare_activitypub_data() ) {
96 return $this->activitypub_object;
97 }
98
99 $queried_object = $this->get_queried_object();
100 $transformer = Factory::get_transformer( $queried_object );
101
102 if ( $transformer && ! \is_wp_error( $transformer ) ) {
103 $this->activitypub_object = $transformer->to_object();
104 }
105
106 return $this->activitypub_object;
107 }
108
109 /**
110 * Get the ActivityPub object ID.
111 *
112 * @return string The ActivityPub object ID.
113 */
114 public function get_activitypub_object_id() {
115 if ( $this->activitypub_object_id ) {
116 return $this->activitypub_object_id;
117 }
118
119 if ( $this->prepare_activitypub_data() ) {
120 return $this->activitypub_object_id;
121 }
122
123 $queried_object = $this->get_queried_object();
124 $transformer = Factory::get_transformer( $queried_object );
125
126 if ( $transformer && ! \is_wp_error( $transformer ) ) {
127 $this->activitypub_object_id = $transformer->to_id();
128 }
129
130 return $this->activitypub_object_id;
131 }
132
133 /**
134 * Prepare and set both ActivityPub object and ID for Outbox activities and virtual objects.
135 *
136 * @return bool True if an object was found and set, false otherwise.
137 */
138 private function prepare_activitypub_data() {
139 $queried_object = $this->get_queried_object();
140
141 // Check for Outbox Activity.
142 if (
143 $queried_object instanceof \WP_Post &&
144 Outbox::POST_TYPE === $queried_object->post_type
145 ) {
146 $activitypub_object = Outbox::maybe_get_activity( $queried_object );
147
148 // Check if the Outbox Activity is public.
149 if ( ! \is_wp_error( $activitypub_object ) ) {
150 $this->activitypub_object = $activitypub_object;
151 $this->activitypub_object_id = $this->activitypub_object->get_id();
152 return true;
153 }
154 }
155
156 if ( ! $queried_object ) {
157 // If the object is not a valid ActivityPub object, try to get a virtual object.
158 $activitypub_object = $this->maybe_get_virtual_object();
159
160 if ( $activitypub_object ) {
161 $this->activitypub_object = $activitypub_object;
162 $this->activitypub_object_id = $this->activitypub_object->get_id();
163 return true;
164 }
165 }
166
167 return false;
168 }
169
170 /**
171 * Get the queried object.
172 *
173 * This adds support for Comments by `?c=123` IDs and Users by `?author=123` and `@username` IDs.
174 *
175 * @return \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null The queried object.
176 */
177 public function get_queried_object() {
178 $queried_object = \get_queried_object();
179
180 // Check Comment by ID.
181 if ( ! $queried_object ) {
182 $comment_id = \get_query_var( 'c' );
183 if ( $comment_id ) {
184 $queried_object = \get_comment( $comment_id );
185 }
186 }
187
188 // Check Post by ID (works for custom post types).
189 if ( ! $queried_object ) {
190 $post_id = \get_query_var( 'p' );
191 if ( $post_id ) {
192 $queried_object = \get_post( $post_id );
193 }
194 }
195
196 // Try to get Author by ID.
197 if ( ! $queried_object ) {
198 $url = $this->get_request_url();
199 $author_id = url_to_authorid( $url );
200 if ( $author_id ) {
201 $queried_object = \get_user_by( 'id', $author_id );
202 }
203 }
204
205 /**
206 * Filters the queried object.
207 *
208 * @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null $queried_object The queried object.
209 */
210 return apply_filters( 'activitypub_queried_object', $queried_object );
211 }
212
213 /**
214 * Get the virtual object.
215 *
216 * Virtual objects are objects that are not stored in the database, but are created on the fly.
217 * The plugins currently supports two virtual objects: The Blog-Actor and the Application-Actor.
218 *
219 * @see \Activitypub\Model\Blog
220 * @see \Activitypub\Model\Application
221 *
222 * @return object|null The virtual object.
223 */
224 protected function maybe_get_virtual_object() {
225 $url = $this->get_request_url();
226
227 if ( ! $url ) {
228 return null;
229 }
230
231 $author_id = url_to_authorid( $url );
232
233 if ( ! is_numeric( $author_id ) ) {
234 $author_id = $url;
235 }
236
237 $user = Actors::get_by_various( $author_id );
238
239 if ( \is_wp_error( $user ) || ! $user ) {
240 return null;
241 }
242
243 return $user;
244 }
245
246 /**
247 * Get the request URL.
248 *
249 * @return string|null The request URL.
250 */
251 protected function get_request_url() {
252 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
253 return null;
254 }
255
256 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
257 $url = \wp_unslash( $_SERVER['REQUEST_URI'] );
258 $url = \WP_Http::make_absolute_url( $url, \home_url() );
259 $url = \sanitize_url( $url );
260
261 return $url;
262 }
263
264 /**
265 * Check if the current request is an ActivityPub request.
266 *
267 * @return bool True if the request is an ActivityPub request, false otherwise.
268 */
269 public function is_activitypub_request() {
270 if ( isset( $this->is_activitypub_request ) ) {
271 return $this->is_activitypub_request;
272 }
273
274 global $wp_query;
275
276 // One can trigger an ActivityPub request by adding `?activitypub` to the URL.
277 if (
278 isset( $wp_query->query_vars['activitypub'] ) ||
279 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
280 isset( $_GET['activitypub'] )
281 ) {
282 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
283 $this->is_activitypub_request = true;
284
285 return true;
286 }
287
288 /*
289 * The other (more common) option to make an ActivityPub request
290 * is to send an Accept header.
291 */
292 if ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
293 $accept = \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
294
295 /*
296 * $accept can be a single value, or a comma separated list of values.
297 * We want to support both scenarios,
298 * and return true when the header includes at least one of the following:
299 * - application/activity+json
300 * - application/ld+json
301 * - application/json
302 */
303 if ( \preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
304 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
305 $this->is_activitypub_request = true;
306
307 return true;
308 }
309 }
310
311 $this->is_activitypub_request = false;
312
313 return false;
314 }
315
316 /**
317 * Check if the current request is from the old host.
318 *
319 * @return bool True if the request is from the old host, false otherwise.
320 */
321 public function is_old_host_request() {
322 if ( isset( $this->is_old_host_request ) ) {
323 return $this->is_old_host_request;
324 }
325
326 $old_host = \get_option( 'activitypub_old_host' );
327
328 if ( ! $old_host ) {
329 $this->is_old_host_request = false;
330 return false;
331 }
332
333 $request_host = isset( $_SERVER['HTTP_HOST'] ) ? \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
334 $referer_host = isset( $_SERVER['HTTP_REFERER'] ) ? \wp_parse_url( \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_REFERER'] ) ), PHP_URL_HOST ) : '';
335
336 // Check if the domain matches either the request domain or referer.
337 $check = $old_host === $request_host || $old_host === $referer_host;
338 $this->is_old_host_request = $check;
339
340 return $check;
341 }
342
343 /**
344 * Fake an old host request.
345 *
346 * @param bool $state Optional. The state to set. Default true.
347 */
348 public function set_old_host_request( $state = true ) {
349 $this->is_old_host_request = $state;
350 }
351 }
352