PluginProbe
ActivityPub / 9.0.0
ActivityPub v9.0.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 / handler / class-feature-request.php

class-feature-request.php in ActivityPub 9.0.0, at includes/handler/class-feature-request.php

241 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handler for FeatureRequest activities (FEP-7aa9).
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 use function Activitypub\user_can_activitypub;
18
19 /**
20 * Handler for FeatureRequest activities.
21 *
22 * @see https://w3id.org/fep/7aa9
23 */
24 class Feature_Request {
25 /**
26 * Initialize the class, registering WordPress hooks.
27 */
28 public static function init() {
29 \add_action( 'activitypub_inbox_feature_request', array( self::class, 'handle_feature_request' ), 10, 2 );
30 \add_action( 'activitypub_rest_inbox_disallowed', array( self::class, 'handle_blocked_request' ), 10, 3 );
31
32 \add_filter( 'activitypub_validate_object', array( self::class, 'validate_object' ), 10, 3 );
33 }
34
35 /**
36 * Handle FeatureRequest activities.
37 *
38 * @param array $activity The activity object.
39 * @param int|int[] $user_ids The user ID(s) targeted by the inbox dispatch.
40 */
41 public static function handle_feature_request( $activity, $user_ids ) {
42 $state = true;
43 $object_uri = object_to_uri( $activity['object'] );
44 $target = Actors::get_by_resource( $object_uri );
45
46 if ( \is_wp_error( $target ) ) {
47 $user_id = \is_array( $user_ids ) ? \reset( $user_ids ) : $user_ids;
48 self::queue_reject( $activity, $user_id );
49 return;
50 }
51
52 $user_id = $target->get__id();
53
54 $policy = \get_option( 'activitypub_default_feature_policy', ACTIVITYPUB_INTERACTION_POLICY_ME );
55
56 switch ( $policy ) {
57 case ACTIVITYPUB_INTERACTION_POLICY_ANYONE:
58 self::queue_accept( $activity, $user_id );
59 break;
60
61 case ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS:
62 $follower = Remote_Actors::get_by_uri( object_to_uri( $activity['actor'] ) );
63 if ( ! \is_wp_error( $follower ) && Followers::follows( $follower->ID, $user_id ) ) {
64 self::queue_accept( $activity, $user_id );
65 } else {
66 self::queue_reject( $activity, $user_id );
67 $state = false;
68 }
69 break;
70
71 case ACTIVITYPUB_INTERACTION_POLICY_ME:
72 default:
73 self::queue_reject( $activity, $user_id );
74 $state = false;
75 break;
76 }
77
78 /**
79 * Fires after an ActivityPub FeatureRequest activity has been handled.
80 *
81 * @param array $activity The ActivityPub activity data.
82 * @param int[] $user_ids The local user IDs.
83 * @param bool $state True on accept, false otherwise.
84 * @param string $policy The active site policy.
85 */
86 \do_action( 'activitypub_handled_feature_request', $activity, (array) $user_ids, $state, $policy );
87 }
88
89 /**
90 * ActivityPub inbox disallowed activity.
91 *
92 * @param array $activity The activity array.
93 * @param int|int[]|null $user_ids The user ID(s).
94 * @param string $type The activity type.
95 */
96 public static function handle_blocked_request( $activity, $user_ids, $type ) {
97 if ( ! \in_array( \strtolower( $type ), array( 'featurerequest', 'feature_request' ), true ) ) {
98 return;
99 }
100
101 $user_id = \is_array( $user_ids ) ? \reset( $user_ids ) : $user_ids;
102 self::queue_reject( $activity, $user_id );
103 }
104
105 /**
106 * Send an Accept activity in response to the FeatureRequest, issuing a stamp.
107 *
108 * Idempotent: a second call with the same instrument for the same user reuses
109 * the existing umeta row instead of minting a duplicate stamp.
110 *
111 * @param array $activity_object The activity object.
112 * @param int $user_id The local user ID being featured.
113 */
114 public static function queue_accept( $activity_object, $user_id ) {
115 if ( ! user_can_activitypub( $user_id ) ) {
116 $user_id = Actors::BLOG_USER_ID;
117 }
118
119 $actor = Actors::get_by_id( $user_id );
120 if ( \is_wp_error( $actor ) ) {
121 return;
122 }
123
124 $activity_object['instrument'] = object_to_uri( $activity_object['instrument'] );
125
126 // Idempotent stamp creation.
127 $existing = \get_user_meta( $user_id, '_activitypub_featured_by', false );
128 if ( \in_array( $activity_object['instrument'], (array) $existing, true ) ) {
129 global $wpdb;
130 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
131 $umeta_id = $wpdb->get_var(
132 $wpdb->prepare(
133 "SELECT umeta_id FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key = %s AND meta_value = %s LIMIT 1",
134 $user_id,
135 '_activitypub_featured_by',
136 $activity_object['instrument']
137 )
138 );
139 } else {
140 $umeta_id = \add_user_meta( $user_id, '_activitypub_featured_by', $activity_object['instrument'] );
141 }
142
143 // Send minimal activity object back.
144 $activity_object = \array_intersect_key(
145 $activity_object,
146 array(
147 'id' => 1,
148 'type' => 1,
149 'actor' => 1,
150 'object' => 1,
151 'instrument' => 1,
152 )
153 );
154
155 $stamp_url = \add_query_arg(
156 array(
157 'actor' => $user_id,
158 'stamp' => $umeta_id,
159 ),
160 \home_url( '/' )
161 );
162
163 $activity = new Activity();
164 $activity->set_type( 'Accept' );
165 $activity->set_actor( $actor->get_id() );
166 $activity->set_object( $activity_object );
167 $activity->set_result( $stamp_url );
168 $activity->add_to( object_to_uri( $activity_object['actor'] ) );
169
170 add_to_outbox( $activity, null, $user_id, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE );
171 }
172
173 /**
174 * Send a Reject activity in response to the FeatureRequest.
175 *
176 * @param array $activity_object The activity object.
177 * @param int $user_id The user ID.
178 */
179 public static function queue_reject( $activity_object, $user_id ) {
180 if ( ! user_can_activitypub( $user_id ) ) {
181 $user_id = Actors::BLOG_USER_ID;
182 }
183
184 $actor = Actors::get_by_id( $user_id );
185 if ( \is_wp_error( $actor ) ) {
186 return;
187 }
188
189 if ( isset( $activity_object['instrument'] ) ) {
190 $activity_object['instrument'] = object_to_uri( $activity_object['instrument'] );
191 }
192
193 // Only send minimal data.
194 $activity_object = \array_intersect_key(
195 $activity_object,
196 array(
197 'id' => 1,
198 'type' => 1,
199 'actor' => 1,
200 'object' => 1,
201 'instrument' => 1,
202 )
203 );
204
205 $activity = new Activity();
206 $activity->set_type( 'Reject' );
207 $activity->set_actor( $actor->get_id() );
208 $activity->set_object( $activity_object );
209 $activity->add_to( object_to_uri( $activity_object['actor'] ) );
210
211 add_to_outbox( $activity, null, $user_id, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE );
212 }
213
214 /**
215 * Validate the object on incoming FeatureRequest activities.
216 *
217 * @param bool $valid The current validation state.
218 * @param string $param The object parameter name.
219 * @param \WP_REST_Request $request The request object.
220 *
221 * @return bool
222 */
223 public static function validate_object( $valid, $param, $request ) {
224 $activity = $request->get_json_params();
225
226 if ( empty( $activity['type'] ) ) {
227 return false;
228 }
229
230 if ( 'FeatureRequest' !== $activity['type'] ) {
231 return $valid;
232 }
233
234 if ( ! isset( $activity['actor'], $activity['object'], $activity['instrument'] ) ) {
235 return false;
236 }
237
238 return $valid;
239 }
240 }
241