PluginProbe
ActivityPub / 7.0.1
ActivityPub v7.0.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 / class-query.php

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

372 lines 9.5 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 global $wp_query;
272
273 $this->is_activitypub_request = false;
274
275 // One can trigger an ActivityPub request by adding `?activitypub` to the URL.
276 if ( isset( $wp_query->query_vars['activitypub'] ) || isset( $_GET['activitypub'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
277 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
278 $this->is_activitypub_request = true;
279
280 // The other (more common) option to make an ActivityPub request is to send an Accept header.
281 } elseif ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
282 $accept = \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
283
284 /*
285 * $accept can be a single value, or a comma separated list of values.
286 * We want to support both scenarios,
287 * and return true when the header includes at least one of the following:
288 * - application/activity+json
289 * - application/ld+json
290 * - application/json
291 */
292 if ( \preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
293 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
294 $this->is_activitypub_request = true;
295 }
296 }
297 }
298
299 /**
300 * Filters whether the current request is an ActivityPub request.
301 *
302 * @param bool $is_activitypub_request True if the request is an ActivityPub request, false otherwise.
303 */
304 return \apply_filters( 'activitypub_is_activitypub_request', $this->is_activitypub_request );
305 }
306
307 /**
308 * Check if content negotiation is allowed for a request.
309 *
310 * @return bool True if content negotiation is allowed, false otherwise.
311 */
312 public function should_negotiate_content() {
313 $return = false;
314 $always_negotiate = array( 'p', 'c', 'author', 'actor', 'preview', 'activitypub' );
315 $url = \wp_parse_url( $this->get_request_url(), PHP_URL_QUERY );
316 $query = array();
317 \wp_parse_str( $url, $query );
318
319 // Check if any of the query params are in the `$always_negotiate` array.
320 if ( \array_intersect( \array_keys( $query ), $always_negotiate ) ) {
321 $return = true;
322 }
323
324 if ( \get_option( 'activitypub_content_negotiation', '1' ) ) {
325 $return = true;
326 }
327
328 /**
329 * Filters whether content negotiation should be forced.
330 *
331 * @param bool $return Whether content negotiation should be forced.
332 */
333 return \apply_filters( 'activitypub_should_negotiate_content', $return );
334 }
335
336 /**
337 * Check if the current request is from the old host.
338 *
339 * @return bool True if the request is from the old host, false otherwise.
340 */
341 public function is_old_host_request() {
342 if ( isset( $this->is_old_host_request ) ) {
343 return $this->is_old_host_request;
344 }
345
346 $old_host = \get_option( 'activitypub_old_host' );
347
348 if ( ! $old_host ) {
349 $this->is_old_host_request = false;
350 return false;
351 }
352
353 $request_host = isset( $_SERVER['HTTP_HOST'] ) ? \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
354 $referer_host = isset( $_SERVER['HTTP_REFERER'] ) ? \wp_parse_url( \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_REFERER'] ) ), PHP_URL_HOST ) : '';
355
356 // Check if the domain matches either the request domain or referer.
357 $check = $old_host === $request_host || $old_host === $referer_host;
358 $this->is_old_host_request = $check;
359
360 return $check;
361 }
362
363 /**
364 * Fake an old host request.
365 *
366 * @param bool $state Optional. The state to set. Default true.
367 */
368 public function set_old_host_request( $state = true ) {
369 $this->is_old_host_request = $state;
370 }
371 }
372