PluginProbe
ActivityPub / 7.8.4
ActivityPub v7.8.4
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 7.8.4, at includes/class-query.php

431 lines 11.0 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\Activity\Extended_Object\Quote_Authorization;
11 use Activitypub\Collection\Actors;
12 use Activitypub\Collection\Outbox;
13 use Activitypub\Transformer\Factory;
14
15 /**
16 * Singleton class to handle and store the ActivityPub query.
17 */
18 class Query {
19
20 /**
21 * The singleton instance.
22 *
23 * @var Query
24 */
25 private static $instance;
26
27 /**
28 * The ActivityPub object.
29 *
30 * @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object
31 *
32 * @var object
33 */
34 private $activitypub_object;
35
36 /**
37 * The ActivityPub object ID.
38 *
39 * @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-id
40 *
41 * @var string
42 */
43 private $activitypub_object_id;
44
45 /**
46 * Whether the current request is an ActivityPub request.
47 *
48 * @var bool
49 */
50 private $is_activitypub_request;
51
52 /**
53 * Whether the current request is from the old host.
54 *
55 * @var bool
56 */
57 private $is_old_host_request;
58
59 /**
60 * The constructor.
61 */
62 private function __construct() {
63 // Do nothing.
64 }
65
66 /**
67 * The destructor.
68 */
69 public function __destruct() {
70 self::$instance = null;
71 }
72
73 /**
74 * Get the singleton instance.
75 *
76 * @return Query The singleton instance.
77 */
78 public static function get_instance() {
79 if ( ! isset( self::$instance ) ) {
80 self::$instance = new self();
81 }
82
83 return self::$instance;
84 }
85
86 /**
87 * Get the ActivityPub object.
88 *
89 * @return object The ActivityPub object.
90 */
91 public function get_activitypub_object() {
92 if ( $this->activitypub_object ) {
93 return $this->activitypub_object;
94 }
95
96 if ( $this->prepare_activitypub_data() ) {
97 return $this->activitypub_object;
98 }
99
100 $queried_object = $this->get_queried_object();
101 $transformer = Factory::get_transformer( $queried_object );
102
103 if ( $transformer && ! \is_wp_error( $transformer ) ) {
104 $this->activitypub_object = $transformer->to_object();
105 }
106
107 return $this->activitypub_object;
108 }
109
110 /**
111 * Get the ActivityPub object ID.
112 *
113 * @return string The ActivityPub object ID.
114 */
115 public function get_activitypub_object_id() {
116 if ( $this->activitypub_object_id ) {
117 return $this->activitypub_object_id;
118 }
119
120 if ( $this->prepare_activitypub_data() ) {
121 return $this->activitypub_object_id;
122 }
123
124 $queried_object = $this->get_queried_object();
125 $transformer = Factory::get_transformer( $queried_object );
126
127 if ( $transformer && ! \is_wp_error( $transformer ) ) {
128 $this->activitypub_object_id = $transformer->to_id();
129 }
130
131 return $this->activitypub_object_id;
132 }
133
134 /**
135 * Prepare and set both ActivityPub object and ID for Outbox activities and virtual objects.
136 *
137 * @return bool True if an object was found and set, false otherwise.
138 */
139 private function prepare_activitypub_data() {
140 $queried_object = $this->get_queried_object();
141
142 if ( $queried_object instanceof \WP_Post && \get_query_var( 'stamp' ) ) {
143 return $this->maybe_get_stamp();
144 }
145
146 // Check for Outbox Activity.
147 if (
148 $queried_object instanceof \WP_Post &&
149 Outbox::POST_TYPE === $queried_object->post_type
150 ) {
151 $activitypub_object = Outbox::maybe_get_activity( $queried_object );
152
153 // Check if the Outbox Activity is public.
154 if ( ! \is_wp_error( $activitypub_object ) ) {
155 $this->activitypub_object = $activitypub_object;
156 $this->activitypub_object_id = $this->activitypub_object->get_id();
157 return true;
158 }
159 }
160
161 if ( ! $queried_object ) {
162 // If the object is not a valid ActivityPub object, try to get a virtual object.
163 $activitypub_object = $this->maybe_get_virtual_object();
164
165 if ( $activitypub_object ) {
166 $this->activitypub_object = $activitypub_object;
167 $this->activitypub_object_id = $this->activitypub_object->get_id();
168 return true;
169 }
170 }
171
172 return false;
173 }
174
175 /**
176 * Get the queried object.
177 *
178 * This adds support for Comments by `?c=123` IDs and Users by `?author=123` and `@username` IDs.
179 *
180 * @return \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null The queried object.
181 */
182 public function get_queried_object() {
183 $queried_object = \get_queried_object();
184
185 // Check Comment by ID.
186 if ( ! $queried_object ) {
187 $comment_id = \get_query_var( 'c' );
188 if ( $comment_id ) {
189 $queried_object = \get_comment( $comment_id );
190 }
191 }
192
193 // Check Post by ID (works for custom post types).
194 if ( ! $queried_object ) {
195 $post_id = \get_query_var( 'p' );
196 if ( $post_id ) {
197 $queried_object = \get_post( $post_id );
198 }
199 }
200
201 // Check Term by ID.
202 if ( ! $queried_object ) {
203 $term_id = \get_query_var( 'term_id' );
204 if ( $term_id ) {
205 $queried_object = \get_term( $term_id );
206 }
207 }
208
209 // Try to get Author by ID.
210 if ( ! $queried_object ) {
211 $url = $this->get_request_url();
212 $author_id = url_to_authorid( $url );
213 if ( $author_id ) {
214 $queried_object = \get_user_by( 'id', $author_id );
215 }
216 }
217
218 /**
219 * Filters the queried object.
220 *
221 * @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null $queried_object The queried object.
222 */
223 return apply_filters( 'activitypub_queried_object', $queried_object );
224 }
225
226 /**
227 * Get the virtual object.
228 *
229 * Virtual objects are objects that are not stored in the database, but are created on the fly.
230 * The plugins currently supports two virtual objects: The Blog-Actor and the Application-Actor.
231 *
232 * @see \Activitypub\Model\Blog
233 * @see \Activitypub\Model\Application
234 *
235 * @return object|null The virtual object.
236 */
237 protected function maybe_get_virtual_object() {
238 $url = $this->get_request_url();
239
240 if ( ! $url ) {
241 return null;
242 }
243
244 $author_id = url_to_authorid( $url );
245
246 if ( ! is_numeric( $author_id ) ) {
247 $author_id = $url;
248 }
249
250 $user = Actors::get_by_various( $author_id );
251
252 if ( \is_wp_error( $user ) || ! $user ) {
253 return null;
254 }
255
256 return $user;
257 }
258
259 /**
260 * Get the request URL.
261 *
262 * @return string|null The request URL.
263 */
264 public function get_request_url() {
265 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
266 return null;
267 }
268
269 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
270 $url = \wp_unslash( $_SERVER['REQUEST_URI'] );
271 $url = \WP_Http::make_absolute_url( $url, \home_url() );
272 $url = \sanitize_url( $url );
273
274 return $url;
275 }
276
277 /**
278 * Check if the current request is an ActivityPub request.
279 *
280 * @return bool True if the request is an ActivityPub request, false otherwise.
281 */
282 public function is_activitypub_request() {
283 if ( ! isset( $this->is_activitypub_request ) ) {
284 global $wp_query;
285
286 $this->is_activitypub_request = false;
287
288 // One can trigger an ActivityPub request by adding `?activitypub` to the URL.
289 if ( isset( $wp_query->query_vars['activitypub'] ) || isset( $_GET['activitypub'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
290 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
291 $this->is_activitypub_request = true;
292
293 // The other (more common) option to make an ActivityPub request is to send an Accept header.
294 } elseif ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
295 $accept = \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
296
297 /*
298 * $accept can be a single value, or a comma separated list of values.
299 * We want to support both scenarios,
300 * and return true when the header includes at least one of the following:
301 * - application/activity+json
302 * - application/ld+json
303 * - application/json
304 */
305 if ( \preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
306 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
307 $this->is_activitypub_request = true;
308 }
309 }
310 }
311
312 /**
313 * Filters whether the current request is an ActivityPub request.
314 *
315 * @param bool $is_activitypub_request True if the request is an ActivityPub request, false otherwise.
316 */
317 return \apply_filters( 'activitypub_is_activitypub_request', $this->is_activitypub_request );
318 }
319
320 /**
321 * Check if content negotiation is allowed for a request.
322 *
323 * @return bool True if content negotiation is allowed, false otherwise.
324 */
325 public function should_negotiate_content() {
326 $return = false;
327 $always_negotiate = array( 'p', 'c', 'author', 'actor', 'stamp', 'preview', 'activitypub' );
328 $url = \wp_parse_url( $this->get_request_url(), PHP_URL_QUERY );
329 $query = array();
330 \wp_parse_str( $url, $query );
331
332 // Check if any of the query params are in the `$always_negotiate` array.
333 if ( \array_intersect( \array_keys( $query ), $always_negotiate ) ) {
334 $return = true;
335 }
336
337 if ( \get_option( 'activitypub_content_negotiation', '1' ) ) {
338 $return = true;
339 }
340
341 if ( \is_author() && \get_user_option( 'activitypub_use_permalink_as_id', \get_queried_object_id() ) ) {
342 $return = true;
343 }
344
345 /**
346 * Filters whether content negotiation should be forced.
347 *
348 * @param bool $return Whether content negotiation should be forced.
349 */
350 return \apply_filters( 'activitypub_should_negotiate_content', $return );
351 }
352
353 /**
354 * Check if the current request is from the old host.
355 *
356 * @return bool True if the request is from the old host, false otherwise.
357 */
358 public function is_old_host_request() {
359 if ( isset( $this->is_old_host_request ) ) {
360 return $this->is_old_host_request;
361 }
362
363 $old_host = \get_option( 'activitypub_old_host' );
364
365 if ( ! $old_host ) {
366 $this->is_old_host_request = false;
367 return false;
368 }
369
370 $request_host = isset( $_SERVER['HTTP_HOST'] ) ? \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
371 $referer_host = isset( $_SERVER['HTTP_REFERER'] ) ? \wp_parse_url( \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_REFERER'] ) ), PHP_URL_HOST ) : '';
372
373 // Check if the domain matches either the request domain or referer.
374 $check = $old_host === $request_host || $old_host === $referer_host;
375 $this->is_old_host_request = $check;
376
377 return $check;
378 }
379
380 /**
381 * Fake an old host request.
382 *
383 * @param bool $state Optional. The state to set. Default true.
384 */
385 public function set_old_host_request( $state = true ) {
386 $this->is_old_host_request = $state;
387 }
388
389 /**
390 * Maybe get a QuoteAuthorization object from a stamp.
391 *
392 * @return bool True if the object was prepared, false otherwise.
393 */
394 private function maybe_get_stamp() {
395 require_once ABSPATH . 'wp-admin/includes/post.php';
396
397 $stamp = \get_query_var( 'stamp' );
398 $meta = \get_post_meta_by_id( (int) $stamp );
399
400 if ( ! $meta ) {
401 return false;
402 }
403
404 $post = $this->get_queried_object();
405 $user_uri = get_user_id( $post->post_author );
406
407 if ( ! $user_uri ) {
408 return false;
409 }
410
411 $stamp_uri = \add_query_arg(
412 array(
413 'p' => $post->ID,
414 'stamp' => $meta->meta_id,
415 ),
416 \home_url( '/' )
417 );
418
419 $activitypub_object = new Quote_Authorization();
420 $activitypub_object->set_id( $stamp_uri );
421 $activitypub_object->set_attributed_to( $user_uri );
422 $activitypub_object->set_interacting_object( $meta->meta_value );
423 $activitypub_object->set_interaction_target( get_post_id( $post->ID ) );
424
425 $this->activitypub_object = $activitypub_object;
426 $this->activitypub_object_id = $activitypub_object->get_id();
427
428 return true;
429 }
430 }
431