PluginProbe
Friends / 2.7.7
Friends v2.7.7
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
friends / includes / class-rest.php

class-rest.php in Friends 2.7.7, at includes/class-rest.php

468 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends REST
4 *
5 * This contains the functions for REST.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the REST part of the Friends Plugin.
14 *
15 * @since 0.6
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class REST {
21 const PREFIX = 'friends/v1';
22 /**
23 * Contains a reference to the Friends class.
24 *
25 * @var Friends
26 */
27 private $friends;
28
29 /**
30 * Constructor
31 *
32 * @param Friends $friends A reference to the Friends object.
33 */
34 public function __construct( Friends $friends ) {
35 $this->friends = $friends;
36 $this->register_hooks();
37 }
38
39 /**
40 * Register the WordPress hooks
41 */
42 private function register_hooks() {
43 add_action( 'rest_api_init', array( $this, 'add_rest_routes' ) );
44 add_action( 'wp_trash_post', array( $this, 'notify_remote_friend_post_deleted' ) );
45 add_action( 'before_delete_post', array( $this, 'notify_remote_friend_post_deleted' ) );
46 add_action( 'set_user_role', array( $this, 'notify_remote_friend_request_accepted' ), 20, 3 );
47 }
48
49 /**
50 * Add the REST API to send and receive friend requests
51 */
52 public function add_rest_routes() {
53 register_rest_route(
54 self::PREFIX,
55 'friend-request',
56 array(
57 'methods' => 'POST',
58 'callback' => array( $this, 'rest_friend_request' ),
59 'permission_callback' => '__return_true',
60 )
61 );
62 register_rest_route(
63 self::PREFIX,
64 'accept-friend-request',
65 array(
66 'methods' => 'POST',
67 'callback' => array( $this, 'rest_accept_friend_request' ),
68 'permission_callback' => '__return_true',
69 )
70 );
71 register_rest_route(
72 self::PREFIX,
73 'post-deleted',
74 array(
75 'methods' => 'POST',
76 'callback' => array( $this, 'rest_friend_post_deleted' ),
77 'permission_callback' => '__return_true',
78 )
79 );
80 register_rest_route(
81 self::PREFIX,
82 'embed',
83 array(
84 'methods' => 'GET',
85 'callback' => array( $this, 'rest_embed_friend_post' ),
86 'permission_callback' => '__return_true',
87 )
88 );
89 }
90
91 /**
92 * Receive a notification via REST that a friend request was accepted
93 *
94 * @param \WP_REST_Request $request The incoming request.
95 * @return array The array to be returned via the REST API.
96 */
97 public function rest_accept_friend_request( \WP_REST_Request $request ) {
98 $request_id = $request->get_param( 'request' );
99 $friend_user_id = get_option( 'friends_request_' . sha1( $request_id ) );
100 $friend_user = false;
101 if ( $friend_user_id ) {
102 $friend_user = new User( $friend_user_id );
103 }
104
105 if ( ! $request_id || ! $friend_user || is_wp_error( $friend_user ) || ! $friend_user->user_url ) {
106 return new \WP_Error(
107 'friends_invalid_parameters',
108 'Not all necessary parameters were provided.',
109 array(
110 'status' => 403,
111 )
112 );
113 }
114
115 $future_in_token = get_user_option( 'friends_future_in_token_' . sha1( $request_id ), $friend_user_id );
116 $proof = $request->get_param( 'proof' );
117 if ( ! $proof || sha1( $future_in_token . $request_id ) !== $proof ) {
118 return new \WP_Error(
119 'friends_invalid_proof',
120 'An invalid proof was provided.',
121 array(
122 'status' => 403,
123 )
124 );
125 }
126
127 $friend_user_login = User::get_user_login_for_url( $friend_user->user_url );
128 if ( ! $friend_user->has_cap( 'pending_friend_request' ) ) {
129 return new \WP_Error(
130 'friends_offer_no_longer_valid',
131 'The friendship offer is no longer valid.',
132 array(
133 'status' => 403,
134 )
135 );
136 }
137
138 $future_out_token = $request->get_param( 'key' );
139 if ( ! is_string( $future_out_token ) || empty( $future_out_token ) ) {
140 return new \WP_Error(
141 'friends_invalid_key',
142 'The key must be a non-empty string.',
143 array(
144 'status' => 403,
145 )
146 );
147 }
148 $friend_user->make_friend( $future_out_token, $future_in_token );
149
150 $friend_user->update_user_icon_url( $request->get_param( 'icon_url' ) );
151 if ( $request->get_param( 'name' ) ) {
152 wp_update_user(
153 array(
154 'ID' => $friend_user->ID,
155 'nickname' => $request->get_param( 'name' ),
156 'first_name' => $request->get_param( 'name' ),
157 'display_name' => $request->get_param( 'name' ),
158 )
159 );
160 }
161
162 delete_user_option( $friend_user_id, 'friends_future_in_token_' . sha1( $request_id ) );
163
164 do_action( 'notify_accepted_friend_request', $friend_user );
165 return array(
166 'signature' => sha1( $future_out_token . $future_in_token ),
167 );
168 }
169
170 /**
171 * Receive a friend request via REST
172 *
173 * @param \WP_REST_Request $request The incoming request.
174 * @return array The array to be returned via the REST API.
175 */
176 public function rest_friend_request( \WP_REST_Request $request ) {
177 $version = $request->get_param( 'version' );
178 if ( 2 !== intval( $version ) ) {
179 return new \WP_Error(
180 'friends_unsupported_protocol_version',
181 'Incompatible Friends protocol version.',
182 array(
183 'status' => 403,
184 )
185 );
186 }
187
188 $codeword = $request->get_param( 'codeword' );
189 if ( get_option( 'friends_require_codeword' ) && get_option( 'friends_codeword', 'friends' ) !== $codeword ) {
190 return new \WP_Error(
191 'friends_invalid_codeword',
192 get_option( 'friends_wrong_codeword_message', 'An invalid codeword was provided.' ),
193 array(
194 'status' => 403,
195 )
196 );
197 }
198
199 $url = trim( $request->get_param( 'url' ) );
200 if ( ! is_string( $url ) || ! Friends::check_url( $url ) || 0 === strcasecmp( home_url(), $url ) ) {
201 return new \WP_Error(
202 'friends_invalid_site',
203 'An invalid site was provided.',
204 array(
205 'status' => 403,
206 )
207 );
208 }
209
210 $future_out_token = $request->get_param( 'key' );
211 if ( ! is_string( $future_out_token ) || empty( $future_out_token ) ) {
212 return new \WP_Error(
213 'friends_invalid_key',
214 'The key must be a non-empty string.',
215 array(
216 'status' => 403,
217 )
218 );
219 }
220 $user_login = User::get_user_login_for_url( $url );
221 $friend_user = User::create( $user_login, 'friend_request', $url, $request->get_param( 'name' ), $request->get_param( 'icon_url' ) );
222 if ( $friend_user->has_cap( 'friend' ) ) {
223 if ( get_user_option( 'friends_out_token', $friend_user->ID ) && ! get_user_option( 'friends_out_token', $friend_user->ID ) ) {
224 // TODO: trigger an accept friend request right away?
225 }
226 $friend_user->set_role( 'friend_request' );
227 }
228 $friend_user->update_user_icon_url( $request->get_param( 'icon_url' ) );
229 $friend_user->update_user_option( 'friends_future_out_token', $request->get_param( 'key' ) );
230 $message = $request->get_param( 'message' );
231 if ( is_string( $message ) ) {
232 $friend_user->update_user_option( 'friends_request_message', mb_substr( $message, 0, 2000 ) );
233 }
234
235 $request_id = wp_generate_password( 128, false );
236 $friend_user->update_user_option( 'friends_request_id', $request_id );
237
238 return array(
239 'request' => $request_id,
240 );
241 }
242
243 /**
244 * Notify friends of a deleted post
245 *
246 * @param int $post_id The post id of the post that is deleted.
247 */
248 public function notify_remote_friend_post_deleted( $post_id ) {
249 $post = \WP_Post::get_instance( $post_id );
250 if ( 'post' !== $post->post_type ) {
251 return;
252 }
253
254 $friends = User_Query::all_friends();
255 $friends = $friends->get_results();
256
257 foreach ( $friends as $friend_user ) {
258 $friend_rest_url = $friend_user->get_rest_url();
259
260 $response = wp_safe_remote_post(
261 $friend_rest_url . '/post-deleted',
262 array(
263 'body' => array(
264 'post_id' => $post_id,
265 'auth' => $friend_user->get_friend_auth(),
266 ),
267 'timeout' => 20,
268 'redirection' => 5,
269 )
270 );
271 }
272 }
273
274 /**
275 * Receive a REST message that a post was deleted.
276 *
277 * @param \WP_REST_Request $request The incoming request.
278 * @return array The array to be returned via the REST API.
279 */
280 public function rest_friend_post_deleted( $request ) {
281 $tokens = explode( '-', $request->get_param( 'auth' ) );
282 $user_id = $this->friends->access_control->verify_token( $tokens[0], isset( $tokens[1] ) ? $tokens[1] : null, isset( $tokens[2] ) ? $tokens[2] : null );
283 if ( ! $user_id ) {
284 return new \WP_Error(
285 'friends_request_failed',
286 __( 'Could not respond to the request.', 'friends' ),
287 array(
288 'status' => 403,
289 )
290 );
291 }
292 $friend_user = new User( $user_id );
293 $remote_post_id = $request->get_param( 'post_id' );
294 $remote_post_ids = $friend_user->get_remote_post_ids();
295
296 if ( ! isset( $remote_post_ids[ $remote_post_id ] ) ) {
297 return array(
298 'deleted' => false,
299 );
300 }
301
302 $post_id = $remote_post_ids[ $remote_post_id ];
303 $post = \WP_Post::get_instance( $post_id );
304 if ( Friends::CPT === $post->post_type ) {
305 wp_delete_post( $post_id );
306 }
307
308 return array(
309 'deleted' => true,
310 );
311 }
312
313 public function rest_embed_friend_post( $request ) {
314 if ( empty( $_GET['url'] ) ) {
315 return false;
316 }
317 $post_id = $this->friends->feed->url_to_postid( $_GET['url'] );
318 if ( empty( $post_id ) ) {
319 return false;
320 }
321
322 if ( ! in_array( get_post_type( $post_id ), apply_filters( 'friends_frontend_post_types', array() ) ) ) {
323 return false;
324 }
325
326 enqueue_embed_scripts();
327 $post = get_post( $post_id );
328 $args = compact( 'post' );
329 setup_postdata( $post );
330
331 header( 'Content-type: text/html' );
332 Friends::template_loader()->get_template_part( 'embed/header-embed', null, $args );
333 Friends::template_loader()->get_template_part( 'embed/embed-content', null, $args );
334 exit;
335 }
336
337 /**
338 * Discover the REST URL for a friend site
339 *
340 * @param array $feeds The URL of the site.
341 * @return string|\WP_Error The REST URL or an error.
342 */
343 public function get_friends_rest_url( $feeds ) {
344 foreach ( $feeds as $feed_url => $feed ) {
345 if ( isset( $feed['parser'] ) && 'friends' === $feed['parser'] ) {
346 return $feed_url;
347 }
348 }
349
350 return false;
351 }
352
353 /**
354 * Discover the REST URL for a friend site
355 *
356 * @param string $url The URL of the site.
357 * @return string|\WP_Error The REST URL or an error.
358 */
359 public function discover_rest_url( $url ) {
360 if ( ! is_string( $url ) || ! Friends::check_url( $url ) ) {
361 return new \WP_Error( 'invalid-url-given', 'An invalid URL was given.' );
362 }
363
364 $response = wp_safe_remote_get(
365 $url,
366 array(
367 'timeout' => 20,
368 'redirection' => 5,
369 )
370 );
371
372 if ( is_wp_error( $response ) ) {
373 return $response;
374 }
375
376 if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
377 $dom = new \DOMDocument();
378 set_error_handler( '__return_null' );
379 $dom->loadHTML( wp_remote_retrieve_body( $response ) );
380 restore_error_handler();
381
382 $xpath = new \DOMXpath( $dom );
383 foreach ( $xpath->query( '//link[@rel and @href]' ) as $link ) {
384 if ( 'friends-base-url' === $link->getAttribute( 'rel' ) ) {
385 $rest_url = $link->getAttribute( 'href' );
386 if ( is_string( $rest_url ) && Friends::check_url( $rest_url ) ) {
387 return $rest_url;
388 }
389 }
390 }
391 }
392
393 return null;
394 }
395
396 /**
397 * Notify the friend's site via REST about the accepted friend request.
398 *
399 * Accepting a friend request is simply setting the role to "friend".
400 *
401 * @param int $user_id The user id.
402 * @param string $new_role The new role.
403 * @param string $old_roles The old roles.
404 */
405 public function notify_remote_friend_request_accepted( $user_id, $new_role, $old_roles ) {
406 if ( 'friend' !== $new_role && 'acquaintance' !== $new_role ) {
407 return;
408 }
409
410 $request_token = get_user_option( 'friends_request_id', $user_id );
411 if ( ! $request_token ) {
412 // We were accepted, so no need to notify the other.
413 return;
414 }
415
416 $friend_user = new User( $user_id );
417
418 $friend_rest_url = $friend_user->get_rest_url();
419 $request_id = $friend_user->get_user_option( 'friends_request_id' );
420 $future_out_token = $friend_user->get_user_option( 'friends_future_out_token' );
421 $future_in_token = wp_generate_password( 128, false );
422
423 $current_user = wp_get_current_user();
424 $response = wp_safe_remote_post(
425 $friend_rest_url . '/accept-friend-request',
426 array(
427 'body' => array(
428 'request' => $request_id,
429 'proof' => sha1( $future_out_token . $request_id ),
430 'key' => $future_in_token,
431 'name' => $current_user->display_name,
432 'icon_url' => get_avatar_url( $current_user->ID ),
433 ),
434 'timeout' => 20,
435 'redirection' => 5,
436 )
437 );
438
439 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
440 // TODO find a way to message the user.
441 return;
442 }
443
444 $json = json_decode( wp_remote_retrieve_body( $response ) );
445 if ( ! isset( $json->signature ) || sha1( $future_in_token . $future_out_token ) !== $json->signature ) {
446 $friend_user->set_role( 'friend_request' );
447 // TODO find a way to message the user.
448 return;
449 }
450
451 $friend_user->make_friend( $future_out_token, $future_in_token );
452 $friend_user->delete_user_option( 'friends_request_id' );
453 $friend_user->delete_user_option( 'friends_future_out_token' );
454
455 /*
456 TODO
457 if ( isset( $json->user_icon_url ) ) {
458 $this->friends->access_control->update_user_icon_url( $friend_user->ID, $json->user_icon_url );
459 }
460 When their friend request is no longer valid
461 $friend_user->set_role( 'pending_friend_request' );
462 if ( isset( $json->friend_request_pending ) ) {
463 }
464 }
465 */
466 }
467 }
468