PluginProbe
ActivityPub / 7.6.1
ActivityPub v7.6.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 / handler / class-quote-request.php

class-quote-request.php in ActivityPub 7.6.1, at includes/handler/class-quote-request.php

233 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 * Handler for QuoteRequest activities.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Handler;
9
10 use Activitypub\Activity\Activity;
11 use Activitypub\Collection\Actors;
12 use Activitypub\Collection\Followers;
13 use Activitypub\Collection\Remote_Actors;
14
15 use function Activitypub\add_to_outbox;
16 use function Activitypub\object_to_uri;
17
18 /**
19 * Handler for QuoteRequest activities.
20 *
21 * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md
22 */
23 class Quote_Request {
24 /**
25 * Initialize the class, registering WordPress hooks.
26 */
27 public static function init() {
28 \add_action( 'activitypub_inbox_quote_request', array( self::class, 'handle_quote_request' ), 10, 2 );
29 \add_action( 'activitypub_rest_inbox_disallowed', array( self::class, 'handle_blocked_request' ), 10, 3 );
30
31 \add_filter( 'activitypub_validate_object', array( self::class, 'validate_object' ), 10, 3 );
32 }
33
34 /**
35 * Handle QuoteRequest activities.
36 *
37 * @param array $activity The activity object.
38 * @param int|int[] $user_ids The user ID(s).
39 */
40 public static function handle_quote_request( $activity, $user_ids ) {
41 // Extract the user ID (quote requests are always for a single user).
42 $user_id = \is_array( $user_ids ) ? \reset( $user_ids ) : $user_ids;
43
44 $state = true;
45 $post_id = \url_to_postid( object_to_uri( $activity['object'] ) );
46
47 if ( ! $post_id ) {
48 self::queue_reject( $activity, $user_id );
49 return;
50 }
51
52 $content_policy = \get_post_meta( $post_id, 'activitypub_interaction_policy_quote', true );
53
54 switch ( $content_policy ) {
55 case ACTIVITYPUB_INTERACTION_POLICY_ME:
56 self::queue_reject( $activity, $user_id );
57 $state = false;
58 break;
59 case ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS:
60 $follower = Remote_Actors::get_by_uri( object_to_uri( $activity['actor'] ) );
61 if ( ! \is_wp_error( $follower ) && Followers::follows( $follower->ID, $user_id ) ) {
62 self::queue_accept( $activity, $user_id, $post_id );
63 } else {
64 self::queue_reject( $activity, $user_id );
65 $state = false;
66 }
67 break;
68 case ACTIVITYPUB_INTERACTION_POLICY_ANYONE:
69 default:
70 self::queue_accept( $activity, $user_id, $post_id );
71 break;
72 }
73
74 /**
75 * Fires after an ActivityPub QuoteRequest activity has been handled.
76 *
77 * @param array $activity The ActivityPub activity data.
78 * @param int[] $user_ids The local user IDs.
79 * @param bool $success True on success, false otherwise.
80 * @param string $content_policy The content policy for the quoted post.
81 */
82 \do_action( 'activitypub_handled_quote_request', $activity, (array) $user_ids, $state, $content_policy );
83 }
84
85 /**
86 * ActivityPub inbox disallowed activity.
87 *
88 * @param array $activity The activity array.
89 * @param int|int[]|null $user_ids The user ID(s).
90 * @param string $type The type of the activity.
91 */
92 public static function handle_blocked_request( $activity, $user_ids, $type ) {
93 if ( 'quoterequest' !== \strtolower( $type ) ) {
94 return;
95 }
96
97 // Extract the user ID (quote requests are always for a single user).
98 $user_id = \is_array( $user_ids ) ? \reset( $user_ids ) : $user_ids;
99
100 self::queue_reject( $activity, $user_id );
101 }
102
103 /**
104 * Send an Accept activity in response to the QuoteRequest.
105 *
106 * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md#example-accept
107 *
108 * @param array $activity_object The activity object.
109 * @param int $user_id The user ID.
110 * @param int $post_id The post ID.
111 */
112 public static function queue_accept( $activity_object, $user_id, $post_id ) {
113 $actor = Actors::get_by_id( $user_id );
114
115 if ( \is_wp_error( $actor ) ) {
116 return;
117 }
118
119 $activity_object['instrument'] = object_to_uri( $activity_object['instrument'] );
120
121 $post_meta = \get_post_meta( $post_id, '_activitypub_quoted_by', false );
122 if ( in_array( $activity_object['instrument'], $post_meta, true ) ) {
123 global $wpdb;
124
125 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
126 $meta_id = $wpdb->get_var(
127 $wpdb->prepare(
128 "SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_value = %s LIMIT 1",
129 $post_id,
130 '_activitypub_quoted_by',
131 $activity_object['instrument']
132 )
133 );
134 } else {
135 $meta_id = \add_post_meta( $post_id, '_activitypub_quoted_by', $activity_object['instrument'] );
136 }
137
138 // Only send minimal data.
139 $activity_object = array_intersect_key(
140 $activity_object,
141 array(
142 'id' => 1,
143 'type' => 1,
144 'actor' => 1,
145 'object' => 1,
146 'instrument' => 1,
147 )
148 );
149
150 $url = \add_query_arg(
151 array(
152 'p' => $post_id,
153 'stamp' => $meta_id,
154 ),
155 \home_url( '/' )
156 );
157
158 $activity = new Activity();
159 $activity->set_type( 'Accept' );
160 $activity->set_actor( $actor->get_id() );
161 $activity->set_object( $activity_object );
162 $activity->set_result( $url );
163 $activity->add_to( object_to_uri( $activity_object['actor'] ) );
164
165 add_to_outbox( $activity, null, $user_id, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE );
166 }
167
168 /**
169 * Send a Reject activity in response to the QuoteRequest.
170 *
171 * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md#example-reject
172 *
173 * @param array $activity_object The activity object.
174 * @param int $user_id The user ID.
175 */
176 public static function queue_reject( $activity_object, $user_id ) {
177 $actor = Actors::get_by_id( $user_id );
178
179 if ( \is_wp_error( $actor ) ) {
180 return;
181 }
182
183 $activity_object['instrument'] = object_to_uri( $activity_object['instrument'] );
184
185 // Only send minimal data.
186 $activity_object = array_intersect_key(
187 $activity_object,
188 array(
189 'id' => 1,
190 'type' => 1,
191 'actor' => 1,
192 'object' => 1,
193 'instrument' => 1,
194 )
195 );
196
197 $activity = new Activity();
198 $activity->set_type( 'Reject' );
199 $activity->set_actor( $actor->get_id() );
200 $activity->set_object( $activity_object );
201 $activity->add_to( object_to_uri( $activity_object['actor'] ) );
202
203 add_to_outbox( $activity, null, $user_id, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE );
204 }
205
206 /**
207 * Validate the object.
208 *
209 * @param bool $valid The validation state.
210 * @param string $param The object parameter.
211 * @param \WP_REST_Request $request The request object.
212 *
213 * @return bool The validation state: true if valid, false if not.
214 */
215 public static function validate_object( $valid, $param, $request ) {
216 $activity = $request->get_json_params();
217
218 if ( empty( $activity['type'] ) ) {
219 return false;
220 }
221
222 if ( 'QuoteRequest' !== $activity['type'] ) {
223 return $valid;
224 }
225
226 if ( ! isset( $activity['actor'], $activity['object'], $activity['instrument'] ) ) {
227 return false;
228 }
229
230 return $valid;
231 }
232 }
233